Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FlowFolder #90

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,15 @@ parameter must be adjusted together with `progressCallbacksInterval` parameter.
* `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]`)

* `getFolderPathsInfo` function to get all files paths info. It will be passed a paths(string) array, a FlowFolder object and a callback function.And the callback function should be passed an Object(`{"a/": entryId or some other things, "a/c/": entryId or some other things}`) as parameter.

#### Properties

* `.support` A boolean value indicator whether or not Flow.js is supported by the current browser.
* `.supportDirectory` A boolean value, which indicates if 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 (see full docs for this object type below).
* `.parsedFiles` An array of `FlowFile` or `FlowFolder` file objects added by the user (see full docs for this object type below).

#### Methods

Expand Down Expand Up @@ -181,8 +182,8 @@ This event is also called before file is added to upload queue,
this means that calling `flow.upload()` function will not start current file upload.
Optionally, you can use the browser `event` object from when the file was
added.
* `.filesAdded(array, event)` Same as fileAdded, but used for multiple file validation.
* `.filesSubmitted(array, event)` Can be used to start upload of currently added files.
* `.filesAdded(array/*FlowFile*/, array/*FlowFolder*/, event)` Same as fileAdded, but used for multiple file validation.
* `.filesSubmitted(array/*FlowFile*/, array/*FlowFolder*/, event)` Can be used to start upload of currently added files.
* `.fileRetry(file, chunk)` Something went wrong during upload of a specific file, uploading is being
retried.
* `.fileError(file, message, chunk)` An error occurred during upload of a specific file.
Expand All @@ -197,6 +198,7 @@ FlowFile constructor can be accessed in `Flow.FlowFile`.
#### Properties

