Skip to content

Commit 5143ae9

Browse files
authored
Merge pull request src-d#183 from camathieu/multiple_files
1.2
2 parents 0c48f92 + 2bb19c8 commit 5143ae9

File tree

4 files changed

+181
-104
lines changed

4 files changed

+181
-104
lines changed

server/handlers/getArchive.go

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,22 @@ func GetArchive(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request
7272
// Get files to archive
7373
var files []*common.File
7474
for _, file := range upload.Files {
75-
// If upload has OneShot option, test if one of the files has not been already downloaded once
76-
if upload.OneShot && file.Status == "downloaded" {
77-
log.Warningf("File %s has already been downloaded", file.Name)
78-
common.Fail(ctx, req, resp, fmt.Sprintf("File %s has already been downloaded", file.Name), 404)
79-
return
75+
// Ignore uploading, missing, removed, one shot already downloaded,...
76+
if file.Status != "uploaded" {
77+
continue
8078
}
8179

82-
// If the file is marked as deleted by a previous call, we abort request
83-
if file.Status == "removed" {
84-
log.Warningf("File %s has been removed", file.Name)
85-
common.Fail(ctx, req, resp, "File %s has been removed", 404)
86-
return
80+
// Update metadata if oneShot option is set.
81+
// Doing this later would increase the window to race the condition.
82+
// To avoid the race completely AddOrUpdateFile should return the previous version of the metadata
83+
// and ensure proper locking ( which is the case of bolt and looks doable with mongodb but would break the interface ).
84+
if upload.OneShot {
85+
file.Status = "downloaded"
86+
err := metadataBackend.GetMetaDataBackend().AddOrUpdateFile(ctx, upload, file)
87+
if err != nil {
88+
log.Warningf("Error while deleting file %s from upload %s metadata : %s", file.Name, upload.ID, err)
89+
continue
90+
}
8791
}
8892

8993
files = append(files, file)
@@ -165,15 +169,6 @@ func GetArchive(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request
165169
return
166170
}
167171

168-
// Update metadata if oneShot option is set
169-
if upload.OneShot {
170-
file.Status = "downloaded"
171-
err = metadataBackend.GetMetaDataBackend().AddOrUpdateFile(ctx, upload, file)
172-
if err != nil {
173-
log.Warningf("Error while deleting file %s from upload %s metadata : %s", file.Name, upload.ID, err)
174-
}
175-
}
176-
177172
// File is piped directly to zip archive thus to the http response body without buffering
178173
_, err = io.Copy(fileWriter, fileReader)
179174
if err != nil {

server/handlers/getFile.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
150150
defer fileReader.Close()
151151

152152
// Update metadata if oneShot option is set
153+
// There is a small possible race from upload.OneShot && file.Status == "downloaded" to here.
154+
// To avoid the race completely AddOrUpdateFile should return the previous version of the metadata
155+
// and ensure proper locking ( which is the case of bolt and looks doable with mongodb but would break the interface ).
153156
if upload.OneShot {
154157
file.Status = "downloaded"
155158
err = metadataBackend.GetMetaDataBackend().AddOrUpdateFile(ctx, upload, file)

server/public/js/app.js

Lines changed: 114 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,13 @@ angular.module('api', ['ngFileUpload']).
198198
// Upload a file
199199
api.uploadFile = function(upload, file, progres_cb, basicAuth) {
200200
var mode = upload.stream ? "stream" : "file";
201-
var url = api.base + '/' + mode + '/' + upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
201+
var url;
202+
if (file.metadata.id) {
203+
url = api.base + '/' + mode + '/' + upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
204+
} else {
205+
// When adding file to an existing upload
206+
url = api.base + '/' + mode + '/' + upload.id;
207+
}
202208
return api.upload(url, file, null, progres_cb, basicAuth, upload.uploadToken);
203209
};
204210

@@ -398,6 +404,7 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
398404

399405
// Initialize main controller
400406
$scope.init = function () {
407+
$scope.mode = 'upload';
401408
// Display error from redirect if any
402409
var err = $location.search().err;
403410
if (!_.isUndefined(err)) {
@@ -408,6 +415,7 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
408415
} else {
409416
var code = $location.search().errcode;
410417
$dialog.alert({status: code, message: err});
418+
$location.search({});
411419
}
412420
} else {
413421
// Load current upload id
@@ -418,8 +426,10 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
418426
// Load upload from id
419427
$scope.load = function (id) {
420428
if (!id) return;
429+
$scope.mode = 'download';
421430
$scope.upload.id = id;
422-
$api.getUpload($scope.upload.id, $location.search().uploadToken)
431+
$scope.upload.uploadToken = $location.search().uploadToken;
432+
$api.getUpload($scope.upload.id, $scope.upload.uploadToken)
423433
.then(function (upload) {
424434
_.extend($scope.upload, upload);
425435
$scope.files = _.map($scope.upload.files, function (file) {
@@ -492,10 +502,24 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
492502
file.fileSize = file.size;
493503
file.fileType = file.type;
494504

505+
file.metadata = { status : "toUpload" };
506+
495507
$scope.files.push(file);
496508
});
497509
};
498510

511+
$scope.somethingToUpload = function() {
512+
return _.find($scope.files, function(file){
513+
if (file.metadata.status == "toUpload") return true;
514+
});
515+
};
516+
517+
$scope.somethingToDownload = function() {
518+
return _.find($scope.files, function(file){
519+
if (file.metadata.status == "uploaded") return true;
520+
});
521+
};
522+
499523
// Kikoo style water drop effect
500524
$scope.waterDrop = function(event){
501525
var body = $('body');
@@ -533,76 +557,84 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
533557
};
534558

535559
// Create a new upload
536-
$scope.newUpload = function () {
537-
if (!$scope.files.length) return;
538-
// Get TTL value
539-
if(!$scope.checkTTL()) return;
540-
$scope.upload.ttl = $scope.getTTL();
541-
// HTTP basic auth prompt dialog
542-
if ($scope.password && !($scope.upload.login && $scope.upload.password)) {
543-
$scope.getPassword();
544-
return;
545-
}
546-
// Yubikey prompt dialog
547-
if ($scope.config.yubikeyEnabled && $scope.yubikey && !$scope.upload.yubikey) {
548-
$scope.getYubikey();
549-
return;
550-
}
551-
// Create file to upload list
552-
$scope.upload.files = {};
553-
var ko = _.find($scope.files, function (file) {
554-
// Check file name length
555-
if(file.fileName.length > fileNameMaxLength) {
556-
$dialog.alert({
557-
status: 0,
558-
message: "File name max length is " + fileNameMaxLength + " characters"
559-
});
560-
return true; // break find loop
560+
$scope.newUpload = function (empty) {
561+
if (!empty && !$scope.files.length) return;
562+
if ($scope.upload.id) {
563+
// When adding file to an existing upload
564+
$scope.uploadFiles();
565+
} else {
566+
// Get TTL value
567+
if (!$scope.checkTTL()) return;
568+
$scope.upload.ttl = $scope.getTTL();
569+
// HTTP basic auth prompt dialog
570+
if ($scope.password && !($scope.upload.login && $scope.upload.password)) {
571+
$scope.getPassword();
572+
return;
561573
}
562-
// Check invalid characters
563-
if (!$scope.fileNameValidator(file.fileName)) {
564-
$dialog.alert({
565-
status: 0,
566-
message: "Invalid file name " + file.fileName + "\n",
567-
value: "Forbidden characters are : " + invalidCharList.join(' ')
568-
});
569-
return true; // break find loop
574+
// Yubikey prompt dialog
575+
if ($scope.config.yubikeyEnabled && $scope.yubikey && !$scope.upload.yubikey) {
576+
$scope.getYubikey();
577+
return;
570578
}
571-
// Sanitize file object
572-
$scope.upload.files[file.reference] = {
573-
fileName : file.fileName,
574-
fileType : file.fileType,
575-
fileSize : file.fileSize,
576-
reference : file.reference
577-
};
578-
});
579-
if(ko) return;
580-
$api.createUpload($scope.upload)
581-
.then(function (upload) {
582-
$scope.upload = upload;
583-
// Match file metadata using the reference
584-
_.each($scope.upload.files, function (file) {
585-
_.every($scope.files, function (f) {
586-
if (f.reference == file.reference) {
587-
f.metadata = file;
588-
return false;
589-
}
590-
return true;
579+
// Create file to upload list
580+
$scope.upload.files = {};
581+
var ko = _.find($scope.files, function (file) {
582+
// Check file name length
583+
if (file.fileName.length > fileNameMaxLength) {
584+
$dialog.alert({
585+
status: 0,
586+
message: "File name max length is " + fileNameMaxLength + " characters"
591587
});
592-
});
593-
$location.search('id', $scope.upload.id);
594-
$scope.uploadFiles();
595-
})
596-
.then(null, function (error) {
597-
$dialog.alert(error);
588+
return true; // break find loop
589+
}
590+
// Check invalid characters
591+
if (!$scope.fileNameValidator(file.fileName)) {
592+
$dialog.alert({
593+
status: 0,
594+
message: "Invalid file name " + file.fileName + "\n",
595+
value: "Forbidden characters are : " + invalidCharList.join(' ')
596+
});
597+
return true; // break find loop
598+
}
599+
// Sanitize file object
600+
$scope.upload.files[file.reference] = {
601+
fileName: file.fileName,
602+
fileType: file.fileType,
603+
fileSize: file.fileSize,
604+
reference: file.reference
605+
};
598606
});
607+
if (ko) return;
608+
$api.createUpload($scope.upload)
609+
.then(function (upload) {
610+
$scope.upload = upload;
611+
// Match file metadata using the reference
612+
_.each($scope.upload.files, function (file) {
613+
_.every($scope.files, function (f) {
614+
if (f.reference == file.reference) {
615+
f.metadata = file;
616+
f.metadata.status = "toUpload";
617+
return false;
618+
}
619+
return true;
620+
});
621+
});
622+
$location.search('id', $scope.upload.id);
623+
if (empty) $scope.setAdminUrl();
624+
$scope.uploadFiles();
625+
})
626+
.then(null, function (error) {
627+
$dialog.alert(error);
628+
});
629+
}
599630
};
600631

601632
// Upload every files
602633
$scope.uploadFiles = function () {
603634
if (!$scope.upload.id) return;
635+
$scope.mode = 'download';
604636
_.each($scope.files, function (file) {
605-
if (!file.metadata.id) return;
637+
if (!(file.metadata && file.metadata.status == "toUpload")) return;
606638
var progress = function (event) {
607639
// Update progress bar callback
608640
file.progress = parseInt(100.0 * event.loaded / event.total);
@@ -639,7 +671,7 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
639671
$api.removeFile($scope.upload, file)
640672
.then(function () {
641673
$scope.files = _.reject($scope.files, function (f) {
642-
return f.metadata.reference == file.metadata.reference;
674+
return f.metadata.id == file.metadata.id;
643675
});
644676
// Redirect to main page if no more files
645677
if (!$scope.files.length) {
@@ -652,6 +684,25 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
652684
});
653685
};
654686

687+
// Check if file is downloadable
688+
$scope.isDownloadable = function(file) {
689+
if ($scope.upload.stream) {
690+
if (file.metadata.status == 'missing') return true;
691+
} else {
692+
if (file.metadata.status == 'uploaded') return true;
693+
}
694+
return false;
695+
};
696+
697+
// Check if file is in a error status
698+
$scope.isOk = function(file) {
699+
if (file.metadata.status == 'toUpload') return true;
700+
else if (file.metadata.status == 'uploading') return true;
701+
else if (file.metadata.status == 'uploaded') return true;
702+
else if ($scope.upload.stream && file.metadata.status == 'missing') return true;
703+
return false;
704+
};
705+
655706
// Compute human readable size
656707
$scope.humanReadableSize = function (size) {
657708
if (_.isUndefined(size)) return;

0 commit comments

Comments
 (0)