Check for duplicate without uploading (checksum calculated on the client)
This commit is contained in:
parent
b4378c2a25
commit
04126634ce
8
lib-typedarrays-min.js
vendored
Normal file
8
lib-typedarrays-min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(function(){if("function"==typeof ArrayBuffer){var b=CryptoJS.lib.WordArray,e=b.init;(b.init=function(a){a instanceof ArrayBuffer&&(a=new Uint8Array(a));if(a instanceof Int8Array||a instanceof Uint8ClampedArray||a instanceof Int16Array||a instanceof Uint16Array||a instanceof Int32Array||a instanceof Uint32Array||a instanceof Float32Array||a instanceof Float64Array)a=new Uint8Array(a.buffer,a.byteOffset,a.byteLength);if(a instanceof Uint8Array){for(var b=a.byteLength,d=[],c=0;c<b;c++)d[c>>>2]|=a[c]<<
|
||||
24-8*(c%4);e.call(this,d,b)}else e.apply(this,arguments)}).prototype=b}})();
|
@ -39,6 +39,7 @@ final class Admin extends Access {
|
||||
case 'Photo::setTags': self::setPhotoTagsAction(); break;
|
||||
case 'Photo::duplicate': self::duplicatePhotoAction(); break;
|
||||
case 'Photo::delete': self::deletePhotoAction(); break;
|
||||
case 'Photo::isDuplicate': self::isDuplicateAction(); break;
|
||||
|
||||
// Add functions
|
||||
case 'Photo::add': self::uploadAction(); break;
|
||||
@ -224,6 +225,19 @@ final class Admin extends Access {
|
||||
|
||||
}
|
||||
|
||||
private static function isDuplicateAction() {
|
||||
|
||||
Validator::required(isset($_POST['hash']), __METHOD__);
|
||||
|
||||
$photo = new Photo(null);
|
||||
$ret = $photo->exists($_POST['hash']);
|
||||
if($ret!=false){
|
||||
$ret = true;
|
||||
}
|
||||
Response::json($ret);
|
||||
|
||||
}
|
||||
|
||||
// Add functions
|
||||
|
||||
private static function uploadAction() {
|
||||
|
@ -258,7 +258,7 @@ final class Photo {
|
||||
/**
|
||||
* @return array|false Returns a subset of a photo when same photo exists or returns false on failure.
|
||||
*/
|
||||
private function exists($checksum, $photoID = null) {
|
||||
public function exists($checksum, $photoID = null) {
|
||||
|
||||
// Exclude $photoID from select when $photoID is set
|
||||
if (isset($photoID)) $query = Database::prepare(Database::get(), "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' AND id <> '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum, $photoID));
|
||||
|
15
sha1.js
Normal file
15
sha1.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
|
||||
n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b<a;b++)c[d+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[d+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
|
||||
32-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*e.random()|0);return new n.init(c,a)}}),b=p.enc={},h=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++){var f=c[d>>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d+=2)b[d>>>3]|=parseInt(a.substr(d,
|
||||
2),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++)b.push(String.fromCharCode(c[d>>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d++)b[d>>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}},
|
||||
k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;g<a;g+=f)this._doProcessBlock(b,g);g=b.splice(0,a);c.sigBytes-=d}return new n.init(g,d)},clone:function(){var a=f.clone.call(this);
|
||||
a._data=this._data.clone();return a},_minBufferSize:0});j.Hasher=k.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){k.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,b){return(new a.init(b)).finalize(c)}},_createHmacHelper:function(a){return function(b,f){return(new s.HMAC.init(a,
|
||||
f)).finalize(b)}}});var s=p.algo={};return p}(Math);
|
||||
(function(){var e=CryptoJS,m=e.lib,p=m.WordArray,j=m.Hasher,l=[],m=e.algo.SHA1=j.extend({_doReset:function(){this._hash=new p.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(f,n){for(var b=this._hash.words,h=b[0],g=b[1],e=b[2],k=b[3],j=b[4],a=0;80>a;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^
|
||||
k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})();
|
@ -29,6 +29,8 @@ paths.view = {
|
||||
scripts: [
|
||||
'node_modules/jquery/dist/jquery.min.js',
|
||||
'../jquery.scrollIntoView.min.js',
|
||||
'../sha1.js',
|
||||
'../lib-typedarrays-min.js',
|
||||
'../dist/_view--javascript.js'
|
||||
],
|
||||
svg: [
|
||||
@ -88,6 +90,8 @@ paths.main = {
|
||||
'node_modules/basiccontext/dist/basicContext.min.js',
|
||||
'node_modules/basicmodal/dist/basicModal.min.js',
|
||||
'../dist/_main--javascript.js',
|
||||
'../sha1.js',
|
||||
'../lib-typedarrays-min.js',
|
||||
'../jquery.scrollIntoView.min.js'
|
||||
],
|
||||
scss: [
|
||||
|
@ -44,6 +44,134 @@ upload.start = {
|
||||
let error = false
|
||||
let warning = false
|
||||
|
||||
const preprocess = function(files, file){
|
||||
// Check if file is supported
|
||||
if (file.supported===false) {
|
||||
|
||||
// Skip file
|
||||
if (file.next!=null) preprocess(files, file.next)
|
||||
else {
|
||||
|
||||
// Look for supported files
|
||||
// If zero files are supported, hide the upload after a delay
|
||||
|
||||
let hasSupportedFiles = false
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
|
||||
if (files[i].supported===true) {
|
||||
hasSupportedFiles = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (hasSupportedFiles===false) setTimeout(finish, 2000)
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
if(!('FileReader' in window)) {
|
||||
process(files, file)
|
||||
return
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest()
|
||||
let formData = new FormData()
|
||||
|
||||
|
||||
var sha1 = CryptoJS.algo.SHA1.create();
|
||||
var read = 0;
|
||||
var unit = 1024 * 1024;
|
||||
var blob;
|
||||
var reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file.slice(read, read + unit));
|
||||
reader.onload = function(e) {
|
||||
var bytes = CryptoJS.lib.WordArray.create(e.target.result);
|
||||
sha1.update(bytes);
|
||||
read += unit;
|
||||
if (read < file.size) {
|
||||
blob = file.slice(read, read + unit);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
} else {
|
||||
var hash = sha1.finalize();
|
||||
console.log(hash.toString(CryptoJS.enc.Hex)); // print the result
|
||||
|
||||
|
||||
xhr.open('POST', api.path)
|
||||
formData.append('function', 'Photo::isDuplicate')
|
||||
formData.append('hash', hash.toString(CryptoJS.enc.Hex))
|
||||
xhr.onload = function() {
|
||||
if (xhr.status===200 && xhr.responseText==='false') {
|
||||
process(files, file)
|
||||
}
|
||||
else{
|
||||
if (file.next!=null)
|
||||
$('.basicModal .rows .row:nth-child(' + (file.num + 1) + ')').scrollIntoView();
|
||||
|
||||
file.ready = true
|
||||
$('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status').html('Skipped (Duplicate)')
|
||||
// Upload next file
|
||||
if (file.next!=null)
|
||||
preprocess(files, file.next)
|
||||
|
||||
|
||||
let wait = false
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
|
||||
if (files[i].ready===false) {
|
||||
wait = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Finish upload when all files are finished
|
||||
if (wait===false) finish()
|
||||
}
|
||||
}
|
||||
xhr.send(formData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const finish = function() {
|
||||
|
||||
window.onbeforeunload = null
|
||||
|
||||
$('#upload_files').val('')
|
||||
|
||||
if (error===false && warning===false) {
|
||||
|
||||
// Success
|
||||
basicModal.close()
|
||||
upload.notify('Upload complete')
|
||||
|
||||
} else if (error===false && warning===true) {
|
||||
|
||||
// Warning
|
||||
$('.basicModal #basicModal__action.hidden').show()
|
||||
upload.notify('Upload complete')
|
||||
|
||||
} else {
|
||||
|
||||
// Error
|
||||
$('.basicModal #basicModal__action.hidden').show()
|
||||
upload.notify('Upload complete', 'Failed to upload one or more photos.')
|
||||
|
||||
}
|
||||
|
||||
albums.refresh()
|
||||
|
||||
if (album.getID()===false) lychee.goto('0')
|
||||
else album.load(albumID)
|
||||
|
||||
}
|
||||
|
||||
const process = function(files, file) {
|
||||
|
||||
let formData = new FormData()
|
||||
@ -52,38 +180,6 @@ upload.start = {
|
||||
let progress = 0
|
||||
let next_file_started = false
|
||||
|
||||
const finish = function() {
|
||||
|
||||
window.onbeforeunload = null
|
||||
|
||||
$('#upload_files').val('')
|
||||
|
||||
if (error===false && warning===false) {
|
||||
|
||||
// Success
|
||||
basicModal.close()
|
||||
upload.notify('Upload complete')
|
||||
|
||||
} else if (error===false && warning===true) {
|
||||
|
||||
// Warning
|
||||
$('.basicModal #basicModal__action.hidden').show()
|
||||
upload.notify('Upload complete')
|
||||
|
||||
} else {
|
||||
|
||||
// Error
|
||||
$('.basicModal #basicModal__action.hidden').show()
|
||||
upload.notify('Upload complete', 'Failed to upload one or more photos.')
|
||||
|
||||
}
|
||||
|
||||
albums.refresh()
|
||||
|
||||
if (album.getID()===false) lychee.goto('0')
|
||||
else album.load(albumID)
|
||||
|
||||
}
|
||||
|
||||
formData.append('function', 'Photo::add')
|
||||
formData.append('albumID', albumID)
|
||||
@ -207,7 +303,7 @@ upload.start = {
|
||||
|
||||
// Upload next file
|
||||
if (file.next!=null) {
|
||||
process(files, file.next)
|
||||
preprocess(files, file.next)
|
||||
next_file_started = true
|
||||
}
|
||||
|
||||
@ -237,7 +333,7 @@ upload.start = {
|
||||
upload.show('Uploading', files, function() {
|
||||
|
||||
// Upload first file
|
||||
process(files, files[0])
|
||||
preprocess(files, files[0])
|
||||
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user