From 6983b74acd9e80bfba18be2a1194d0b20384e64a Mon Sep 17 00:00:00 2001 From: Benoit Schweblin Date: Sun, 7 Apr 2013 16:22:13 +0100 Subject: [PATCH] Added Dropbox synchronization --- cache.manifest | 2 +- css/main.css | 12 +- dropbox-oauth-receiver.html | 11 + index.html | 201 ++++++++++++------- js/async-runner.js | 3 +- js/config.js | 5 +- js/core.js | 2 +- js/dropbox.js | 389 ++++++++++++++++++++++++++++++++++++ js/dropbox.min.js | 5 + js/file-manager.js | 108 ++++++++-- js/gdrive.js | 37 ++-- js/main.js | 1 + js/synchronizer.js | 93 ++++++++- 13 files changed, 754 insertions(+), 115 deletions(-) create mode 100644 dropbox-oauth-receiver.html create mode 100644 js/dropbox.js create mode 100644 js/dropbox.min.js diff --git a/cache.manifest b/cache.manifest index 76c7d888..410f7995 100644 --- a/cache.manifest +++ b/cache.manifest @@ -1 +1 @@ -CACHE MANIFEST # v32 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/bootstrap.js js/config.js js/custo.github.js js/gdrive.js js/jgrowl.js js/jquery.js js/jquery-ui.js js/layout.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js js/require.js js/synchronizer.js img/ajax-loader.gif img/dropbox.png img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * +CACHE MANIFEST # v32 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/bootstrap.js js/config.js js/custo.github.js js/dropbox.js js/gdrive.js js/jgrowl.js js/jquery.js js/jquery-ui.js js/layout.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js js/require.js js/synchronizer.js img/ajax-loader.gif img/dropbox.png img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * diff --git a/css/main.css b/css/main.css index 619574d0..c35b3684 100644 --- a/css/main.css +++ b/css/main.css @@ -9,7 +9,7 @@ body { cursor: progress; } -.btn { +.btn, .dropdown-menu { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -35,6 +35,7 @@ div, span, a, ul, li, textarea, input, button { .dropdown-menu { border: 1px solid #e2e2e2 !important; + text-align: left; } .input-prepend input, @@ -80,11 +81,10 @@ input.error { } .btn-primary { - background-color: #777; + background-color: #777; } input[disabled], select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly], .input-append .add-on { - cursor: not-allowed; background-color: #f5f5f5; } @@ -93,8 +93,10 @@ input[disabled], select[disabled], textarea[disabled], input[readonly], select[r .btn-primary:active, .btn-primary.active, .btn-primary.disabled, -.btn-primary[disabled] { - background-color: #888; +.btn-primary[disabled], +.btn-group.open .btn.btn-primary.dropdown-toggle { + color: #fff; + background-color: #888; } .btn-group { diff --git a/dropbox-oauth-receiver.html b/dropbox-oauth-receiver.html new file mode 100644 index 00000000..a0dac02a --- /dev/null +++ b/dropbox-oauth-receiver.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index d5eed9f0..f4bba471 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,9 @@ + + + @@ -47,18 +50,18 @@
  • @@ -85,43 +88,47 @@
    -
    + - + + + + + +
    diff --git a/js/async-runner.js b/js/async-runner.js index dca28026..1ce24a38 100644 --- a/js/async-runner.js +++ b/js/async-runner.js @@ -84,7 +84,7 @@ define(["core"], function(core) { }; function runSafe(func) { - if(currentTask.finished === true) { + if(currentTask === undefined || currentTask.finished === true) { return; } try { @@ -107,6 +107,7 @@ define(["core"], function(core) { // Add a task in the queue asyncTaskRunner.addTask = function(asyncTask) { asyncTaskQueue.push(asyncTask); + asyncTaskRunner.runTask(); }; return asyncTaskRunner; diff --git a/js/config.js b/js/config.js index c215e23f..663c8229 100644 --- a/js/config.js +++ b/js/config.js @@ -1,14 +1,17 @@ var GOOGLE_SCOPES = [ 'https://www.googleapis.com/auth/drive.install', 'https://www.googleapis.com/auth/drive' ]; var GOOGLE_DRIVE_APP_ID = "241271498917"; +var DROPBOX_APP_KEY = "lq6mwopab8wskas"; +var DROPBOX_APP_SECRET = "851fgnucpezy84t"; var DEFAULT_FILE_TITLE = "Title"; var GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document"; var CHECK_ONLINE_PERIOD = 60000; var AJAX_TIMEOUT = 10000; var ASYNC_TASK_DEFAULT_TIMEOUT = 30000; var AUTH_POPUP_TIMEOUT = 90000; -var SYNC_PERIOD = 60000; +var SYNC_PERIOD = 180000; var SYNC_PROVIDER_GDRIVE = "sync.gdrive."; +var SYNC_PROVIDER_DROPBOX = "sync.dropbox."; // Use by Google's client.js var delayedFunction = undefined; diff --git a/js/core.js b/js/core.js index 1f79c21a..523b4366 100644 --- a/js/core.js +++ b/js/core.js @@ -45,7 +45,7 @@ define(["jquery", "bootstrap", "jgrowl", "layout", "Markdown.Editor"], function( core.showMessage = function(msg, iconClass, options) { options = options || {}; iconClass = iconClass || "icon-info-sign"; - $.jGrowl(" " + msg, options); + $.jGrowl(" " + $("
    ").text(msg).html(), options); }; // Used to show an error message diff --git a/js/dropbox.js b/js/dropbox.js new file mode 100644 index 00000000..bb074bce --- /dev/null +++ b/js/dropbox.js @@ -0,0 +1,389 @@ +define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { + + // Dependencies + var fileManager = undefined; + + var client = undefined; + var authenticated = false; + + var dropbox = {}; + + // Try to connect dropbox by downloading client.js + function connect(callback) { + callback = callback || core.doNothing; + var asyncTask = {}; + asyncTask.run = function() { + if(core.isOffline === true) { + client = undefined; + core.showMessage("Operation not available in offline mode."); + asyncTask.error(); + return; + } + if (client !== undefined) { + asyncTask.success(); + return; + } + $.ajax({ + url : "js/dropbox.min.js", + dataType : "script", timeout : AJAX_TIMEOUT + }).done(function() { + asyncTask.success(); + }).fail(function() { + asyncTask.error(); + }); + }; + asyncTask.onSuccess = function() { + client = new Dropbox.Client({ + key: DROPBOX_APP_KEY, + secret: DROPBOX_APP_SECRET + }); + client.authDriver(new Dropbox.Drivers.Popup({ + receiverUrl: "http://localhost/dropbox-oauth-receiver.html", + rememberUser: true + })); + callback(); + }; + asyncTask.onError = function() { + core.setOffline(); + callback(); + }; + asyncTaskRunner.addTask(asyncTask); + } + + // Try to authenticate with Oauth + function authenticate(callback, immediate) { + callback = callback || core.doNothing; + if (immediate === undefined) { + immediate = true; + } + connect(function() { + if (client === undefined) { + callback(); + return; + } + + var asyncTask = {}; + asyncTask.run = function() { + if (authenticated === true) { + asyncTask.success(); + return; + } + if (immediate === false) { + core.showMessage("Please make sure the Dropbox authorization popup is not blocked by your browser."); + } + client.authenticate({interactive: !immediate}, function(error, client) { + if (client.authState !== Dropbox.Client.DONE) { + // Handle error + asyncTask.error(); + return; + } + + asyncTask.success(); + }); + }; + asyncTask.onSuccess = function() { + callback(); + }; + asyncTask.onError = function() { + // If immediate did not work retry without immediate flag + if (client !== undefined && immediate === true) { + authenticate(callback, false); + return; + } + callback(); + }; + asyncTaskRunner.addTask(asyncTask); + }); + } + + dropbox.upload = function(path, content, callback) { + callback = callback || core.doNothing; + authenticate(function() { + if (client === undefined) { + callback(); + return; + } + + var fileSyncIndex = undefined; + var asyncTask = {}; + asyncTask.run = function() { + client.writeFile(path, content, function(error, stat) { + if (!error) { + fileSyncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(stat.path.toLowerCase()); + localStorage[fileSyncIndex + ".version"] = stat.versionTag; + asyncTask.success(); + return; + } + // Handle error + if(error.status === Dropbox.ApiError.INVALID_PARAM) { + error = 'Could not upload document into path "' + path + '".'; + } + handleError(error, asyncTask, callback); + }); + }; + asyncTask.onSuccess = function() { + callback(fileSyncIndex); + }; + asyncTaskRunner.addTask(asyncTask); + }); + }; + + dropbox.checkUpdates = function(lastChangeId, callback) { + callback = callback || core.doNothing; + authenticate(function() { + if (client === undefined) { + callback(); + return; + } + + var changes = []; + var newChangeId = lastChangeId || 0; + function retrievePageOfChanges(changeId) { + var shouldPullAgain = false; + var asyncTask = {}; + asyncTask.run = function() { + client.pullChanges(changeId, function(error, pullChanges) { + if (pullChanges && pullChanges.cursorTag) { + // Retrieve success + newChangeId = pullChanges.cursor(); + shouldPullAgain = pullChanges.shouldPullAgain; + if(pullChanges.changes !== undefined) { + for(var i=0; i:"\|?\*]+$/)) { + core.showError('"' + path + '" contains invalid characters.'); + return undefined; + } + if(path.indexOf("/") !== 0) { + return "/" + path; + } + return path; + }; + + return dropbox; +}); diff --git a/js/dropbox.min.js b/js/dropbox.min.js new file mode 100644 index 00000000..40fcec61 --- /dev/null +++ b/js/dropbox.min.js @@ -0,0 +1,5 @@ +(function(){var t,e,r,n,o,i,s,a,h,u,l,p,c,d,f,y,v,m,g,w,b,S,_,E,R,C,x,T,A,k,O,U=[].indexOf||function(t){for(var e=0,r=this.length;r>e;e++)if(e in this&&this[e]===t)return e;return-1},D={}.hasOwnProperty,P=function(t,e){function r(){this.constructor=t}for(var n in e)D.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t};if(t=function(){return null},t.Util={},t.EventSource=function(){function t(t){this._cancelable=t&&t.cancelable,this._listeners=[]}return t.prototype.addListener=function(t){if("function"!=typeof t)throw new TypeError("Invalid listener type; expected function");return 0>U.call(this._listeners,t)&&this._listeners.push(t),this},t.prototype.removeListener=function(t){var e,r,n,o,i,s;if(this._listeners.indexOf)r=this._listeners.indexOf(t),-1!==r&&this._listeners.splice(r,1);else for(s=this._listeners,e=o=0,i=s.length;i>o;e=++o)if(n=s[e],n===t){this._listeners.splice(e,1);break}return this},t.prototype.dispatch=function(t){var e,r,n,o,i;for(i=this._listeners,n=0,o=i.length;o>n;n++)if(e=i[n],r=e(t),this._cancelable&&r===!1)return!1;return!0},t}(),t.ApiError=function(){function t(t,e,r){var n,o;if(this.method=e,this.url=r,this.status=t.status,t.responseType)try{n=t.response||t.responseText}catch(i){o=i;try{n=t.responseText}catch(i){o=i,n=null}}else try{n=t.responseText}catch(i){o=i,n=null}if(n)try{this.responseText=""+n,this.response=JSON.parse(n)}catch(i){o=i,this.response=null}else this.responseText="(no response)",this.response=null}return t.prototype.status=void 0,t.prototype.method=void 0,t.prototype.url=void 0,t.prototype.responseText=void 0,t.prototype.response=void 0,t.NETWORK_ERROR=0,t.INVALID_PARAM=400,t.INVALID_TOKEN=401,t.OAUTH_ERROR=403,t.NOT_FOUND=404,t.INVALID_METHOD=405,t.RATE_LIMITED=503,t.OVER_QUOTA=507,t.prototype.toString=function(){return"Dropbox API error "+this.status+" from "+this.method+" "+this.url+" :: "+this.responseText},t.prototype.inspect=function(){return""+this},t}(),t.AuthDriver=function(){function e(){}return e.prototype.url=function(e){return"https://some.url?dboauth_token="+t.Xhr.urlEncode(e)},e.prototype.doAuthorize=function(t,e,r,n){return n("oauth-token")},e.prototype.onAuthStateChange=function(t,e){return e()},e}(),t.Drivers={},"function"==typeof d&&"function"==typeof w?(d=function(t){return window.atob(t)},w=function(t){return window.btoa(t)}):"undefined"==typeof window&&"undefined"==typeof self||"undefined"==typeof navigator||"string"!=typeof navigator.userAgent?(d=function(t){var e,r;return e=new Buffer(t,"base64"),function(){var t,n,o;for(o=[],r=t=0,n=e.length;n>=0?n>t:t>n;r=n>=0?++t:--t)o.push(String.fromCharCode(e[r]));return o}().join("")},w=function(t){var e,r;return e=new Buffer(function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}()),e.toString("base64")}):(y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=function(t,e,r){var n,o;for(o=3-e,t<<=8*o,n=3;n>=o;)r.push(y.charAt(63&t>>6*n)),n-=1;for(n=e;3>n;)r.push("="),n+=1;return null},f=function(t,e,r){var n,o;for(o=4-e,t<<=6*o,n=2;n>=o;)r.push(String.fromCharCode(255&t>>8*n)),n-=1;return null},w=function(t){var e,r,n,o,i,s;for(o=[],e=0,r=0,n=i=0,s=t.length;s>=0?s>i:i>s;n=s>=0?++i:--i)e=e<<8|t.charCodeAt(n),r+=1,3===r&&(b(e,r,o),e=r=0);return r>0&&b(e,r,o),o.join("")},d=function(t){var e,r,n,o,i,s,a;for(i=[],e=0,n=0,o=s=0,a=t.length;(a>=0?a>s:s>a)&&(r=t.charAt(o),"="!==r);o=a>=0?++s:--s)e=e<<6|y.indexOf(r),n+=1,4===n&&(f(e,n,i),e=n=0);return n>0&&f(e,n,i),i.join("")}),t.Util.atob=d,t.Util.btoa=w,t.Client=function(){function e(e){var r=this;this.sandbox=e.sandbox||!1,this.apiServer=e.server||this.defaultApiServer(),this.authServer=e.authServer||this.defaultAuthServer(),this.fileServer=e.fileServer||this.defaultFileServer(),this.downloadServer=e.downloadServer||this.defaultDownloadServer(),this.onXhr=new t.EventSource({cancelable:!0}),this.onError=new t.EventSource,this.onAuthStateChange=new t.EventSource,this.xhrOnErrorHandler=function(t,e){return r.handleXhrError(t,e)},this.oauth=new t.Oauth(e),this.driver=null,this.filter=null,this.uid=null,this.authState=null,this.authError=null,this._credentials=null,this.setCredentials(e),this.setupUrls()}return e.prototype.authDriver=function(t){return this.driver=t,this},e.prototype.onXhr=null,e.prototype.onError=null,e.prototype.onAuthStateChange=null,e.prototype.dropboxUid=function(){return this.uid},e.prototype.credentials=function(){return this._credentials||this.computeCredentials(),this._credentials},e.prototype.authenticate=function(t,e){var r,o,i,s=this;if(e||"function"!=typeof t||(e=t,t=null),r=t&&"interactive"in t?t.interactive:!0,!this.driver&&this.authState!==n.DONE)throw Error("Call authDriver to set an authentication driver");if(this.authState===n.ERROR)throw Error("Client got in an error state. Call reset() to reuse it!");return o=null,i=function(){var t;if(o!==s.authState&&(o=s.authState,s.driver&&s.driver.onAuthStateChange))return s.driver.onAuthStateChange(s,i),void 0;switch(s.authState){case n.RESET:return r?s.requestToken(function(t,e){var r,o;return t?(s.authError=t,s.authState=n.ERROR):(r=e.oauth_token,o=e.oauth_token_secret,s.oauth.setToken(r,o),s.authState=n.REQUEST),s._credentials=null,s.onAuthStateChange.dispatch(s),i()}):(e&&e(null,s),void 0);case n.REQUEST:return r?(t=s.authorizeUrl(s.oauth.token),s.driver.doAuthorize(t,s.oauth.token,s.oauth.tokenSecret,function(){return s.authState=n.AUTHORIZED,s._credentials=null,s.onAuthStateChange.dispatch(s),i()})):(e&&e(null,s),void 0);case n.AUTHORIZED:return s.getAccessToken(function(t,e){return t?(s.authError=t,s.authState=n.ERROR):(s.oauth.setToken(e.oauth_token,e.oauth_token_secret),s.uid=e.uid,s.authState=n.DONE),s._credentials=null,s.onAuthStateChange.dispatch(s),i()});case n.DONE:e&&e(null,s);break;case n.SIGNED_OFF:return s.authState=n.RESET,s.reset(),i();case n.ERROR:e&&e(s.authError,s)}},i(),this},e.prototype.isAuthenticated=function(){return this.authState===n.DONE},e.prototype.signOut=function(e){var r,o=this;return r=new t.Xhr("POST",this.urls.signOut),r.signWithOauth(this.oauth),this.dispatchXhr(r,function(t){return t?(e&&e(t),void 0):(o.authState=n.RESET,o.reset(),o.authState=n.SIGNED_OFF,o.onAuthStateChange.dispatch(o),o.driver&&o.driver.onAuthStateChange?o.driver.onAuthStateChange(o,function(){return e?e(t):void 0}):e?e(t):void 0)})},e.prototype.signOff=function(t){return this.signOut(t)},e.prototype.getUserInfo=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=!1,e&&e.httpCache&&(n=!0),o=new t.Xhr("GET",this.urls.accountInfo),o.signWithOauth(this.oauth,n),this.dispatchXhr(o,function(e,n){return r(e,t.UserInfo.parse(n),n)})},e.prototype.readFile=function(e,r,n){var o,i,s,a,h,u,l;return n||"function"!=typeof r||(n=r,r=null),i={},u="text",a=null,o=!1,r&&(r.versionTag?i.rev=r.versionTag:r.rev&&(i.rev=r.rev),r.arrayBuffer?u="arraybuffer":r.blob?u="blob":r.buffer?u="buffer":r.binary&&(u="b"),r.length?(null!=r.start?(h=r.start,s=r.start+r.length-1):(h="",s=r.length),a="bytes="+h+"-"+s):null!=r.start&&(a="bytes="+r.start+"-"),r.httpCache&&(o=!0)),l=new t.Xhr("GET",""+this.urls.getFile+"/"+this.urlEncodePath(e)),l.setParams(i).signWithOauth(this.oauth,o),l.setResponseType(u),a&&(a&&l.setHeader("Range",a),l.reportResponseHeaders()),this.dispatchXhr(l,function(e,r,o,i){var s;return s=i?t.RangeInfo.parse(i["content-range"]):null,n(e,r,t.Stat.parse(o),s)})},e.prototype.writeFile=function(e,r,n,o){var i;return o||"function"!=typeof n||(o=n,n=null),i=t.Xhr.canSendForms&&"object"==typeof r,i?this.writeFileUsingForm(e,r,n,o):this.writeFileUsingPut(e,r,n,o)},e.prototype.writeFileUsingForm=function(e,r,n,o){var i,s,a,h;return a=e.lastIndexOf("/"),-1===a?(i=e,e=""):(i=e.substring(a),e=e.substring(0,a)),s={file:i},n&&(n.noOverwrite&&(s.overwrite="false"),n.lastVersionTag?s.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(s.parent_rev=n.parentRev||n.parent_rev)),h=new t.Xhr("POST",""+this.urls.postFile+"/"+this.urlEncodePath(e)),h.setParams(s).signWithOauth(this.oauth).setFileField("file",i,r,"application/octet-stream"),delete s.file,this.dispatchXhr(h,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.writeFileUsingPut=function(e,r,n,o){var i,s;return i={},n&&(n.noOverwrite&&(i.overwrite="false"),n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev)),s=new t.Xhr("POST",""+this.urls.putFile+"/"+this.urlEncodePath(e)),s.setBody(r).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.resumableUploadStep=function(e,r,n){var o,i;return r?(o={offset:r.offset},r.tag&&(o.upload_id=r.tag)):o={offset:0},i=new t.Xhr("POST",this.urls.chunkedUpload),i.setBody(e).setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return e&&e.status===t.ApiError.INVALID_PARAM&&e.response&&e.response.upload_id&&e.response.offset?n(null,t.UploadCursor.parse(e.response)):n(e,t.UploadCursor.parse(r))})},e.prototype.resumableUploadFinish=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={upload_id:r.tag},n&&(n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev),n.noOverwrite&&(i.overwrite="false")),s=new t.Xhr("POST",""+this.urls.commitChunkedUpload+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.stat=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.version&&(i.rev=r.version),(r.removed||r.deleted)&&(i.include_deleted="true"),r.readDir&&(i.list="true",r.readDir!==!0&&(i.file_limit=""+r.readDir)),r.cacheHash&&(i.hash=r.cacheHash),r.httpCache&&(o=!0)),i.include_deleted||(i.include_deleted="false"),i.list||(i.list="false"),s=new t.Xhr("GET",""+this.urls.metadata+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i,s;return s=t.Stat.parse(r),o=(null!=r?r.contents:void 0)?function(){var e,n,o,s;for(o=r.contents,s=[],e=0,n=o.length;n>e;e++)i=o[e],s.push(t.Stat.parse(i));return s}():void 0,n(e,s,o)})},e.prototype.readdir=function(t,e,r){var n;return r||"function"!=typeof e||(r=e,e=null),n={readDir:!0},e&&(null!=e.limit&&(n.readDir=e.limit),e.versionTag&&(n.versionTag=e.versionTag),(e.removed||e.deleted)&&(n.removed=e.removed||e.deleted),e.httpCache&&(n.httpCache=e.httpCache)),this.stat(t,n,function(t,e,n){var o,i;return o=n?function(){var t,e,r;for(r=[],t=0,e=n.length;e>t;t++)i=n[t],r.push(i.name);return r}():null,r(t,o,e,n)})},e.prototype.metadata=function(t,e,r){return this.stat(t,e,r)},e.prototype.makeUrl=function(e,r,n){var o,i,s,a,h,u=this;return n||"function"!=typeof r||(n=r,r=null),i=r&&(r["long"]||r.longUrl||r.downloadHack)?{short_url:"false"}:{},e=this.urlEncodePath(e),s=""+this.urls.shares+"/"+e,o=!1,a=!1,r&&(r.downloadHack?(o=!0,a=!0):r.download&&(o=!0,s=""+this.urls.media+"/"+e)),h=new t.Xhr("POST",s).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(h,function(e,r){return a&&(null!=r?r.url:void 0)&&(r.url=r.url.replace(u.authServer,u.downloadServer)),n(e,t.PublicUrl.parse(r,o))})},e.prototype.history=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.limit&&(i.rev_limit=r.limit),r.httpCache&&(o=!0)),s=new t.Xhr("GET",""+this.urls.revisions+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i;return i=r?function(){var e,n,i;for(i=[],e=0,n=r.length;n>e;e++)o=r[e],i.push(t.Stat.parse(o));return i}():void 0,n(e,i)})},e.prototype.revisions=function(t,e,r){return this.history(t,e,r)},e.prototype.thumbnailUrl=function(t,e){var r;return r=this.thumbnailXhr(t,e),r.paramsToUrl().url},e.prototype.readThumbnail=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o="b",r&&(r.blob&&(o="blob"),r.arrayBuffer&&(o="arraybuffer"),r.buffer&&(o="buffer")),i=this.thumbnailXhr(e,r),i.setResponseType(o),this.dispatchXhr(i,function(e,r,o){return n(e,r,t.Stat.parse(o))})},e.prototype.thumbnailXhr=function(e,r){var n,o;return n={},r&&(r.format?n.format=r.format:r.png&&(n.format="png"),r.size&&(n.size=r.size)),o=new t.Xhr("GET",""+this.urls.thumbnails+"/"+this.urlEncodePath(e)),o.setParams(n).signWithOauth(this.oauth)},e.prototype.revertFile=function(e,r,n){var o;return o=new t.Xhr("POST",""+this.urls.restore+"/"+this.urlEncodePath(e)),o.setParams({rev:r}).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.restore=function(t,e,r){return this.revertFile(t,e,r)},e.prototype.findByName=function(e,r,n,o){var i,s,a;return o||"function"!=typeof n||(o=n,n=null),s={query:r},i=!1,n&&(null!=n.limit&&(s.file_limit=n.limit),(n.removed||n.deleted)&&(s.include_deleted=!0),n.httpCache&&(i=!0)),a=new t.Xhr("GET",""+this.urls.search+"/"+this.urlEncodePath(e)),a.setParams(s).signWithOauth(this.oauth,i),this.dispatchXhr(a,function(e,r){var n,i;return i=r?function(){var e,o,i;for(i=[],e=0,o=r.length;o>e;e++)n=r[e],i.push(t.Stat.parse(n));return i}():void 0,o(e,i)})},e.prototype.search=function(t,e,r,n){return this.findByName(t,e,r,n)},e.prototype.makeCopyReference=function(e,r){var n;return n=new t.Xhr("GET",""+this.urls.copyRef+"/"+this.urlEncodePath(e)),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.CopyReference.parse(n))})},e.prototype.copyRef=function(t,e){return this.makeCopyReference(t,e)},e.prototype.pullChanges=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=e?e.cursorTag?{cursor:e.cursorTag}:{cursor:e}:{},o=new t.Xhr("POST",this.urls.delta),o.setParams(n).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,n){return r(e,t.PulledChanges.parse(n))})},e.prototype.delta=function(t,e){return this.pullChanges(t,e)},e.prototype.mkdir=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsCreateFolder),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r?r(e,t.Stat.parse(n)):void 0})},e.prototype.remove=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsDelete),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r?r(e,t.Stat.parse(n)):void 0})},e.prototype.unlink=function(t,e){return this.remove(t,e)},e.prototype["delete"]=function(t,e){return this.remove(t,e)},e.prototype.copy=function(e,r,n){var o,i,s;return n||"function"!=typeof o||(n=o,o=null),i={root:this.fileRoot,to_path:this.normalizePath(r)},e instanceof t.CopyReference?i.from_copy_ref=e.tag:i.from_path=this.normalizePath(e),s=new t.Xhr("POST",this.urls.fileopsCopy),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.move=function(e,r,n){var o,i;return n||"function"!=typeof o||(n=o,o=null),i=new t.Xhr("POST",this.urls.fileopsMove),i.setParams({root:this.fileRoot,from_path:this.normalizePath(e),to_path:this.normalizePath(r)}).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.reset=function(){var t;return this.uid=null,this.oauth.setToken(null,""),t=this.authState,this.authState=n.RESET,t!==this.authState&&this.onAuthStateChange.dispatch(this),this.authError=null,this._credentials=null,this},e.prototype.setCredentials=function(t){var e;return e=this.authState,this.oauth.reset(t),this.uid=t.uid||null,this.authState=t.authState?t.authState:t.token?n.DONE:n.RESET,this.authError=null,this._credentials=null,e!==this.authState&&this.onAuthStateChange.dispatch(this),this},e.prototype.appHash=function(){return this.oauth.appHash()},e.prototype.setupUrls=function(){return this.fileRoot=this.sandbox?"sandbox":"dropbox",this.urls={requestToken:""+this.apiServer+"/1/oauth/request_token",authorize:""+this.authServer+"/1/oauth/authorize",accessToken:""+this.apiServer+"/1/oauth/access_token",signOut:""+this.apiServer+"/1/unlink_access_token",accountInfo:""+this.apiServer+"/1/account/info",getFile:""+this.fileServer+"/1/files/"+this.fileRoot,postFile:""+this.fileServer+"/1/files/"+this.fileRoot,putFile:""+this.fileServer+"/1/files_put/"+this.fileRoot,metadata:""+this.apiServer+"/1/metadata/"+this.fileRoot,delta:""+this.apiServer+"/1/delta",revisions:""+this.apiServer+"/1/revisions/"+this.fileRoot,restore:""+this.apiServer+"/1/restore/"+this.fileRoot,search:""+this.apiServer+"/1/search/"+this.fileRoot,shares:""+this.apiServer+"/1/shares/"+this.fileRoot,media:""+this.apiServer+"/1/media/"+this.fileRoot,copyRef:""+this.apiServer+"/1/copy_ref/"+this.fileRoot,thumbnails:""+this.fileServer+"/1/thumbnails/"+this.fileRoot,chunkedUpload:""+this.fileServer+"/1/chunked_upload",commitChunkedUpload:""+this.fileServer+"/1/commit_chunked_upload/"+this.fileRoot,fileopsCopy:""+this.apiServer+"/1/fileops/copy",fileopsCreateFolder:""+this.apiServer+"/1/fileops/create_folder",fileopsDelete:""+this.apiServer+"/1/fileops/delete",fileopsMove:""+this.apiServer+"/1/fileops/move"}},e.prototype.authState=null,e.ERROR=0,e.RESET=1,e.REQUEST=2,e.AUTHORIZED=3,e.DONE=4,e.SIGNED_OFF=5,e.prototype.urlEncodePath=function(e){return t.Xhr.urlEncodeValue(this.normalizePath(e)).replace(/%2F/gi,"/")},e.prototype.normalizePath=function(t){var e;if("/"===t.substring(0,1)){for(e=1;"/"===t.substring(e,e+1);)e+=1;return t.substring(e)}return t},e.prototype.requestToken=function(e){var r;return r=new t.Xhr("POST",this.urls.requestToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.authorizeUrl=function(e){var r,n;return r=this.driver.url(e),n=null===r?{oauth_token:e}:{oauth_token:e,oauth_callback:r},""+this.urls.authorize+"?"+t.Xhr.urlEncode(n)},e.prototype.getAccessToken=function(e){var r;return r=new t.Xhr("POST",this.urls.accessToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.dispatchXhr=function(t,e){var r;return t.setCallback(e),t.onError=this.xhrOnErrorHandler,t.prepare(),r=t.xhr,this.onXhr.dispatch(t)&&t.send(),r},e.prototype.handleXhrError=function(e,r){var o=this;return e.status===t.ApiError.INVALID_TOKEN&&this.authState===n.DONE&&(this.authError=e,this.authState=n.ERROR,this.onAuthStateChange.dispatch(this),this.driver&&this.driver.onAuthStateChange)?(this.driver.onAuthStateChange(this,function(){return o.onError.dispatch(e),r(e)}),null):(this.onError.dispatch(e),r(e),null)},e.prototype.defaultApiServer=function(){return"https://api.dropbox.com"},e.prototype.defaultAuthServer=function(){return this.apiServer.replace("api.","www.")},e.prototype.defaultFileServer=function(){return this.apiServer.replace("api.","api-content.")},e.prototype.defaultDownloadServer=function(){return this.apiServer.replace("api.","dl.")},e.prototype.computeCredentials=function(){var t;return t={key:this.oauth.key,sandbox:this.sandbox},this.oauth.secret&&(t.secret=this.oauth.secret),this.oauth.token&&(t.token=this.oauth.token,t.tokenSecret=this.oauth.tokenSecret),this.uid&&(t.uid=this.uid),this.authState!==n.ERROR&&this.authState!==n.RESET&&this.authState!==n.DONE&&this.authState!==n.SIGNED_OFF&&(t.authState=this.authState),this.apiServer!==this.defaultApiServer()&&(t.server=this.apiServer),this.authServer!==this.defaultAuthServer()&&(t.authServer=this.authServer),this.fileServer!==this.defaultFileServer()&&(t.fileServer=this.fileServer),this.downloadServer!==this.defaultDownloadServer()&&(t.downloadServer=this.downloadServer),this._credentials=t},e}(),n=t.Client,t.Drivers.BrowserBase=function(){function e(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.useQuery=(null!=t?t.useQuery:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default",this.storageKey=null,this.dbTokenRe=RegExp("(#|\\?|&)dboauth_token=([^&#]+)(&|#|$)"),this.rejectedRe=RegExp("(#|\\?|&)not_approved=true(&|#|$)"),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return e.prototype.onAuthStateChange=function(t,e){var r=this;switch(this.setStorageKey(t),t.authState){case n.RESET:return this.loadCredentials(function(n){return n?n.authState?(t.setCredentials(n),e()):r.rememberUser?(t.setCredentials(n),t.getUserInfo(function(n){return n?(t.reset(),r.forgetCredentials(e)):e()})):r.forgetCredentials(e):e()});case n.REQUEST:return this.storeCredentials(t.credentials(),e);case n.DONE:return this.rememberUser?this.storeCredentials(t.credentials(),e):this.forgetCredentials(e);case n.SIGNED_OFF:return this.forgetCredentials(e);case n.ERROR:return this.forgetCredentials(e);default:return e(),this}},e.prototype.setStorageKey=function(t){return this.storageKey="dropbox-auth:"+this.scope+":"+t.appHash(),this},e.prototype.storeCredentials=function(t,e){return localStorage.setItem(this.storageKey,JSON.stringify(t)),e(),this},e.prototype.loadCredentials=function(t){var e,r;if(r=localStorage.getItem(this.storageKey),!r)return t(null),this;try{t(JSON.parse(r))}catch(n){e=n,t(null)}return this},e.prototype.forgetCredentials=function(t){return localStorage.removeItem(this.storageKey),t(),this},e.prototype.computeUrl=function(t){var e,r,n,o;return o="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&dboauth_token=",r=t,-1===r.indexOf("#")?e=null:(n=r.split("#",2),r=n[0],e=n[1]),this.useQuery?(r+=-1===r.indexOf("?")?"?"+o:"&"+o,e?[r,"#"+e]:[r,""]):[r+"#?"+o,""]},e.prototype.locationToken=function(e){var r,n,o;return r=e||t.Drivers.BrowserBase.currentLocation(),o="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&",-1===("function"==typeof r.indexOf?r.indexOf(o):void 0)?null:this.rejectedRe.test(r)?(n=this.dbTokenRe.exec(r),n?decodeURIComponent(n[2]):null):(n=this.tokenRe.exec(r),n?decodeURIComponent(n[2]):null)},e.currentLocation=function(){return window.location.href},e}(),t.Drivers.Redirect=function(e){function r(e){var n;r.__super__.constructor.call(this,e),n=this.computeUrl(t.Drivers.BrowserBase.currentLocation()),this.receiverUrl1=n[0],this.receiverUrl2=n[1]}return P(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?t.token===i.locationToken()&&t.authState===n.REQUEST?(t.authState=n.AUTHORIZED,i.storeCredentials(t,o)):i.forgetCredentials(o):o()}):o()},r.prototype.url=function(t){return this.receiverUrl1+encodeURIComponent(t)+this.receiverUrl2},r.prototype.doAuthorize=function(t){return window.location.assign(t)},r}(t.Drivers.BrowserBase),t.Drivers.Popup=function(e){function r(t){var e;r.__super__.constructor.call(this,t),e=this.computeUrl(this.baseUrl(t)),this.receiverUrl1=e[0],this.receiverUrl2=e[1]}return P(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},r.prototype.doAuthorize=function(t,e,r,n){return this.listenForMessage(e,n),this.openWindow(t)},r.prototype.url=function(t){return this.receiverUrl1+encodeURIComponent(t)+this.receiverUrl2},r.prototype.baseUrl=function(e){var r;if(e){if(e.receiverUrl)return e.receiverUrl;if(e.receiverFile)return r=t.Drivers.BrowserBase.currentLocation().split("/"),r[r.length-1]=e.receiverFile,r.join("/")}return t.Drivers.BrowserBase.currentLocation()},r.prototype.openWindow=function(t){return window.open(t,"_dropboxOauthSigninWindow",this.popupWindowSpec(980,700))},r.prototype.popupWindowSpec=function(t,e){var r,n,o,i,s,a,h,u,l,p;return s=null!=(h=window.screenX)?h:window.screenLeft,a=null!=(u=window.screenY)?u:window.screenTop,i=null!=(l=window.outerWidth)?l:document.documentElement.clientWidth,r=null!=(p=window.outerHeight)?p:document.documentElement.clientHeight,n=Math.round(s+(i-t)/2),o=Math.round(a+(r-e)/2.5),s>n&&(n=s),a>o&&(o=a),"width="+t+",height="+e+","+("left="+n+",top="+o)+"dialog=yes,dependent=yes,scrollbars=yes,location=yes"},r.prototype.listenForMessage=function(e,r){var n,o=this;return n=function(i){var s;return s=i.data?i.data:i,o.locationToken(s)===e?(e=null,window.removeEventListener("message",n),t.Drivers.Popup.onMessage.removeListener(n),r()):void 0},window.addEventListener("message",n,!1),t.Drivers.Popup.onMessage.addListener(n)},r.oauthReceiver=function(){return window.addEventListener("load",function(){var t,e,r;if(r=window.opener,window.parent!==window.top&&(r||(r=window.parent)),r){try{r.postMessage(window.location.href,"*")}catch(n){e=n}try{r.Dropbox.Drivers.Popup.onMessage.dispatch(window.location.href)}catch(n){t=n}}return window.close()})},r.onMessage=new t.EventSource,r}(t.Drivers.BrowserBase),e=null,r=null,"undefined"!=typeof chrome&&null!==chrome&&(chrome.runtime&&(chrome.runtime.onMessage&&(e=chrome.runtime.onMessage),chrome.runtime.sendMessage&&(r=function(t){return chrome.runtime.sendMessage(t)})),chrome.extension&&(chrome.extension.onMessage&&(e||(e=chrome.extension.onMessage)),chrome.extension.sendMessage&&(r||(r=function(t){return chrome.extension.sendMessage(t)}))),e||function(){var e,r;return r=function(e){return e.Dropbox?(t.Drivers.Chrome.prototype.onMessage=e.Dropbox.Drivers.Chrome.onMessage,t.Drivers.Chrome.prototype.sendMessage=e.Dropbox.Drivers.Chrome.sendMessage):(e.Dropbox=t,t.Drivers.Chrome.prototype.onMessage=new t.EventSource,t.Drivers.Chrome.prototype.sendMessage=function(e){return t.Drivers.Chrome.prototype.onMessage.dispatch(e)})},chrome.extension&&chrome.extension.getBackgroundPage&&(e=chrome.extension.getBackgroundPage())?r(e):chrome.runtime&&chrome.runtime.getBackgroundPage?chrome.runtime.getBackgroundPage(function(t){return r(t)}):void 0}()),t.Drivers.Chrome=function(n){function o(t){var e,r;o.__super__.constructor.call(this,t),e=t&&t.receiverPath||"chrome_oauth_receiver.html",this.rememberUser=!0,this.useQuery=!0,r=this.computeUrl(this.expandUrl(e)),this.receiverUrl=r[0],this.receiverUrl2=r[1],this.storageKey="dropbox_js_"+this.scope+"_credentials"}return P(o,n),o.prototype.onMessage=e,o.prototype.sendMessage=r,o.prototype.expandUrl=function(t){return chrome.runtime&&chrome.runtime.getURL?chrome.runtime.getURL(t):chrome.extension&&chrome.extension.getURL?chrome.extension.getURL(t):t},o.prototype.onAuthStateChange=function(e,r){var n=this;switch(e.authState){case t.Client.RESET:return this.loadCredentials(function(t){if(t){if(t.authState)return n.forgetCredentials(r);e.setCredentials(t)}return r()});case t.Client.DONE:return this.storeCredentials(e.credentials(),r);case t.Client.SIGNED_OFF:return this.forgetCredentials(r);case t.Client.ERROR:return this.forgetCredentials(r);default:return r()}},o.prototype.doAuthorize=function(t,e,r,n){var o,i,s,a,h=this;return(null!=(i=chrome.identity)?i.launchWebAuthFlow:void 0)?chrome.identity.launchWebAuthFlow({url:t,interactive:!0},function(t){return h.locationToken(t)===e?n():void 0}):(null!=(s=chrome.experimental)?null!=(a=s.identity)?a.launchWebAuthFlow:void 0:void 0)?chrome.experimental.identity.launchWebAuthFlow({url:t,interactive:!0},function(t){return h.locationToken(t)===e?n():void 0}):(o={handle:null},this.listenForMessage(e,o,n),this.openWindow(t,function(t){return o.handle=t}))},o.prototype.openWindow=function(t,e){return chrome.tabs&&chrome.tabs.create?(chrome.tabs.create({url:t,active:!0,pinned:!1},function(t){return e(t)}),this):this},o.prototype.closeWindow=function(t){return chrome.tabs&&chrome.tabs.remove&&t.id?(chrome.tabs.remove(t.id),this):chrome.app&&chrome.app.window&&t.close?(t.close(),this):this},o.prototype.url=function(t){return this.receiverUrl+encodeURIComponent(t)},o.prototype.listenForMessage=function(t,e,r){var n,o=this;return n=function(i,s){return s&&s.tab&&s.tab.url.substring(0,o.receiverUrl.length)!==o.receiverUrl||!i.dropbox_oauth_receiver_href?void 0:o.locationToken(i.dropbox_oauth_receiver_href)===t?(e.handle&&o.closeWindow(e.handle),o.onMessage.removeListener(n),r()):void 0},this.onMessage.addListener(n)},o.prototype.storeCredentials=function(t,e){var r;return r={},r[this.storageKey]=t,chrome.storage.local.set(r,e),this},o.prototype.loadCredentials=function(t){var e=this;return chrome.storage.local.get(this.storageKey,function(r){return t(r[e.storageKey]||null)}),this},o.prototype.forgetCredentials=function(t){return chrome.storage.local.remove(this.storageKey,t),this},o.oauthReceiver=function(){return window.addEventListener("load",function(){var e;return e=new t.Drivers.Chrome,e.sendMessage({dropbox_oauth_receiver_href:window.location.href}),window.close?window.close():void 0})},o}(t.Drivers.BrowserBase),t.Drivers.Cordova=function(t){function e(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default"}return P(e,t),e.prototype.doAuthorize=function(t,e,r,n){var o,i,s,a;return i=window.open(t,"_blank","location=yes"),a=!1,o=/^[^/]*\/\/[^/]*\//.exec(t)[0],s=function(e){return e.url===t&&a===!1?(a=!0,void 0):e.url&&e.url.substring(0,o.length)!==o?(a=!1,void 0):"exit"===e.type||a?(i.removeEventListener("loadstop",s),i.removeEventListener("exit",s),"exit"!==e.type&&i.close(),n()):void 0},i.addEventListener("loadstop",s),i.addEventListener("exit",s)},e.prototype.url=function(){return null},e.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return e.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},e}(t.Drivers.BrowserBase),t.Drivers.NodeServer=function(){function t(t){this.port=(null!=t?t.port:void 0)||8912,this.faviconFile=(null!=t?t.favicon:void 0)||null,this.fs=require("fs"),this.http=require("http"),this.open=require("open"),this.callbacks={},this.nodeUrl=require("url"),this.createApp()}return t.prototype.url=function(t){return"http://localhost:"+this.port+"/oauth_callback?dboauth_token="+encodeURIComponent(t)},t.prototype.doAuthorize=function(t,e,r,n){return this.callbacks[e]=n,this.openBrowser(t)},t.prototype.openBrowser=function(t){if(!t.match(/^https?:\/\//))throw Error("Not a http/https URL: "+t);return"BROWSER"in process.env?this.open(t,process.env.BROWSER):this.open(t)},t.prototype.createApp=function(){var t=this;return this.app=this.http.createServer(function(e,r){return t.doRequest(e,r)}),this.app.listen(this.port)},t.prototype.closeServer=function(){return this.app.close()},t.prototype.doRequest=function(t,e){var r,n,o,i,s=this;return i=this.nodeUrl.parse(t.url,!0),"/oauth_callback"===i.pathname&&("true"===i.query.not_approved?(n=!0,o=i.query.dboauth_token):(n=!1,o=i.query.oauth_token),this.callbacks[o]&&(this.callbacks[o](n),delete this.callbacks[o])),r="",t.on("data",function(t){return r+=t}),t.on("end",function(){return s.faviconFile&&"/favicon.ico"===i.pathname?s.sendFavicon(e):s.closeBrowser(e)})},t.prototype.closeBrowser=function(t){var e;return e='\n\n

    Please close this window.

    ',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end()},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end()})},t}(),v=function(t,e){return c(E(A(t),A(e),t.length,e.length))},m=function(t){return c(T(A(t),t.length))},"undefined"!=typeof require)try{S=require("crypto"),S.createHmac&&S.createHash&&(v=function(t,e){var r;return r=S.createHmac("sha1",e),r.update(t),r.digest("base64")},m=function(t){var e;return e=S.createHash("sha1"),e.update(t),e.digest("base64")})}catch(X){C=X}if(t.Util.hmac=v,t.Util.sha1=m,E=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=T(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]); +return r}(),o=T(s.concat(t),64+r),T(a.concat(o),84)},T=function(t,e){var r,n,o,i,s,a,h,u,l,c,d,f,y,v,m,g,w,b;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,h=271733878,l=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,a=s,u=h,c=l,y=b=0;80>b;y=++b)g[y]=16>y?t[f+y]:x(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&h,v=1518500249):40>y?(d=o^s^h,v=1859775393):60>y?(d=o&s|o&h|s&h,v=-1894007588):(d=o^s^h,v=-899497514),w=p(p(x(r,5),d),p(p(l,g[y]),v)),l=h,h=s,s=x(o,30),o=r,r=w;r=p(r,n),o=p(o,i),s=p(s,a),h=p(h,u),l=p(l,c),f+=16}return[r,o,s,h,l]},x=function(t,e){return t<>>32-e},p=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},c=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=O[63&i>>18],o+=O[63&i>>12],e+=1,o+=e>=n?"=":O[63&i>>6],e+=1,o+=e>=n?"=":O[63&i],e+=1;return o},O="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",A=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=d(_(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(e){return e.oauth_consumer_key=this.k,e.oauth_nonce=t.Oauth.nonce(),e.oauth_signature_method="HMAC-SHA1",this.token&&(e.oauth_token=this.token),e.oauth_timestamp=Math.floor(Date.now()/1e3),e.oauth_version="1.0",e},e.nonce=function(){return Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),v(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=m(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),_=function(t,e){var r,n,o,i,s,a,h,u,l,p,c,f;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1)));return o}()):(p=t.split("|",2),t=p[0],e=p[1],t=d(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=d(e)),i=function(){for(f=[],u=0;256>u;u++)f.push(u);return f}.apply(this),a=0,s=l=0;256>l;s=++l)a=(a+i[r]+t[s%t.length])%256,c=[i[a],i[s]],i[s]=c[0],i[a]=c[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[w(t.join("")),w(o.join(""))].join("|")},t.Util.encodeKey=_,t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e.prototype.cursor=function(){return this.cursorTag},e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.RangeInfo=function(){function e(t){var e;(e=/^bytes (\d*)-(\d*)\/(.*)$/.exec(t))?(this.start=parseInt(e[1]),this.end=parseInt(e[2]),this.size="*"===e[3]?null:parseInt(e[3])):(this.start=0,this.end=0,this.size=null)}return e.parse=function(e){return"string"==typeof e?new t.RangeInfo(e):e},e.prototype.start=null,e.prototype.size=null,e.prototype.end=null,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:"direct"in t?t.direct:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect,this._json=null}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=null,e.prototype.expiresAt=null,e.prototype.isDirect=null,e.prototype.isPreview=null,e.prototype.json=function(){return this._json||(this._json={url:this.url,expires:""+this.expiresAt,direct:this.isDirect})},e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires)),this._json=t):(this.tag=t,this.expiresAt=new Date(1e3*Math.ceil(Date.now()/1e3)),this._json=null)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={copy_ref:this.tag,expires:""+this.expiresAt})},e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this._json=t,this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e.prototype.json=function(){return this._json},e}(),t.UploadCursor=function(){function e(t){this.replace(t)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.UploadCursor(e)},e.prototype.tag=null,e.prototype.offset=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={upload_id:this.tag,offset:this.offset,expires:""+this.expiresAt})},e.prototype.replace=function(t){return"object"==typeof t?(this.tag=t.upload_id||null,this.offset=t.offset||0,this.expiresAt=new Date(Date.parse(t.expires)||Date.now()),this._json=t):(this.tag=t||null,this.offset=0,this.expiresAt=new Date(1e3*Math.floor(Date.now()/1e3)),this._json=null),this},e}(),t.UserInfo=function(){function e(t){var e;this._json=t,this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e.prototype.json=function(){return this._json},e}(),"undefined"==typeof XMLHttpRequest||"undefined"==typeof window&&"undefined"==typeof self||"undefined"==typeof navigator||"string"!=typeof navigator.userAgent?(h=require("xhr2"),a=!1,i=!1,s=!1):("undefined"==typeof XDomainRequest||"withCredentials"in new XMLHttpRequest?(h=XMLHttpRequest,a=!1,i="undefined"!=typeof FormData&&-1===navigator.userAgent.indexOf("Firefox")):(h=XDomainRequest,a=!0,i=!1),s=!0),"undefined"==typeof Uint8Array)o=null,l=!1,u=!1;else if(Object.getPrototypeOf?o=Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor:Object.__proto__&&(o=new Uint8Array(0).__proto__.__proto__.constructor),"undefined"==typeof Blob)l=!1,u=!0;else{try{2===new Blob([new Uint8Array(2)]).size?(l=!0,u=!0):(u=!1,l=2===new Blob([new ArrayBuffer(2)]).size)}catch(X){g=X,u=!1,l=!1,"undefined"!=typeof WebKitBlobBuilder&&-1!==navigator.userAgent.indexOf("Android")&&(i=!1)}o===Object&&(u=!1)}if(t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.wantHeaders=!1,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.completed=!1,this.responseType=null,this.callback=null,this.xhr=null,this.onError=null}return e.Request=h,e.ieXdr=a,e.canSendForms=i,e.doesPreflight=s,e.ArrayBufferView=o,e.sendArrayBufferView=u,e.wrapBlob=l,e.prototype.xhr=null,e.prototype.onError=null,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e,r){return t.Xhr.ieXdr?this.addOauthParams(e):this.preflight||!t.Xhr.doesPreflight?this.addOauthHeader(e):this.isGet&&r?this.addOauthHeader(e):this.addOauthParams(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return"string"==typeof t||"undefined"!=typeof FormData&&t instanceof FormData||(this.headers["Content-Type"]="application/octet-stream",this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.reportResponseHeaders=function(){return this.wantHeaders=!0},e.prototype.setFileField=function(e,r,n,o){var i,s,a,h;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("setFileField cannot be called on GET requests");if("object"==typeof n){"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer?t.Xhr.sendArrayBufferView&&(n=new Uint8Array(n)):!t.Xhr.sendArrayBufferView&&0===n.byteOffset&&n.buffer instanceof ArrayBuffer&&(n=n.buffer)),o||(o="application/octet-stream");try{n=new Blob([n],{type:o})}catch(u){g=u,window.WebKitBlobBuilder&&(a=new WebKitBlobBuilder,a.append(n),(i=a.getBlob(o))&&(n=i))}"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),h=n instanceof Blob}else h=!1;return h?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),s=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+s,this.body=["--",s,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",s,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieXdr,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onXdrLoad()},this.xhr.onerror=function(){return i.onXdrError()},this.xhr.ontimeout=function(){return i.onXdrError()},this.xhr.onprogress=function(){}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)D.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r,n;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.sendArrayBufferView?r instanceof ArrayBuffer&&(r=new Uint8Array(r)):0===r.byteOffset&&r.buffer instanceof ArrayBuffer&&(r=r.buffer);try{this.xhr.send(r)}catch(o){if(n=o,t.Xhr.sendArrayBufferView||!t.Xhr.wrapBlob)throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u,l,p,c;if(4!==this.xhr.readyState)return!0;if(this.completed)return!0;if(this.completed=!0,200>this.xhr.status||this.xhr.status>=300)return r=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(r,this.callback):this.callback(r),!0;if(this.wantHeaders?(e=this.xhr.getAllResponseHeaders(),i=e?t.Xhr.parseResponseHeaders(e):this.guessResponseHeaders(),u=i["x-dropbox-metadata"]):(i=void 0,u=this.xhr.getResponseHeader("x-dropbox-metadata")),null!=u?u.length:void 0)try{h=JSON.parse(u)}catch(d){a=d,h=void 0}else h=void 0;if(this.responseType){if("b"===this.responseType){for(o=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,n=[],s=p=0,c=o.length;c>=0?c>p:p>c;s=c>=0?++p:--p)n.push(String.fromCharCode(255&o.charCodeAt(s)));l=n.join(""),this.callback(null,l,h,i)}else this.callback(null,this.xhr.response,h,i);return!0}switch(l=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(l),h,i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(l),h,i);break;default:this.callback(null,l,h,i)}return!0},e.parseResponseHeaders=function(t){var e,r,n,o,i,s,a,h;for(n={},r=t.split("\n"),a=0,h=r.length;h>a;a++)o=r[a],e=o.indexOf(":"),i=o.substring(0,e).trim().toLowerCase(),s=o.substring(e+1).trim(),n[i]=s;return n},e.prototype.guessResponseHeaders=function(){var t,e,r,n,o,i;for(t={},i=["cache-control","content-language","content-range","content-type","expires","last-modified","pragma","x-dropbox-metadata"],n=0,o=i.length;o>n;n++)e=i[n],r=this.xhr.getResponseHeader(e),r&&(t[e]=r);return t},e.prototype.onXdrLoad=function(){var e,r,n;if(this.completed)return!0;if(this.completed=!0,n=this.xhr.responseText,e=this.wantHeaders?{"content-type":this.xhr.contentType}:void 0,r=void 0,this.responseType)return this.callback(null,n,r,e),!0;switch(this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(n),r,e);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(n),r,e);break;default:this.callback(null,n,r,e)}return!0},e.prototype.onXdrError=function(){var e;return this.completed?!0:(this.completed=!0,e=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(e,this.callback):this.callback(e),!0)},e}(),"undefined"!=typeof module&&"exports"in module)module.exports=t;else if("undefined"!=typeof window&&null!==window)if(window.Dropbox)for(R in t)D.call(t,R)&&(k=t[R],window.Dropbox[R]=k);else window.Dropbox=t;else{if("undefined"==typeof self||null===self)throw Error("This library only supports node.js and modern browsers.");self.Dropbox=t}}).call(this); +/* +//@ sourceMappingURL=dropbox.min.map +*/ \ No newline at end of file diff --git a/js/file-manager.js b/js/file-manager.js index 3e4b99c0..05c7faad 100644 --- a/js/file-manager.js +++ b/js/file-manager.js @@ -1,9 +1,11 @@ -define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, core, gdrive, synchronizer, asyncTaskRunner) { +define(["jquery", "core", "gdrive", "dropbox", "synchronizer", "async-runner"], + function($, core, gdrive, dropbox, synchronizer, asyncTaskRunner) { var fileManager = {}; fileManager.init = function() { gdrive.init(fileManager); + dropbox.init(fileManager); var changeSyncButtonState = function() { if(synchronizer.isRunning() || synchronizer.isQueueEmpty() || core.isOffline) { @@ -75,7 +77,18 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, + core.encodeBase64(content); window.open(uriContent, 'file'); }); - $(".action-upload-gdrive").click(uploadGdrive); + $(".action-upload-gdrive-root").click(function() { + uploadGdrive(); + }); + $(".action-upload-gdrive-select").click(function() { + // This action is not available because picker does not support + // folder selection yet + gdrive.picker(function(ids) { + if(ids !== undefined && ids.length !== 0) { + uploadGdrive(ids[0]); + } + }, true); + }); $(".action-download-gdrive").click(function() { gdrive.picker(importGdrive); }); @@ -84,10 +97,15 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, manualGdrive(fileId); }); $(".action-download-dropbox").click(function() { - core.showMessage("Sorry, Dropbox synchronization is not yet available."); + dropbox.picker(importDropbox); }); - $(".action-upload-dropbox").click(function() { - core.showMessage("Sorry, Dropbox synchronization is not yet available."); + $(".action-upload-dropbox").click(function(event) { + var path = core.getInputValue($("#upload-dropbox-path"), event); + manualDropbox(path); + }); + $(".action-manual-dropbox").click(function(event) { + var path = core.getInputValue($("#manual-dropbox-path"), event); + manualDropbox(path); }); }; @@ -209,12 +227,17 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, } var useGoogleDrive = false; + var useDropbox = false; function composeTitle(fileIndex) { - var result = localStorage[fileIndex + ".title"]; + var result = " " + localStorage[fileIndex + ".title"]; var sync = localStorage[fileIndex + ".sync"]; + if (sync.indexOf(";" + SYNC_PROVIDER_DROPBOX) !== -1) { + useDropbox = true; + result = '' + result; + } if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) { useGoogleDrive = true; - result = ' ' + result; + result = '' + result; } return result; } @@ -245,6 +268,7 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, $("#file-selector").append(li); } synchronizer.useGoogleDrive = useGoogleDrive; + synchronizer.useDropbox = useDropbox; }; // Remove a synchronized location @@ -258,8 +282,10 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, refreshManageSync(); } } - // Remove etag + // Remove Google Drive etag localStorage.removeItem(fileSyncIndex + ".etag"); + // Remove Dropbox version + localStorage.removeItem(fileSyncIndex + ".version"); }; // Look for local file associated to a synchronized location @@ -277,11 +303,11 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, return fileIndex; }; - function uploadGdrive() { + function uploadGdrive(folderId) { var fileIndex = localStorage["file.current"]; var content = localStorage[fileIndex + ".content"]; var title = localStorage[fileIndex + ".title"]; - gdrive.createFile(title, content, function(fileSyncIndex) { + gdrive.upload(undefined, folderId, title, content, function(fileSyncIndex) { if (fileSyncIndex === undefined) { return; } @@ -342,6 +368,56 @@ define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, }); } + function manualDropbox(path) { + if(!path) { + return; + } + path = dropbox.checkPath(path); + if(path === undefined) { + return; + } + // Check that file is not synchronized with an other one + var fileSyncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(path.toLowerCase()); + var fileIndex = fileManager.getFileIndexFromSync(fileSyncIndex); + if(fileIndex !== undefined) { + var title = localStorage[fileIndex + ".title"]; + core.showError('Path "' + path + '" is already synchronized with "' + title + '"'); + return; + } + var fileIndex = localStorage["file.current"]; + var content = localStorage[fileIndex + ".content"]; + var title = localStorage[fileIndex + ".title"]; + dropbox.upload(path, content, function(fileSyncIndex) { + if (fileSyncIndex === undefined) { + return; + } + localStorage[fileIndex + ".sync"] += fileSyncIndex + ";"; + refreshManageSync(); + fileManager.updateFileTitles(); + core.showMessage('"' + title + + '" will now be synchronized on Dropbox.'); + }); + } + + function importDropbox(paths) { + if(paths === undefined) { + return; + } + var importPaths = []; + for(var i=0; i')); line.append($("").prop("type", "text").prop( "disabled", true).addClass("span5").val( - "ID=" - + fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length))); + fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length))); + } + if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) { + line.append($("").addClass("add-on").html( + '')); + line.append($("").prop("type", "text").prop( + "disabled", true).addClass("span5").val( + decodeURIComponent(fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length)))); } line.append($("").addClass("btn").html( '').prop("title", - "Remove this synchronized location").click(function() { + "Remove this location").click(function() { fileManager.removeSync(fileSyncIndex); fileManager.updateFileTitles(); })); diff --git a/js/gdrive.js b/js/gdrive.js index f044dcd4..5c43d602 100644 --- a/js/gdrive.js +++ b/js/gdrive.js @@ -34,12 +34,10 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { }); }; asyncTask.onSuccess = function() { - delayedFunction = undefined; connected = true; callback(); }; asyncTask.onError = function() { - delayedFunction = undefined; core.setOffline(); callback(); }; @@ -99,7 +97,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { }); } - function upload(fileId, parentId, title, content, callback) { + gdrive.upload = function(fileId, parentId, title, content, callback) { callback = callback || core.doNothing; authenticate(function() { if (connected === false) { @@ -172,7 +170,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { }; asyncTaskRunner.addTask(asyncTask); }); - } + }; gdrive.checkUpdates = function(lastChangeId, callback) { callback = callback || core.doNothing; @@ -245,10 +243,13 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { var id = ids.pop(); var asyncTask = {}; asyncTask.run = function() { - var accessToken = gapi.auth.getToken().access_token; + var token = gapi.auth.getToken(); + var headers = { + Authorization : token ? "Bearer " + token.access_token: null + }; $.ajax({ url : "https://www.googleapis.com/drive/v2/files/" + id, - headers : { "Authorization" : "Bearer " + accessToken }, + headers : headers, dataType : "json", timeout : AJAX_TIMEOUT }).done(function(data, textStatus, jqXHR) { @@ -261,7 +262,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { }; // Handle error if(error.code === 404) { - error = "File is not available."; + error = 'File ID "' + id + '" does not exist on Google Drive.'; } handleError(error, asyncTask, callback); }); @@ -292,7 +293,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { else if(object.kind == "drive#change") { file = object.file; } - if(file === undefined) { + if(!file) { this.downloadContent(objects, callback, result); return; } @@ -305,10 +306,13 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { var asyncTask = {}; asyncTask.run = function() { - var accessToken = gapi.auth.getToken().access_token; + var token = gapi.auth.getToken(); + var headers = { + Authorization : token ? "Bearer " + token.access_token: null + }; $.ajax({ url : file.downloadUrl, - headers : { "Authorization" : "Bearer " + accessToken }, + headers : headers, dataType : "text", timeout : AJAX_TIMEOUT }).done(function(data, textStatus, jqXHR) { @@ -365,7 +369,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { var pickerLoaded = false; function loadPicker(callback) { - authenticate(function() { + connect(function() { if (connected === false) { pickerLoaded = false; callback(); @@ -407,7 +411,6 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { callback(); return; } - var view = new google.picker.View(google.picker.ViewId.DOCS); view.setMimeTypes("text/x-markdown,text/plain"); var pickerBuilder = new google.picker.PickerBuilder(); @@ -443,14 +446,6 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { }); }; - gdrive.createFile = function(title, content, callback) { - upload(undefined, undefined, title, content, callback); - }; - - gdrive.updateFile = function(id, title, content, callback) { - upload(id, undefined, title, content, callback); - }; - gdrive.importFiles = function(ids) { gdrive.downloadMetadata(ids, function(result) { if(result === undefined) { @@ -481,7 +476,7 @@ define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) { localStorage.removeItem("sync.gdrive.state"); state = JSON.parse(state); if (state.action == "create") { - upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, + gdrive.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE, "", function(fileSyncIndex) { if(fileSyncIndex === undefined) { return; diff --git a/js/main.js b/js/main.js index 557bd786..fa789f0a 100644 --- a/js/main.js +++ b/js/main.js @@ -6,6 +6,7 @@ if(location.hostname.indexOf("benweet.github.") === 0) { // RequireJS configuration requirejs.config({ + waitSeconds: 0, paths: configPaths, shim: { 'jquery-ui': ['jquery'], diff --git a/js/synchronizer.js b/js/synchronizer.js index 9be07b48..2e2b49e5 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -1,4 +1,4 @@ -define(["jquery", "core", "gdrive"], function($, core, gdrive) { +define(["jquery", "core", "gdrive", "dropbox"], function($, core, gdrive, dropbox) { var synchronizer = {}; // Dependencies @@ -6,6 +6,7 @@ define(["jquery", "core", "gdrive"], function($, core, gdrive) { // Used to know the providers we are connected to synchronizer.useGoogleDrive = false; + synchronizer.useDropbox = false; var onSyncBegin = undefined; var onSyncEnd = undefined; @@ -60,7 +61,21 @@ define(["jquery", "core", "gdrive"], function($, core, gdrive) { // Try to find the provider if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length); - gdrive.updateFile(id, title, content, function(result) { + gdrive.upload(id, undefined, title, content, function(result) { + if (result !== undefined) { + fileUp(fileSyncIndexList, content, title, callback); + return; + } + // If error we put the fileIndex back in the queue + synchronizer.addFileForUpload(localStorage["sync.current"]); + localStorage.removeItem("sync.current"); + callback(); + return; + }); + } else if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) { + var path = fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length); + path = decodeURIComponent(path); + dropbox.upload(path, content, function(result) { if (result !== undefined) { fileUp(fileSyncIndexList, content, title, callback); return; @@ -157,7 +172,7 @@ define(["jquery", "core", "gdrive"], function($, core, gdrive) { core.showMessage('"' + file.title + '" has been updated from Google Drive.'); if(fileIndex == localStorage["file.current"]) { updateFileTitles = false; // Done by next function - fileManager.selectFile(); + fileManager.selectFile(); // Refresh editor } } // Update file etag @@ -175,8 +190,78 @@ define(["jquery", "core", "gdrive"], function($, core, gdrive) { }); } + function syncDownDropbox(callback) { + if (synchronizer.useDropbox === false) { + callback(); + return; + } + var lastChangeId = localStorage[SYNC_PROVIDER_DROPBOX + "lastChangeId"]; + dropbox.checkUpdates(lastChangeId, function(changes, newChangeId) { + if (changes === undefined) { + callback(); + return; + } + dropbox.downloadContent(changes, function(changes) { + if (changes === undefined) { + callback(); + return; + } + var updateFileTitles = false; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + var fileSyncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(change.path.toLowerCase()); + var fileIndex = fileManager.getFileIndexFromSync(fileSyncIndex); + // No file corresponding (this should never happen...) + if(fileIndex === undefined) { + // We can remove the stored version + localStorage.removeItem(fileSyncIndex + ".version"); + continue; + } + var title = localStorage[fileIndex + ".title"]; + // File deleted + if (change.wasRemoved === true) { + fileManager.removeSync(fileSyncIndex); + updateFileTitles = true; + core.showMessage('"' + title + '" has been removed from Dropbox.'); + continue; + } + var content = localStorage[fileIndex + ".content"]; + var file = change.stat; + var contentChanged = content != file.content; + // If file is in the upload queue we have a conflict + if (contentChanged && syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) { + fileManager.createFile(title + " (backup)", content); + updateFileTitles = true; + core.showMessage('Conflict detected on "' + title + '". A backup has been created locally.'); + } + // If file content changed + if(contentChanged) { + localStorage[fileIndex + ".content"] = file.content; + core.showMessage('"' + title + '" has been updated from Dropbox.'); + if(fileIndex == localStorage["file.current"]) { + updateFileTitles = false; // Done by next function + fileManager.selectFile(); // Refresh editor + } + } + // Update file version + localStorage[fileSyncIndex + ".version"] = file.versionTag; + // Synchronize file to others locations + synchronizer.addFileForUpload(fileIndex); + } + if(updateFileTitles) { + fileManager.updateFileTitles(); + } + localStorage[SYNC_PROVIDER_DROPBOX + + "lastChangeId"] = newChangeId; + callback(); + }); + }); + } + function syncDown(callback) { - syncDownGdrive(callback); + syncDownGdrive(function() { + syncDownDropbox(callback); + }); }; var syncRunning = false;