### Run Node.js Sample with Express Source: https://github.com/flowjs/flow.js/blob/master/samples/Node.js/README.md Instructions to install and run the Node.js sample application for flow.js using npm and Node.js. This sample requires the Express framework for cleaner code and will upload files to the 'samples/Node.js/tmp' directory. ```bash cd samples/Node.js npm install node app.js ``` -------------------------------- ### Install Flow.js via npm Source: https://github.com/flowjs/flow.js/blob/master/README.md This command installs the latest version of the Flow.js library using the npm package manager. Ensure you have Node.js and npm installed on your system. ```bash npm install @flowjs/flow.js ``` -------------------------------- ### PHP: Create Temporary Directory for File Uploads Source: https://github.com/flowjs/flow.js/blob/master/samples/Backend on PHP.md This PHP code snippet creates a temporary directory to store uploaded file chunks. It utilizes `mkdir` with specific permissions (0777) and the `true` flag to recursively create parent directories if they do not exist. This ensures a dedicated space for fragmented uploads. ```php mkdir($temp_dir, 0777, true); ``` -------------------------------- ### Complete HTML/JavaScript File Upload UI with Flow.js Source: https://context7.com/flowjs/flow.js/llms.txt This snippet provides a full HTML and JavaScript example for creating a file upload interface. It utilizes Flow.js for handling the upload logic, including drag-and-drop support, progress indication, and upload controls. The UI elements are styled with basic CSS. ```html
' + '' + '' + '
' + '' + '' + '
' + '' ); var $self = $('.flow-file-'+file.uniqueIdentifier); $self.find('.flow-file-name').text(file.name); $self.find('.flow-file-size').text(readablizeBytes(file.size)); $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).hide(); $self.find('.flow-file-pause').on('click', function () { file.pause(); $self.find('.flow-file-pause').hide(); $self.find('.flow-file-resume').show(); }); $self.find('.flow-file-resume').on('click', function () { file.resume(); $self.find('.flow-file-pause').show(); $self.find('.flow-file-resume').hide(); }); $self.find('.flow-file-cancel').on('click', function () { file.cancel(); $self.remove(); }); }); r.on('filesSubmitted', function(file) { r.upload(); }); r.on('complete', function(){ // Hide pause/resume when the upload has completed $('.flow-progress .progress-resume-link, .flow-progress .progress-pause-link').hide(); }); r.on('fileSuccess', function(file,message){ var $self = $('.flow-file-'+file.uniqueIdentifier); // Reflect that the file upload has completed $self.find('.flow-file-progress').text('(completed)'); $self.find('.flow-file-pause, .flow-file-resume').remove(); $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).show(); }); r.on('fileError', function(file, message){ // Reflect that the file upload has resulted in error $('.flow-file-'+file.uniqueIdentifier+' .flow-file-progress').html('(file could not be uploaded: '+message+')'); }); r.on('fileProgress', function(file){ // Handle progress for both the file and the overall upload $('.flow-file-'+file.uniqueIdentifier+' .flow-file-progress') .html(Math.floor(file.progress()*100) + '% ' + readablizeBytes(file.averageSpeed) + '/s ' + secondsToStr(file.timeRemaining()) + ' remaining') ; $('.progress-bar').css({width:Math.floor(r.progress()*100) + '%'}); }); r.on('uploadStart', function(){ // Show pause, hide resume $('.flow-progress .progress-resume-link').hide(); $('.flow-progress .progress-pause-link').show(); }); r.on('catchAll', function() { console.log.apply(console, arguments); }); window.r = { pause: function () { r.pause(); // Show resume, hide pause $('.flow-file-resume').show(); $('.flow-file-pause').hide(); $('.flow-progress .progress-resume-link').show(); $('.flow-progress .progress-pause-link').hide(); }, cancel: function() { r.cancel(); $('.flow-file').remove(); }, upload: function() { $('.flow-file-pause').show(); $('.flow-file-resume').hide(); r.resume(); }, flow: r }; })();
```
--------------------------------
### Resumable.js Client-Side Options for Octet Uploads
Source: https://github.com/flowjs/flow.js/blob/master/samples/java/README.md
Configuration options for initializing Resumable.js on the client-side to work with a server that supports 'octet' uploads. Key settings include the target upload URL, chunk size, simultaneous uploads, and enabling `testChunks` for resumability.
```JavaScript
var r = new Resumable({
target:'/test/upload',
chunkSize:1*1024*1024,
simultaneousUploads:4,
testChunks: true,
throttleProgressCallbacks:1,
method: "octet"
});
```
--------------------------------
### Enable Cross-domain Uploads in Node.js
Source: https://github.com/flowjs/flow.js/blob/master/samples/Node.js/README.md
Configuration steps to enable cross-domain uploads for the Node.js flow.js sample. This involves uncommenting specific lines in app.js to allow 'Access-Control-Allow-Origin' and updating the target URL in public/index.html.
```javascript
// In app.js (uncomment lines 24-31 and line 17)
// Example: Enabling CORS
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
// In public/index.html (update target on line 49)
// Example: target: 'http://www.example.com/upload'
```
--------------------------------
### Tcl Procedure for Handling Resumable Uploads in AOLserver
Source: https://github.com/flowjs/flow.js/blob/master/samples/Backend on AOLserver and OpenACS.md
This Tcl procedure, `handle_resumable_file`, is designed to process resumable file uploads initiated by Flow.js. It validates incoming chunks, checks file sizes against limits, saves individual chunks, and concatenates them into a final file upon completion. It also handles GET requests to check the status of existing chunks.
```tcl
ad_proc handle_resumable_file {
{-file_parameter_name "file"}
{-folder "/tmp"}
-check_video:boolean
{-max_file_size ""}
} {} {
# Check parameter to see if this is indeed a resumable
ad_page_contract {} {
{resumableChunkNumber:integer "0"}
{resumableChunkSize:integer "0"}
{resumableTotalSize:integer "0"}
{resumableIdentifier ""}
{resumableFilename ""}
}
# Clean up the identifier
regsub -all {[^0-9A-Za-z_-]} $resumableIdentifier "" resumableIdentifier
# Check if the request is sane
if { $resumableChunkNumber==0 || $resumableChunkSize==0 || $resumableTotalSize==0 || $resumableIdentifier eq "" } {
return "non_resumable_request"
}
set number_of_chunks [expr int(floor($resumableTotalSize/($resumableChunkSize*1.0)))]
if { $number_of_chunks==0 } {set number_of_chunks 1}
if { $resumableChunkNumber>$number_of_chunks } {
return "invalid_resumable_request"
}
# What would the file name be?
set filename [file join $folder "resumable-${resumableIdentifier}.${resumableChunkNumber}"]
# If this is a GET request, we should tell the uploader if the file is already in place or not
if { [ns_conn method] eq "GET" } {
if { [file exists $filename] && [file size $filename]==$resumableChunkSize } {
doc_return 200 text/plain "ok"
} else {
doc_return 204 text/plain "not found"
}
ad_script_abort
}
# Assign a tmp file
ad_page_contract {} [list "${file_parameter_name}:trim" "${file_parameter_name}.tmpfile:tmpfile"]
set tmp_filename [set "${file_parameter_name}.tmpfile"]
if { $resumableFilename ne "" } {
set original_filename $resumableFilename
} else {
set original_filename [set $file_parameter_name]
}
# Check data size
if { $max_file_size ne "" && $resumableTotalSize>$max_file_size } {
return [list "invalid_resumable_request" "The file is too large" $original_filename]
} elseif { $resumableChunkNumber<$number_of_chunks && [file size $tmp_filename]!=$resumableChunkSize } {
return [list "invalid_resumable_request" "Wrong data size" $original_filename]
} elseif { $number_of_chunks>1 && $resumableChunkNumber==$number_of_chunks && [file size $tmp_filename] != [expr ($resumableTotalSize % $resumableChunkSize) + $resumableChunkSize] } {
return [list "invalid_resumable_request" "Wrong data size" $original_filename]
} elseif { $number_of_chunks==1 && [file size $tmp_filename] != $resumableTotalSize } {
return [list "invalid_resumable_request" "Wrong data size" $original_filename]
}
# Save the chunk
file mkdir $folder
file copy -force $tmp_filename $filename
# Try collating the first and last chunk -- and identify
if { $check_video_p && ($resumableChunkNumber==1 || $resumableChunkNumber==$number_of_chunks) } {
## (Here you can do check on first and last chunk if needed
## For example, we will check if this is a support video file.)
}
# Check if all chunks have come in
set chunk_num 1
set chunk_files [list]
while { $chunk_num<=$number_of_chunks } {
set chunk_filename [file join $folder "resumable-${resumableIdentifier}.${chunk_num}"]
if { ![file exists $chunk_filename] } {
return [list "partly_done" $filename $original_filename]
}
lappend chunk_files $chunk_filename
incr chunk_num
}
# We've come this far, meaning that all the pieces are in place
set output_filename [file join $folder "resumable-${resumableIdentifier}.final"]
foreach file $chunk_files {
exec cat $file >> $output_filename
catch {
file delete $file
}
}
return [list "done" $output_filename $original_filename $resumableIdentifier]
}
```
--------------------------------
### Utility Functions for File Uploads (JavaScript)
Source: https://github.com/flowjs/flow.js/blob/master/samples/Node.js/public/index.html
Provides utility functions to format byte sizes into human-readable strings (e.g., KB, MB, GB) and to convert a duration in seconds into a human-readable string format (e.g., minutes, hours, days). These are used for displaying file progress and estimated time remaining.
```javascript
function readablizeBytes(bytes) {
var s = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
var e = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e];
}
function secondsToStr (temp) {
function numberEnding (number) {
return (number > 1) ? 's' : '';
}
var years = Math.floor(temp / 31536000);
if (years) {
return years + ' year' + numberEnding(years);
}
var days = Math.floor((temp %= 31536000) / 86400);
if (days) {
return days + ' day' + numberEnding(days);
}
var hours = Math.floor((temp %= 86400) / 3600);
if (hours) {
return hours + ' hour' + numberEnding(hours);
}
var minutes = Math.floor((temp %= 3600) / 60);
if (minutes) {
return minutes + ' minute' + numberEnding(minutes);
}
var seconds = temp % 60;
return seconds + ' second' + numberEnding(seconds);
}
```
--------------------------------
### PHP Server for Chunked File Uploads with Flow.js
Source: https://context7.com/flowjs/flow.js/llms.txt
This PHP script handles chunked file uploads from Flow.js. It supports checking if a chunk already exists via GET requests and receiving/storing uploaded chunks via POST requests. After all chunks are received, it merges them into a single file and cleans up temporary directories. Dependencies include basic PHP file system functions.
```php
= ($total_size - $chunk_size + 1)) {
// All chunks received, merge them
$final_file = 'uploads/' . $_POST['flowFilename'];
$fp = fopen($final_file, 'w');
for ($i = 1; $i <= $total_files; $i++) {
$chunk_path = $temp_dir . '/' . $_POST['flowFilename'] . '.part' . $i;
fwrite($fp, file_get_contents($chunk_path));
unlink($chunk_path);
}
fclose($fp);
rmdir($temp_dir);
}
header("HTTP/1.0 200 Ok");
}
}
?>
```
--------------------------------
### Configuration Options
Source: https://github.com/flowjs/flow.js/blob/master/README.md
Configuration options for Flow.js instances, affecting upload behavior and response handling.
```APIDOC
## Configuration Options
### `chunkSize`
The size of each chunk in bytes. For larger files, smaller chunk sizes can improve reliability. (Default: 1MB)
### `autoUpload`
Automatically upload files as they are added. (Default: false)
### `maxChunkRetries`
Maximum number of retries for a chunk upload before failing. (Default: 3)
### `chunkRetryInterval`
Time in seconds to wait before retrying a chunk upload. (Default: undefined)
### `simultaneousUploads`
Maximum number of simultaneous chunk uploads. Setting this to 1.0 means that the average upload speed will be equal to the current upload speed. For longer file uploads it is better set this number to 0.02, because time remaining estimation will be more accurate. This parameter must be adjusted together with `progressCallbacksInterval` parameter. (Default: 3)
### `progressCallbacksInterval`
Interval in milliseconds for progress callbacks. (Default: 0.1)
### `successStatuses`
Response is success if response status is in this list. (Default: `[200,201,202]`)
### `permanentErrors`
Response fails if response status is in this list. (Default: `[404, 415, 500, 501]`)
```
--------------------------------
### Initialize Flow.js Upload Object
Source: https://github.com/flowjs/flow.js/blob/master/README.md
This JavaScript code snippet demonstrates how to create a new Flow object. It specifies the target URL for uploads and can include additional query parameters. It also includes a check for Flow.js support, falling back to an alternative method if not supported.
```javascript
var flow = new Flow({
target:'/api/photo/redeem-upload-token',
query:{upload_token:'my_token'}
});
// Flow.js isn't supported, fall back on a different method
if(!flow.support) location.href = '/some-old-crappy-uploader';
```
--------------------------------
### Control Upload Flow with Flow.js
Source: https://context7.com/flowjs/flow.js/llms.txt
Demonstrates how to control the overall upload process using methods like upload(), pause(), resume(), and cancel(). It also shows how to check the upload status and retrieve progress information. This snippet assumes a 'flow' object is already initialized.
```javascript
document.getElementById('uploadBtn').addEventListener('click', function() {
flow.upload();
});
document.getElementById('pauseBtn').addEventListener('click', function() {
flow.pause();
});
document.getElementById('resumeBtn').addEventListener('click', function() {
flow.resume();
});
document.getElementById('cancelBtn').addEventListener('click', function() {
flow.cancel();
});
if (flow.isUploading()) {
console.log('Upload in progress');
}
var progress = flow.progress();
console.log('Upload progress:', Math.floor(progress * 100) + '%');
console.log('Total size:', flow.getSize(), 'bytes');
console.log('Uploaded:', flow.sizeUploaded(), 'bytes');
console.log('Time remaining:', flow.timeRemaining(), 'seconds');
```
--------------------------------
### Flow.js Server-Side Chunk Handling
Source: https://github.com/flowjs/flow.js/blob/master/README.md
Explains the parameters sent with each chunk upload and the expected server responses for managing upload state.
```APIDOC
## Server-Side Chunk Handling for Flow.js
### Description
Flow.js requires server-side logic to reassemble file chunks. This section details the parameters sent with each chunk and the appropriate HTTP status codes for server responses.
### Method
POST
### Endpoint
`/upload` (or your configured upload endpoint)
### Parameters
#### Query Parameters
- **flowChunkNumber** (integer) - Required - The index of the current chunk (starts at 1).
- **flowTotalChunks** (integer) - Required - The total number of chunks for the file.
- **flowChunkSize** (integer) - Required - The size of each chunk in bytes.
- **flowTotalSize** (integer) - Required - The total size of the file in bytes.
- **flowIdentifier** (string) - Required - A unique identifier for the file.
- **flowFilename** (string) - Required - The original filename.
- **flowRelativePath** (string) - Optional - The relative path of the file when uploading a directory.
### Response
#### Success Response (2xx)
- **200, 201, 202**: Accepted - The chunk was received correctly and no re-upload is needed.
#### Error Response
- **404, 415, 500, 501**: Permanent Error - The file is not supported; cancel the entire upload.
- **Other codes**: Retry - An error occurred, but the chunk should be re-uploaded.
### Note
Allow for chunks to be uploaded multiple times, as Flow.js is designed to handle unstable network conditions. The last chunk's data size may be smaller than `flowChunkSize`.
```
--------------------------------
### Go Flow.js Upload Handler
Source: https://github.com/flowjs/flow.js/blob/master/samples/Backend on Go.md
This Go code implements the backend for handling Flow.js chunked uploads. It defines routes for checking chunk existence and receiving uploaded chunks. The handler parses multipart form data, saves chunks to disk, and orchestrates the assembly of the complete file upon receiving the final chunk. It assumes the last chunk is the final piece of the file.
```go
package main
import (
"bytes"
"github.com/codegangsta/martini"
"github.com/codegangsta/martini-contrib/render"
"io"
"io/ioutil"
"net/http"
"os"
"sort"
"strconv"
"strings"
)
var completedFiles = make(chan string, 100)
func main() {
for i := 0; i < 3; i++ {
go assembleFile(completedFiles)
}
m := martini.Classic()
m.Use(render.Renderer(render.Options{
Layout: "layout",
Delims: render.Delims{"{\[[", "]]}"},
Extensions: []string{”.html”}}))
m.Get("/", func(r render.Render) {
r.HTML(200, "index", nil)
})
m.Post("/upload", streamHandler(chunkedReader))
m.Get("/upload", continueUpload)
m.Run()
}
type ByChunk []os.FileInfo
func (a ByChunk) Len() int { return len(a) }
func (a ByChunk) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByChunk) Less(i, j int) bool {
ai, _ := strconv.Atoi(a[i].Name())
aj, _ := strconv.Atoi(a[j].Name())
return ai < aj
}
type streamHandler func(http.ResponseWriter, *http.Request) error
func (fn streamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
func continueUpload(w http.ResponseWriter, r *http.Request) {
chunkDirPath := "./incomplete/" + r.FormValue("flowFilename") + "/" + r.FormValue("flowChunkNumber")
if _, err := os.Stat(chunkDirPath); err != nil {
w.WriteHeader(204)
return
}
}
func chunkedReader(w http.ResponseWriter, r *http.Request) error {
r.ParseMultipartForm(25)
chunkDirPath := "./incomplete/" + r.FormValue("flowFilename")
err := os.MkdirAll(chunkDirPath, 02750)
if err != nil {
return err
}
for _, fileHeader := range r.MultipartForm.File["file"] {
src, err := fileHeader.Open()
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(chunkDirPath + "/" + r.FormValue("flowChunkNumber"))
if err != nil {
return err
}
defer dst.Close()
io.Copy(dst, src)
fileInfos, err := ioutil.ReadDir(chunkDirPath)
if err != nil {
return err
}
cT, err := strconv.Atoi(chunkTotal)
if err != nil {
return err
}
if len(fileInfos) == cT {
completedFiles <- chunkDirPath
}
}
return nil
}
func assembleFile(jobs <-chan string) {
for path := range jobs {
fileInfos, err := ioutil.ReadDir(path)
if err != nil {
return
}
// create final file to write to
dst, err := os.Create(strings.Split(path, "/")[2])
if err != nil {
return
}
defer dst.Close()
sort.Sort(ByChunk(fileInfos))
for _, fs := range fileInfos {
src, err := os.Open(path + "/" + fs.Name())
if err != nil {
return
}
defer src.Close()
io.Copy(dst, src)
}
os.RemoveAll(path)
}
}
```
--------------------------------
### Flow.js Properties
Source: https://github.com/flowjs/flow.js/blob/master/README.md
Properties available on a Flow.js instance.
```APIDOC
## Properties
### `.support`
A boolean value indicating whether or not Flow.js is supported by the current browser.
### `.supportDirectory`
A boolean value, which indicates if the browser supports directory uploads.
### `.opts`
A hash object of the configuration of the Flow.js instance.
### `.files`
An array of `FlowFile` file objects added by the user.
```
--------------------------------
### Flow.js Methods
Source: https://github.com/flowjs/flow.js/blob/master/README.md
Methods available on a Flow.js instance for managing uploads.
```APIDOC
## Methods
### `.assignBrowse(domNodes, isDirectory, singleFile, attributes)`
Assign a browse action to one or more DOM nodes.
- `domNodes`: array of DOM nodes or a single node.
- `isDirectory`: Pass `true` to allow directories to be selected (Chrome only, support can be checked with `supportDirectory` property).
- `singleFile`: Set to `true` to prevent multiple file uploads. Also look at config parameter `singleFile`.
- `attributes`: Pass an object of keys and values to set custom attributes on input fields. For example, you can set `accept` attribute to `image/*`. This means that the user will be able to select only images. Full list of attributes: https://www.w3.org/wiki/HTML/Elements/input/file. Note: avoid using `a` and `button` tags as file upload buttons, use span instead.
### `.assignDrop(domNodes)`
Assign one or more DOM nodes as a drop target.
### `.unAssignDrop(domNodes)`
Unassign one or more DOM nodes as a drop target.
### `.on(event, callback)`
Listen for an event from Flow.js.
### `.off([event, [callback]])`
- `.off()`: All events are removed.
- `.off(event)`: Remove all callbacks of a specific event.
- `.off(event, callback)`: Remove a specific callback of an event. `callback` should be a `Function`.
### `.upload()`
Start or resume uploading.
### `.pause()`
Pause uploading.
### `.resume()`
Resume uploading.
### `.cancel()`
Cancel upload of all `FlowFile` objects and remove them from the list.
### `.progress()`
Returns a float between 0 and 1 indicating the current upload progress of all files.
### `.isUploading()`
Returns a boolean indicating whether or not the instance is currently uploading anything.
### `.addFile(file)`
Add an HTML5 `File` object to the list of files.
### `.removeFile(file)`
Cancel upload of a specific `FlowFile` object from the list.
### `.getFromUniqueIdentifier(uniqueIdentifier)`
Look up a `FlowFile` object by its unique identifier.
### `.getSize()`
Returns the total size of the upload in bytes.
### `.sizeUploaded()`
Returns the total size uploaded of all files in bytes.
### `.timeRemaining()`
Returns the remaining time to upload all files in seconds. Accuracy is based on average speed. If speed is zero, time remaining will be equal to `Number.POSITIVE_INFINITY`.
```