Skip to content

Commit 26941f9

Browse files
committed
VFS: Added Dropbox v2 support (#28)
1 parent aa676ae commit 26941f9

File tree

4 files changed

+339
-3
lines changed

4 files changed

+339
-3
lines changed

src/client/javascript/core/mount-manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {_} from 'core/locales';
3535
import {getConfig} from 'core/config';
3636

3737
function loadTransports() {
38-
const list = ['web', 'osjs', 'dist', 'applications', 'webdav', 'google-drive', 'onedrive'];
38+
const list = ['web', 'osjs', 'dist', 'applications', 'webdav', 'google-drive', 'onedrive', 'dropbox'];
3939
const result = {};
4040
list.forEach((name) => {
4141
result[name] = require(`vfs/transports/${name}`).default;

src/client/javascript/locales/en_EN.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ module.exports = {
279279
'ERR_VFSMODULE_EXISTS_FMT' : 'Failed to check if exists: {0}',
280280

281281
// VFS -> Dropbox
282+
'ERR_DROPBOX_API' : 'Failed to load Dropbox API',
283+
'ERR_DROPBOX_AUTH' : 'Failed to authenticate via Dropbox',
282284
'DROPBOX_NOTIFICATION_TITLE' : 'You are signed in to Dropbox API',
283285
'DROPBOX_SIGN_OUT' : 'Sign out from Google API Services',
284286

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/*!
2+
* OS.js - JavaScript Cloud/Web Desktop Platform
3+
*
4+
* Copyright (c) 2011-2017, Anders Evenrud <[email protected]>
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice, this
11+
* list of conditions and the following disclaimer
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*
27+
* @author Anders Evenrud <[email protected]>
28+
* @licence Simplified BSD License
29+
*/
30+
31+
// https://github.com/dropbox/dropbox-sdk-js
32+
// https://github.com/dropbox/dropbox-sdk-js/blob/master/examples/javascript/auth/index.html
33+
// http://dropbox.github.io/dropbox-sdk-js/Dropbox.html
34+
35+
import Promise from 'bluebird';
36+
import Transport from 'vfs/transport';
37+
import Preloader from 'utils/preloader';
38+
import {getConfig} from 'core/config';
39+
import FileMetadata from 'vfs/file';
40+
import {urlparams} from 'utils/misc';
41+
import {_} from 'core/locales';
42+
import * as FS from 'utils/fs';
43+
44+
const AUTH_TIMEOUT = (1000 * 30);
45+
46+
///////////////////////////////////////////////////////////////////////////////
47+
// TRANSPORTER
48+
///////////////////////////////////////////////////////////////////////////////
49+
50+
/**
51+
* Dropbox (v2) VFS Transport Module
52+
*
53+
* @extends Transport
54+
*/
55+
export default class DropboxTransport extends Transport {
56+
constructor() {
57+
super(...arguments);
58+
59+
this.loaded = false;
60+
this.authed = false;
61+
this.dbx = null;
62+
}
63+
64+
_loadDependencies() {
65+
if ( this.loaded ) {
66+
return Promise.resolve(true);
67+
}
68+
69+
return new Promise((resolve, reject) => {
70+
Preloader.preload([
71+
'https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js'
72+
]).then(() => {
73+
if ( window.Dropbox ) {
74+
this.loaded = true;
75+
return resolve(true);
76+
}
77+
78+
return reject(new Error(_('ERR_DROPBOX_API')));
79+
}).catch((err) => {
80+
this.loaded = true;
81+
return reject(err);
82+
});
83+
});
84+
}
85+
86+
_createClient(clientId) {
87+
if ( this.authed ) {
88+
return Promise.resolve(true);
89+
}
90+
91+
return new Promise((resolve, reject) => {
92+
let timedOut;
93+
let loginTimeout;
94+
95+
this.dbx = new window.Dropbox({
96+
clientId: clientId
97+
});
98+
99+
const redirectUrl = window.location.href.replace(/\/?$/, '/') + 'dropbox-oauth.html';
100+
const callbackName = '__osjs__dropbox_callback__';
101+
102+
window[callbackName] = (url) => {
103+
clearTimeout(loginTimeout);
104+
if ( timedOut ) {
105+
return;
106+
}
107+
108+
const params = urlparams(url, true);
109+
if ( params.accessToken ) {
110+
this.dbx = new window.Dropbox({
111+
accessToken: params.accessToken
112+
});
113+
114+
resolve(true);
115+
} else {
116+
reject(new Error(_('ERR_DROPBOX_AUTH')));
117+
}
118+
};
119+
120+
const authUrl = this.dbx.getAuthenticationUrl(redirectUrl);
121+
122+
loginTimeout = setTimeout(() => {
123+
timedOut = true;
124+
reject(new Error(_('ERR_DROPBOX_AUTH')));
125+
}, AUTH_TIMEOUT);
126+
127+
window.open(authUrl);
128+
});
129+
}
130+
131+
_init() {
132+
const clientId = getConfig('DropboxAPI.ClientKey');
133+
if ( !clientId ) {
134+
return Promise.reject(new Error('No Dropbox client key defined'));
135+
}
136+
137+
return new Promise((resolve, reject) => {
138+
this._loadDependencies().then(() => {
139+
return this._createClient(clientId).then(resolve).catch(reject);
140+
}).catch(reject);
141+
});
142+
}
143+
144+
request(method, args, options, mount) {
145+
const fargs = arguments;
146+
return new Promise((resolve, reject) => {
147+
this._init().then(() => {
148+
return super.request(...fargs).then(resolve).catch((err) => {
149+
if ( typeof err !== 'string' && !(err instanceof Error) ) {
150+
if ( err.status && err.response && err.error ) {
151+
return reject(new Error(err.error.error_summary));
152+
}
153+
}
154+
return reject(err);
155+
});
156+
}).catch(reject);
157+
});
158+
}
159+
160+
scandir(item, options, mount) {
161+
const root = FS.getPathFromVirtual(item.path);
162+
163+
let result = [];
164+
165+
const scandir = (cursor) => new Promise((resolve, reject) => {
166+
this.dbx.filesListFolder({
167+
path: root === '/' ? '' : root
168+
}).then((response) => {
169+
const found = (response.entries || []).map((iter) => {
170+
return {
171+
id: iter.id,
172+
filename: iter.name,
173+
path: FS.pathJoin(item.path, iter.name),
174+
type: iter['.tag'] === 'folder' ? 'dir' : 'file',
175+
size: iter.size || 0
176+
};
177+
});
178+
179+
result = result.concat(found);
180+
181+
if ( response.has_more && response.cursor ) {
182+
return scandir(response.cursor).then(resolve).catch(reject);
183+
}
184+
185+
return resolve(result);
186+
}).catch(reject);
187+
});
188+
189+
return scandir(null);
190+
}
191+
192+
read(item, options, mount) {
193+
return new Promise((resolve, reject) => {
194+
this.url(item).then((url) => {
195+
this.dbx.sharingGetSharedLinkFile({
196+
url
197+
}).then((data) => {
198+
console.error(data);
199+
return resolve(data.fileBlob);
200+
}).catch(reject);
201+
}).catch(reject);
202+
});
203+
}
204+
205+
write(file, data) {
206+
return new Promise((resolve, reject) => {
207+
this.dbx.filesUpload({
208+
path: FS.getPathFromVirtual(file.path),
209+
mode: {
210+
'.tag': 'overwrite'
211+
},
212+
contents: data
213+
}).then(() => resolve(true)).catch(reject);
214+
});
215+
}
216+
217+
copy(src, dest) {
218+
return new Promise((resolve, reject) => {
219+
this.dbx.filesCopy({
220+
from_path: FS.getPathFromVirtual(src.path),
221+
to_path: FS.getPathFromVirtual(dest.path)
222+
}).then(() => resolve(true)).catch(reject);
223+
});
224+
}
225+
226+
move(src, dest) {
227+
return new Promise((resolve, reject) => {
228+
this.dbx.filesMove({
229+
from_path: FS.getPathFromVirtual(src.path),
230+
to_path: FS.getPathFromVirtual(dest.path)
231+
}).then(() => resolve(true)).catch(reject);
232+
});
233+
}
234+
235+
exists(item) {
236+
return new Promise((resolve, reject) => {
237+
this.fileinfo(item)
238+
.then(() => resolve(true))
239+
.catch(() => resolve(false));
240+
});
241+
}
242+
243+
fileinfo(item) {
244+
return this.dbx.filesGetMetadata({
245+
path: FS.getPathFromVirtual(item.path)
246+
});
247+
}
248+
249+
url(item) {
250+
const visibility = 'public';
251+
252+
const hasLink = () => new Promise((resolve, reject) => {
253+
this.dbx.sharingGetSharedLinks({
254+
path: FS.getPathFromVirtual(item.path)
255+
}).then((response) => {
256+
if ( response.links.length ) {
257+
const found = response.links.find((iter) => iter.visibility['.tag'] === visibility);
258+
if ( found ) {
259+
return resolve(found.url);
260+
}
261+
}
262+
return resolve(false);
263+
}).catch(reject);
264+
});
265+
266+
const newLink = () => new Promise((resolve, reject) => {
267+
this.dbx.sharingCreateSharedLinkWithSettings({
268+
path: FS.getPathFromVirtual(item.path),
269+
settings: {
270+
requested_visibility: visibility
271+
}
272+
}).then((response) => {
273+
return resolve(response.url);
274+
}).catch(reject);
275+
});
276+
277+
return new Promise((resolve, reject) => {
278+
hasLink().then((url) => {
279+
if ( url ) {
280+
console.warn('ALREADY HAS URL', url);
281+
return resolve(url);
282+
}
283+
284+
console.warn('CREATING NEW URL');
285+
return newLink().then(resolve).catch(reject);
286+
}).catch(reject);
287+
});
288+
}
289+
290+
mkdir(dir) {
291+
return new Promise((resolve, reject) => {
292+
this.dbx.filesCreateFolder({
293+
path: FS.getPathFromVirtual(dir.path)
294+
}).then(() => resolve(true)).catch(reject);
295+
});
296+
}
297+
298+
upload(dest, file) {
299+
const item = new FileMetadata({
300+
filename: file.name,
301+
path: FS.pathJoin(dest.path, file.name),
302+
mime: file.type,
303+
size: file.size
304+
});
305+
306+
return this.write(item, file);
307+
}
308+
309+
freeSpace(root) {
310+
return new Promise((resolve, reject) => {
311+
this.dbx.usersGetSpaceUsage().then((response) => {
312+
try {
313+
if ( response.allocation && typeof response.allocation.individual !== 'undefined' ) {
314+
return resolve(response.allocation.individual.allocated);
315+
}
316+
} catch ( e ) {
317+
console.warn(e);
318+
}
319+
320+
return resolve(-1);
321+
}).catch(reject);
322+
});
323+
}
324+
325+
unlink(src) {
326+
return new Promise((resolve, reject) => {
327+
this.dbx.filesDelete({
328+
path: FS.getPathFromVirtual(src.path)
329+
}).then(() => resolve(true)).catch(reject);
330+
});
331+
}
332+
333+
}

src/templates/dist/dropbox-oauth.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4-
<script src="vendor.js" type="text/javascript"></script>
4+
<script src="https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js" type="text/javascript"></script>
55
<script type="text/javascript">
6-
Dropbox.AuthDriver.Popup.oauthReceiver();
6+
window.opener.__osjs__dropbox_callback__(window.location.href);
7+
window.close();
78
</script>
89
</head>
910
<body>

0 commit comments

Comments
 (0)