Skip to content
Merged
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
296 changes: 271 additions & 25 deletions website/static/js/fangorn.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,129 @@ var COMMAND_KEYS = [224, 17, 91, 93];
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;

var PROVIDER_SETTINGS = {
// Bulk mount institution storage settings
'osfstorage': { parallelNum: 4, fileSizeThreshold: 128000000 }, // 128 MB

// Extend storage settings
's3': { parallelNum: 4, fileSizeThreshold: 128000000 },
's3compat': { parallelNum: 4, fileSizeThreshold: 128000000 },
'box': { parallelNum: 4, fileSizeThreshold: 128000000 },
'googledrive': { parallelNum: 4, fileSizeThreshold: 128000000 },
'nextcloud': { parallelNum: 4, fileSizeThreshold: 128000000 },
'onedrive': { parallelNum: 4, fileSizeThreshold: 128000000 },
'dropbox': { parallelNum: 1, fileSizeThreshold: null },
's3compatb3': { parallelNum: 4, fileSizeThreshold: 128000000 },
's3compatsigv4': { parallelNum: 4, fileSizeThreshold: 128000000 },
'azureblobstorage': { parallelNum: 1, fileSizeThreshold: null },
'dataverse': { parallelNum: 1, fileSizeThreshold: null },
'figshare': { parallelNum: 1, fileSizeThreshold: null },
'github': { parallelNum: 1, fileSizeThreshold: null },
'swift': { parallelNum: 1, fileSizeThreshold: null },
'owncloud': { parallelNum: 1, fileSizeThreshold: null },

// Institution storage settings
'dropboxbusiness': { parallelNum: 1, fileSizeThreshold: null },
'nextcloudinstitutions': { parallelNum: 1, fileSizeThreshold: null },
's3compatinstitutions': { parallelNum: 1, fileSizeThreshold: null },
'onedrivebusiness': { parallelNum: 1, fileSizeThreshold: null },
'ociinstitutions': { parallelNum: 1, fileSizeThreshold: null }
};

// Monkey-patch Dropzone.processQueue here (must run before any Dropzone instances are created)
(function() {
if (typeof Dropzone === 'undefined' || !Dropzone.prototype) {
return;
}
var _origProcessQueue = Dropzone.prototype.processQueue;

Dropzone.prototype.processQueue = function() {
var parallelUploads = this.options.parallelUploads;
var processingLength = this.getUploadingFiles().length;
// Start processing from the current number of uploading files
var uploadIndex = processingLength;
if (processingLength >= parallelUploads) {
return;
}
var queuedFiles = this.getQueuedFiles();
if (queuedFiles.length === 0) {
return;
}

var threshold = this.options.fileSizeThreshold || this.options.largeFileSize || this.options.largeFileThreshold;
// If a large file is already uploading, do not start new uploads
if (threshold) {
var uploading = this.getUploadingFiles();
for (var j = 0; j < uploading.length; j++) {
var uploadingFile = uploading[j];
var fsize = (typeof uploadingFile.size === 'number') ? uploadingFile.size : (uploadingFile.upload && uploadingFile.upload.total ? uploadingFile.upload.total : 0);
if (fsize > threshold) {
return;
}
}
}

if (this.options.uploadMultiple) {
// reuse original behavior but limit to available slots
return _origProcessQueue.apply(this, arguments);
} else {
var self = this;
while (uploadIndex < parallelUploads) {
if (!queuedFiles.length) {
return;
}
var next = queuedFiles[0];
var nextSize = (typeof next.size === 'number') ? next.size : (next.upload && next.upload.total ? next.upload.total : 0);

// Enforce single-thread upload for large files: check the threshold
// BEFORE starting the file. A large file may only start when nothing
// else is uploading and nothing has been started earlier in this
// processQueue() pass (uploadIndex === 0, since uploadIndex is seeded
// with the count of already-uploading files). Otherwise defer it so it
// never runs in parallel with another upload.
if (threshold && nextSize > threshold) {
if (uploadIndex > 0) {
// Other upload(s) already active/started in this pass — do not
// start the large file now; it will be retried once the current
// uploads complete and processQueue() runs again.
return;
}
// Nothing else active: start the large file on its own thread, then
// resume the queue only after it completes.
this.processFile(queuedFiles.shift());
var onComplete = function(file) {
if (file !== next) return;
// detach listener (support once()/off() APIs)
try {
if (self.off) { self.off('complete', onComplete); }
} catch (e) {}
// slight delay to allow Dropzone internal state update, then resume
setTimeout(function() {
if (self.options.autoProcessQueue) {
self.processQueue();
}
}, 0);
};
if (this.once) {
this.once('complete', onComplete);
} else {
this.on('complete', onComplete);
}
return;
}

// Small file: start it and keep filling the remaining parallel slots.
this.processFile(queuedFiles.shift());
uploadIndex++;
}
}
};
})();

function getProviderSettings(provider) {
return PROVIDER_SETTINGS[provider] || null;
}