* `.flowObj` A back-reference to the parent `Flow` object.
* `.folderObj` A back-reference to the parent `FlowFolder` object if the file is in a file.
* `.file` The correlating HTML5 `File` object.
* `.name` The name of the file.
* `.relativePath` The relative path to the file (defaults to file name if relative path doesn't exist)
Expand All @@ -210,18 +212,47 @@ FlowFile constructor can be accessed in `Flow.FlowFile`.

#### Methods

* `.progress(relative)` Returns a float between 0 and 1 indicating the current upload progress of the file. If `relative` is `true`, the value is returned relative to all files in the Flow.js instance.
* `.progress()` Returns a float between 0 and 1 indicating the current upload progress of the file.
* `.pause()` Pause uploading the file.
* `.resume()` Resume uploading the file.
* `.cancel()` Abort uploading the file and delete it from the list of files to upload.
* `.retry()` Retry uploading the file.
* `.bootstrap()` Rebuild the state of a `FlowFile` object, including reassigning chunks and XMLHttpRequest instances.
* `.isUploading()` Returns a boolean indicating whether file chunks is uploading.
* `.isComplete()` Returns a boolean indicating whether the file has completed uploading and received a server response.
* `.isPaused()` Returns a boolean indicating whether file is paused.
* `.sizeUploaded()` Returns size uploaded in bytes.
* `.timeRemaining()` Returns remaining time to finish upload file in seconds. Accuracy is based on average speed. If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
* `.getExtension()` Returns file extension in lowercase.
* `.getType()` Returns file type.
* `.getSize()` Returns file size.
* `.hasError()` Returns a boolean indicating whether file has an error.

### FlowFolder
FlowFolder constructor can be accessed in `Flow.FlowFolder`.
#### Properties

* `.flowObj` A back-reference to the parent `Flow` object.
* `.files` An array of FlowFile file objects in the folder.
* `.name` The name of the folder.
* `.pathsInfo` The paths info of the folder.
* `.averageSpeed` Average upload speed, bytes per second.
* `.currentSpeed` Current upload speed, bytes per second.

#### Methods

* `.progress(relative)` Returns a float between 0 and 1 indicating the current upload progress of the folder files.
* `.pause()` Pause uploading the folder files.
* `.resume()` Resume uploading the folder files.
* `.cancel()` Abort uploading the folder files and delete them from the list of files to upload.
* `.retry()` Retry uploading the folder files.
* `.isUploading()` Returns a boolean indicating whether folder files is uploading.
* `.isComplete()` Returns a boolean indicating whether the folder files has completed uploading and received a server response.
* `.isPaused()` Returns a boolean indicating whether file is paused.
* `.sizeUploaded()` Returns size uploaded in bytes.
* `.timeRemaining()` Returns remaining time to finish upload folder files file in seconds. Accuracy is based on average speed. If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
* `.getSize()` Returns folder files size.
* `.hasError()` Returns a boolean indicating whether file has an error.

## Contribution

Expand Down
253 changes: 253 additions & 0 deletions samples/Node.js/public/folder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html>
<head>
<title>Flow.js - Multiple simultaneous, stable and resumable uploads via the HTML5 File API</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div id="frame">

<h1>Flow.js</h1>
<p>It's a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.</p>

<p>The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause and resume uploads without loosing state.</p>

<p>Flow.js relies on the HTML5 File API and the ability to chunks files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.</p>

<hr/>

<h3>Demo</h3>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="flow.js"></script>

<div class="flow-error">
Your browser, unfortunately, is not supported by Flow.js. The library requires support for <a href="http://www.w3.org/TR/FileAPI/">the HTML5 File API</a> along with <a href="http://www.w3.org/TR/FileAPI/#normalization-of-params">file slicing</a>.
</div>

<div class="flow-drop" ondragenter="jQuery(this).addClass('flow-dragover');" ondragend="jQuery(this).removeClass('flow-dragover');" ondrop="jQuery(this).removeClass('flow-dragover');">
Drop files here to upload or <a class="flow-browse-folder"><u>select folder</u></a> or <a class="flow-browse"><u>select from your computer</u></a> or <a class="flow-browse-image"><u>select images</u></a>
</div>

<div class="flow-progress">
<table>
<tr>
<td width="100%"><div class="progress-container"><div class="progress-bar"></div></div></td>
<td class="progress-text" nowrap="nowrap"></td>
<td class="progress-pause" nowrap="nowrap">
<a href="#" onclick="r.upload(); return(false);" class="progress-resume-link"><img src="resume.png" title="Resume upload" /></a>
<a href="#" onclick="r.pause(); return(false);" class="progress-pause-link"><img src="pause.png" title="Pause upload" /></a>
<a href="#" onclick="r.cancel(); return(false);" class="progress-cancel-link"><img src="cancel.png" title="Cancel upload" /></a>
</td>
</tr>
</table>
</div>

<ul class="flow-list"></ul>

<script>
(function () {
var showed = false;
var r = new Flow({
target: function(fileObj) {
if (fileObj.folderObj) {
// in a folder
// the target may be : '/upload/' + fileObj.folderObj.pathsInfo[fileObj.path]
!showed && console.log('pathsInfo', fileObj.folderObj.pathsInfo);
showed = true;
}
return '/upload';
},
query: function(fileObj) {
if (fileObj.folderObj) {
// in a folder
// we may be need to add the `parentId`
// pathsInfo: see getPathsInfo
return {
'parentId': fileObj.folderObj.pathsInfo[fileObj.path]
};
}
return {};
},
chunkSize: 1024*1024,
testChunks: false,
getFolderPathsInfo: function(paths, folderObj, callback) {
console.log('allPaths', paths);
setTimeout(function() {
console.log('got');
var ret = {};
paths.forEach(function(path) {
ret[path] = ~~(Math.random() * 100);
});
callback(ret)
}, 500);
}
});
// Flow.js isn't supported, fall back on a different method
if (!r.support) {
$('.flow-error').show();
return ;
}
// Show a place for dropping/selecting files
$('.flow-drop').show();
r.assignDrop($('.flow-drop')[0]);
r.assignBrowse($('.flow-browse')[0]);
r.assignBrowse($('.flow-browse-folder')[0], true);
r.assignBrowse($('.flow-browse-image')[0], false, false, {accept: 'image/*'});

var i = 1;
// Handle file add event
r.on('filesAdded', function(files, newParsedFiles){
console.log('parsedFiles', newParsedFiles);
// Show progress bar
$('.flow-progress, .flow-list').show();
newParsedFiles.forEach(function(file) {
file.__id__ = i++;
// Add the parsed file to the list
$('.flow-list').append(
'<li class="flow-file flow-file-'+file.__id__+'">' +
'Uploading <span class="flow-file-name"></span> ' +
'<span class="flow-file-size"></span> ' +
'<span class="flow-file-progress"></span> ' +
'<a href="" class="flow-file-download" target="_blank">' +
'Download' +
'</a> ' +
'<span class="flow-file-pause">' +
' <img src="pause.png" title="Pause upload" />' +
'</span>' +
'<span class="flow-file-resume">' +
' <img src="resume.png" title="Resume upload" />' +
'</span>' +
'<span class="flow-file-cancel">' +
' <img src="cancel.png" title="Cancel upload" />' +
'</span>'
);
var $self = $('.flow-file-'+file.__id__);
$self.find('.flow-file-name').text(file.name);
$self.find('.flow-file-size').text(readablizeBytes(file.getSize()));
$self.find('.flow-file-download').attr('href', '/download/' + file.__id__).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(files) {
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){
if (file.folderObj) {
if (file.folderObj.isComplete()) {
file = file.folderObj;
} else {
return;
}
}
var $self = $('.flow-file-'+file.__id__);
// 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.__id__).show();
});
r.on('fileError', function(file, message){
if (file.folderObj) {
if (file.folderObj.hasError()) {
file = file.folderObj;
} else {
return;
}
}
// Reflect that the file upload has resulted in error
$('.flow-file-'+file.__id__+' .flow-file-progress').html('(file could not be uploaded: '+message+')');
});
r.on('fileProgress', function(file){
if (file.folderObj) {
file = file.folderObj;
}
// Handle progress for both the file and the overall upload
$('.flow-file-'+file.__id__+' .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
};
})();

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);
}
</script>

</div>
</body>
</html>



Loading