function findByTempID(parent, tmpID) {
var child;
var item;
Expand Down Expand Up @@ -945,6 +1068,56 @@ function _fangornDragOver(treebeard, event) {
function _fangornDropzoneDrop(treebeard, event) {
var dropzoneHoverClass = 'fangorn-dz-hover';
treebeard.select('.tb-row').removeClass(dropzoneHoverClass);
var files = event.dataTransfer.files;
var totalFilesSize = 0;
var lastFile;
for (var i = 0; i < files.length; i++) {
totalFilesSize += files[i].size;
if (i === files.length-1) {
lastFile = files[i];
}
}

var item = treebeard.dropzoneItemCache;
var provider = 'unknown';
if (item && item.data) {
provider = item.data.provider;
}

// check upload quota for upload folder
treebeard.dropzone.options.limitQuota = false;
if (provider === 'osfstorage' || provider === 's3compatinstitutions') {
// call api get used quota and max quota
var quota = $.ajax({
async: false,
method: 'GET',
url: item.data.nodeApiUrl + 'get_creator_quota/',
});
if (!quota.responseJSON){
return;
}
quota = quota.responseJSON;
if (parseFloat(quota.used) + parseFloat(totalFilesSize) > quota.max) {
treebeard.dropzone.options.limitQuota = true;
treebeard.dropzone.options.lastFile = lastFile;
return;
} else if (parseFloat(quota.used) + parseFloat(totalFilesSize) > quota.max * window.contextVars.threshold) {
$osf.growl(
gettext('Quota usage alert'),
sprintf(gettext('You have used more than %1$s%% of your quota.'),(window.contextVars.threshold * 100)),
'warning'
);
}
}
// Set Dropzone settings for upload multiple file
var providerSettings = getProviderSettings(treebeard.dropzoneItemCache.data.provider);
if (providerSettings && providerSettings.parallelNum && providerSettings.fileSizeThreshold) {
treebeard.dropzone.options.parallelUploads = providerSettings.parallelNum;
treebeard.dropzone.options.fileSizeThreshold = providerSettings.fileSizeThreshold;
} else {
treebeard.dropzone.options.parallelUploads = 1;
treebeard.dropzone.options.fileSizeThreshold = null;
}
}
/**
* Runs when Dropzone's complete hook is run after upload is completed.
Expand Down Expand Up @@ -1116,6 +1289,27 @@ function _fangornDropzoneError(treebeard, file, message, xhr) {
*/
function _uploadEvent(event, item, col) {
var self = this; // jshint ignore:line
// clear cache of input before upload new folder
if (self.dropzone.hiddenFileInput) {
document.body.removeChild(self.dropzone.hiddenFileInput);
}

self.dropzone.hiddenFileInput = document.createElement('input');
self.dropzone.hiddenFileInput.setAttribute('type', 'file');
if (self.dropzone.options.maxFiles == null || self.dropzone.options.maxFiles > 1) {
self.dropzone.hiddenFileInput.setAttribute('multiple', 'multiple');
}
if (self.dropzone.options.acceptedFiles != null) {
self.dropzone.hiddenFileInput.setAttribute('accept', self.dropzone.options.acceptedFiles);
}
self.dropzone.hiddenFileInput.style.visibility = 'hidden';
self.dropzone.hiddenFileInput.style.position = 'absolute';
self.dropzone.hiddenFileInput.style.top = '0';
self.dropzone.hiddenFileInput.style.left = '0';
self.dropzone.hiddenFileInput.style.height = '0';
self.dropzone.hiddenFileInput.style.width = '0';
document.body.appendChild(self.dropzone.hiddenFileInput);

try {
event.stopPropagation();
} catch (e) {
Expand All @@ -1132,10 +1326,58 @@ function _uploadEvent(event, item, col) {

function _onchange() {
var files = self.dropzone.hiddenFileInput.files || [];
var totalFilesSize = 0;
for (var i = 0; i < files.length; i++) {
totalFilesSize += files[i].size;
}

var provider = 'unknown';
if (item && item.data) {
provider = item.data.provider;
}

// check upload quota for upload folder
self.dropzone.options.limitQuota = false;
if (provider === 'osfstorage' || provider === 's3compatinstitutions') {
// call api get used quota and max quota
var quota = $.ajax({
async: false,
method: 'GET',
url: item.data.nodeApiUrl + 'get_creator_quota/',
});
if (!quota.responseJSON){
return;
}
quota = quota.responseJSON;

if (parseFloat(quota.used) + parseFloat(totalFilesSize) > quota.max) {
self.dropzone.options.limitQuota = true;
var msgText = gettext('Not enough quota to upload the file.');
item.notify.update(msgText, 'warning', undefined, 3000);
self.uploadStates = [];
return;
} else if (parseFloat(quota.used) + parseFloat(totalFilesSize) > quota.max * window.contextVars.threshold) {
$osf.growl(
gettext('Quota usage alert'),
sprintf(gettext('You have used more than %1$s%% of your quota.'),(window.contextVars.threshold * 100)),
'warning'
);
}
}
// Set Dropzone settings for upload multiple file
var providerSettings = getProviderSettings(provider);
if (providerSettings && providerSettings.parallelNum && providerSettings.fileSizeThreshold) {
self.dropzone.options.parallelUploads = providerSettings.parallelNum;
self.dropzone.options.fileSizeThreshold = providerSettings.fileSizeThreshold;
}else{
// Default settings
self.dropzone.options.parallelUploads = 1;
self.dropzone.options.fileSizeThreshold = null;
}

// Add files to Treebeard
for (var i = 0; i < files.length; i++) {
self.dropzone.addFile(files[i]);
for (var j = 0; j < files.length; j++) {
self.dropzone.addFile(files[j]);
}
}
}
Expand Down Expand Up @@ -1230,6 +1472,22 @@ function _uploadFolderEvent(event, item, mode, col) {
'danger', 5000);
return;
}
if (parseFloat(quota.used) + parseFloat(totalFilesSize) > quota.max * window.contextVars.threshold) {
$osf.growl(
gettext('Quota usage alert'),
sprintf(gettext('You have used more than %1$s%% of your quota.'),(window.contextVars.threshold * 100)),
'warning'
);
}
}
// Set Dropzone settings for upload folder\
var providerSettings = getProviderSettings(item.data.provider);
if (providerSettings && providerSettings.parallelNum && providerSettings.fileSizeThreshold) {
tb.dropzone.options.parallelUploads = providerSettings.parallelNum;
tb.dropzone.options.fileSizeThreshold = providerSettings.fileSizeThreshold;
}else{
tb.dropzone.options.parallelUploads = 1;
tb.dropzone.options.fileSizeThreshold = null;
}

var createdFolders = [];
Expand Down Expand Up @@ -3961,6 +4219,17 @@ tbOptions = {
var msgText;
var quota;
if (_fangornCanDrop(treebeard, item)) {
var limitQuota = treebeard.dropzone.options.limitQuota || false;
if (limitQuota) {
if (file === treebeard.dropzone.options.lastFile) {
msgText = gettext('Not enough quota to upload the file.');
item.notify.update(msgText, 'warning', undefined, 3000);
}
addFileStatus(treebeard, file, false, msgText, '');
removeFromUI(file, treebeard);
return false;
}

if (item.data.accept && item.data.accept.maxSize) {
size = file.size / 1000000;
maxSize = item.data.accept.maxSize;
Expand All @@ -3974,29 +4243,6 @@ tbOptions = {
return false;
}
}
if ((item.data.provider === 'osfstorage' || item.data.provider === 's3compatinstitutions') && !isInUploadFolderProcess) {
quota = $.ajax({
async: false,
method: 'GET',
url: item.data.nodeApiUrl + 'get_creator_quota/'
});
if (quota.responseJSON) {
quota = quota.responseJSON;
if (quota.used + file.size > quota.max) {
msgText = gettext('Not enough quota to upload the file.');
item.notify.update(msgText, 'warning', undefined, 3000);
addFileStatus(treebeard, file, false, msgText, '');
return false;
}
if (quota.used + file.size > quota.max * window.contextVars.threshold) {
$osf.growl(
gettext('Quota usage alert'),
sprintf(gettext('You have used more than %1$s%% of your quota.'),(window.contextVars.threshold * 100)),
'warning'
);
}
}
}
return true;
}
return false;
Expand Down
3 changes: 3 additions & 0 deletions website/translations/en/LC_MESSAGES/js_messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -9289,6 +9289,9 @@ msgstr ""
msgid "The Integrated Admin added ${contributors} as contributor(s) to ${node}"
msgstr ""

msgid "Not enough quota to upload. The total size of all files is %1$s."
msgstr ""

msgid "${user} added group ${mapcore_groups} to ${node}"
msgstr ""

Expand Down
3 changes: 3 additions & 0 deletions website/translations/ja/LC_MESSAGES/js_messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -10911,6 +10911,9 @@ msgstr ""
" <li>このアドオンを利用すると外部サービスにファイルを保存できます。このアドオンに追加したファイルはGakuNin RDM内には保存されません。</li>\n"
"</ul>\n"

msgid "Not enough quota to upload. The total size of all files is %1$s."
msgstr "アップロードするための空き容量が足りません。全ファイルの合計サイズは %1$s です"

msgid ""
"\n"
"\n"
Expand Down
3 changes: 3 additions & 0 deletions website/translations/js_messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -9242,6 +9242,9 @@ msgstr ""
msgid "The Integrated Admin added ${contributors} as contributor(s) to ${node}"
msgstr ""

msgid "Not enough quota to upload. The total size of all files is %1$s."
msgstr ""

msgid "${user} added group ${mapcore_groups} to ${node}"
msgstr ""

Expand Down
Loading