From 28ab98b0558a081a2912be998f990223dcfc6ade Mon Sep 17 00:00:00 2001 From: Xuan Sang LE Date: Wed, 28 Feb 2018 15:33:57 +0100 Subject: [PATCH] VFS for Google Drive, 1st working version --- src/core/BaseDialog.coffee | 4 +- src/core/api.coffee | 2 + src/core/gui.coffee | 2 +- src/core/tags/afx-list-view.js | 10 ++ src/core/tags/afx-tab-container.js | 5 + src/core/vfs.coffee | 27 +++++- src/core/vfs/GoogleDriveHandler.coffee | 129 +++++++++++++++++++++++-- src/packages/Files/main.coffee | 5 +- src/packages/NotePad/main.coffee | 14 ++- 9 files changed, 178 insertions(+), 20 deletions(-) diff --git a/src/core/BaseDialog.coffee b/src/core/BaseDialog.coffee index f879ba7..41136ba 100644 --- a/src/core/BaseDialog.coffee +++ b/src/core/BaseDialog.coffee @@ -304,6 +304,8 @@ class FileDiaLog extends BaseDialog (@find "bt-cancel").set "onbtclick", (e) -> me.quit() - ($ filename).css("display", "block").val @data.file.basename or "Untitled" if @data and @data.file + if @data and @data.file + ($ filename).css("display", "block").val @data.file.basename or "Untitled" + @trigger "resize" this.OS.register "FileDiaLog", FileDiaLog \ No newline at end of file diff --git a/src/core/api.coffee b/src/core/api.coffee index 0bebe21..745c728 100644 --- a/src/core/api.coffee +++ b/src/core/api.coffee @@ -103,9 +103,11 @@ self.OS.API = .done (data) -> _API.loaded q, p, "OK" c(data) + o.remove() .fail (e, s) -> _API.loaded q, p, "FAIL" f(e, s) + o.remove() o.click() diff --git a/src/core/gui.coffee b/src/core/gui.coffee index e2f7697..5f232a1 100644 --- a/src/core/gui.coffee +++ b/src/core/gui.coffee @@ -353,7 +353,7 @@ self.OS.GUI = _OS.cleanup() # get setting from conf _OS.systemSetting(conf) - console.log _OS.setting + #console.log _OS.setting # load theme _GUI.loadTheme _OS.setting.appearance.theme # initDM diff --git a/src/core/tags/afx-list-view.js b/src/core/tags/afx-list-view.js index 7a8fe10..6bc9692 100644 --- a/src/core/tags/afx-list-view.js +++ b/src/core/tags/afx-list-view.js @@ -60,6 +60,16 @@ self.items.unshift(e) if(u) self.update() } + self.root.replaceItem = function(o, n, u) + { + var ix = self.items.indexOf(o) + if(ix >= 0) + { + self.items[ix] = n + if(u) self.update() + } + + } self.root.remove = function(e,u) { var i = self.items.indexOf(e) diff --git a/src/core/tags/afx-tab-container.js b/src/core/tags/afx-tab-container.js index 10c3506..c61b43a 100644 --- a/src/core/tags/afx-tab-container.js +++ b/src/core/tags/afx-tab-container.js @@ -56,6 +56,11 @@ e.closable = self.closable self.refs.list.root.push(e,u) } + self.root.replaceItem = function(o,n,u) + { + n.closable = self.closable + self.refs.list.root.replaceItem(o,n,u) + } self.root.unshift = function(e,u) { self.refs.list.root.unshift(e,u) diff --git a/src/core/vfs.coffee b/src/core/vfs.coffee index 1a99f5f..afd1de1 100644 --- a/src/core/vfs.coffee +++ b/src/core/vfs.coffee @@ -36,6 +36,12 @@ class BaseFileHandler isRoot: () -> (not @genealogy) or (@genealogy.size is 0) + child: (name) -> + if @isRoot() + return @path + name + else + return @path + "/" + name + isHidden: () -> return false if not @basename @basename[0] is "." @@ -44,10 +50,20 @@ class BaseFileHandler return -1 unless @path return @path.hash() - getb64: (m) -> - return "" unless @cache - b64 = @cache.asBase64() - return "data:#{m};base64,#{b64}" + sendB64: (m, f) -> + me = @ + return unless @cache + if typeof @cache is "string" + b64 = @cache.asBase64() + b64 = "data:#{m};base64,#{b64}" + f(b64) + else + reader = new FileReader() + reader.readAsDataURL(@cache) + reader.onload = () -> + f reader.result + reader.onerror = (e) -> + return _courrier.osfail "Cannot ecode file: #{me.path}", (_API.throwe "OS.VFS"), e parent: () -> return @ if @isRoot() return (@protocol + ":///" + (@genealogy.slice 0 , @genealogy.length - 1).join "/") @@ -140,7 +156,8 @@ class RemoteFileHandler extends self.OS.API.VFS.BaseFileHandler return f { error: "#{@path} is not a directory" } if @info.type is "file" _API.handler.mkdir "#{@path}/#{p}", f when "write" - _API.handler.write @path, p, f + @sendB64 p, (data) -> + _API.handler.write me.path, data, f when "upload" return if @info.type is "file" _API.handler.upload @path, f diff --git a/src/core/vfs/GoogleDriveHandler.coffee b/src/core/vfs/GoogleDriveHandler.coffee index 1d5e5c8..c3ddf35 100644 --- a/src/core/vfs/GoogleDriveHandler.coffee +++ b/src/core/vfs/GoogleDriveHandler.coffee @@ -9,6 +9,7 @@ class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler @setting = _OS.setting.VFS.gdrive return _courrier.oserror "Unknown API setting for google drive VFS", (_API.throwe "OS.VFS"), null unless @setting @gid = 'root' if @isRoot() + @cache = "" oninit: (f) -> me = @ @@ -28,6 +29,7 @@ class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler fn = (r) -> return f() if r # perform the login + G_CACHE = {"gdv:///":"root"} gapi.auth2.getAuthInstance().signIn() gapi.auth2.getAuthInstance().isSignedIn.listen (r) -> @@ -65,21 +67,49 @@ class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler fields: () -> return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size" + isFolder: () -> + return @info.mimeType is "application/vnd.google-apps.folder" + + save: (id, m, f) -> + me = @ + user = gapi.auth2.getAuthInstance().currentUser.get() + oauthToken = user.getAuthResponse().access_token + + xhr = new XMLHttpRequest() + url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media' + xhr.open('PATCH', url) + xhr.setRequestHeader 'Authorization', 'Bearer ' + oauthToken + xhr.setRequestHeader 'Content-Type', m + xhr.setRequestHeader 'Content-Encoding', 'base64' + xhr.setRequestHeader 'Content-Transfer-Encoding', 'base64' + error = (e, s) -> + _courrier.oserror "VFS cannot save : #{me.path}", e, s + xhr.onreadystatechange = () -> + if ( xhr.readyState == 4 ) + if ( xhr.status == 200 ) + f { result: JSON.parse(xhr.responseText) } + else + error xhr, xhr.status + xhr.onerror = () -> + error xhr, xhr.status + + @sendB64 m, (data) -> + xhr.send data.replace /^data:[^;]+;base64,/g, "" action: (n, p, f) -> me = @ switch n when "read" return unless @info.id - if @info.mimeType is "application/vnd.google-apps.folder" + if @isFolder() gapi.client.drive.files.list { q: "'#{me.info.id}' in parents and trashed = false", fields: "files(#{me.fields()})" } .then (r) -> - return unless r.result.files and r.result.files.length > 0 + return unless r.result.files for file in r.result.files - file.path = me.path + "/" + file.name + file.path = me.child file.name file.mime = file.mimeType file.filename = file.name file.type = "file" @@ -87,6 +117,7 @@ class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler if file.mimeType is "application/vnd.google-apps.folder" file.mime = "dir" file.type = "dir" + file.size = 0 f { result: r.result.files } else gapi.client.drive.files.get { @@ -97,19 +128,101 @@ class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler f r.body when "mk" + return f { error: "#{@path} is not a directory" } unless @isFolder() + meta = + name: p, + parents: [@info.id], + mimeType: 'application/vnd.google-apps.folder' + + gapi.client.drive.files.create { + resource: meta, + fields: 'id' + } + .execute (r) -> + #console.log r + return _courrier.oserror "VFS cannot create : #{p}", (_API.throwe "OS.VFS"), r unless r and r.result + G_CACHE[me.child p] = r.result.id + f r + return + when "write" - return + gid = G_CACHE[me.path] + if gid + @save gid, p, f + else + console.log "New file" + dir = @parent().asFileHandler() + dir.onready () -> + meta = + name: me.basename, + mimeType: p, + parents: [dir.info.id] + + gapi.client.drive.files.create { + resource: meta, + fields: 'id' + } + .execute (r) -> + return _courrier.oserror "VFS cannot write : #{me.path}", (_API.throwe "OS.VFS"), r unless r and r.result + G_CACHE[me.path] = r.result.id + me.save r.result.id, p, f + when "upload" - return + return unless @isFolder() + q = _courrier.getMID() + #insert a temporal file selector + o = ($ '').attr('type', 'file').css("display", "none") + o.change () -> + #_API.loading q, p + fo = o[0].files[0] + file = (me.child fo.name).asFileHandler() + file.cache = fo + file.write fo.type, f + o.remove() + + #_API.loaded q, p, "OK" + #_API.loaded q, p, "FAIL" + + o.click() + when "remove" - return + return unless @info.id + gapi.client.drive.files.delete { + fileId: me.info.id + } + .execute (r) -> + #console.log r + return _courrier.oserror "VFS cannot delete : #{me.path}", (_API.throwe "OS.VFS"), r unless r + G_CACHE[me.path] = null + f { result: true } + when "publish" return + when "download" - return + gapi.client.drive.files.get { + fileId: me.info.id, + alt: 'media' + } + .then (r) -> + return _courrier.oserror "VFS cannot get file : #{me.path}", (_API.throwe "OS.VFS"), r unless r.body + blob = new Blob [r.body], { type: "octet/stream" } + _API.saveblob me.basename, blob + when "move" - return + dest = p.asFileHandler().parent().asFileHandler() + dest.onready () -> + previousParents = me.info.parents.join ',' + gapi.client.drive.files.update { + fileId: me.info.id, + addParents: dest.info.id, + removeParents: previousParents, + fields: "id" + } + .execute (r) -> + return _courrier.oserror "VFS cannot move : #{me.path}", (_API.throwe "OS.VFS"), r unless r + f r else return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n diff --git a/src/packages/Files/main.coffee b/src/packages/Files/main.coffee index 65dd106..0c42e24 100644 --- a/src/packages/Files/main.coffee +++ b/src/packages/Files/main.coffee @@ -88,6 +88,7 @@ class Files extends this.OS.GUI.BaseApplication chdir: (p) -> me = @ + console.log "ch" dir = if p then p.asFileHandler() else me.currdir dir.read (d) -> if(d.error) @@ -102,7 +103,7 @@ class Files extends this.OS.GUI.BaseApplication d.result.unshift p ($ me.navinput).val dir.path me.view.set "path", dir.path - console.log d.result + #console.log d.result me.view.set "data", d.result mnFile:() -> @@ -252,7 +253,7 @@ class Files extends this.OS.GUI.BaseApplication @openDialog "PromptDialog", (d) -> fp = "#{me.currdir.path}/#{d}".asFileHandler() - fp.write "", (r) -> + fp.write "text/plain", (r) -> me.error "Fail to create #{d}: #{r.error}" if r.error , "New file", { label: "File name:" } diff --git a/src/packages/NotePad/main.coffee b/src/packages/NotePad/main.coffee index fae8a36..3134f89 100644 --- a/src/packages/NotePad/main.coffee +++ b/src/packages/NotePad/main.coffee @@ -112,6 +112,7 @@ class NotePad extends this.OS.GUI.BaseApplication open: (file) -> #find table i = @findTabByFile file + @fileview.set "preventUpdate", true return @tabarea.set "selected", i if i isnt -1 return @newtab file if file.path.toString() is "Untitled" me = @ @@ -163,7 +164,7 @@ class NotePad extends this.OS.GUI.BaseApplication save: (file) -> me = @ - file.write (file.getb64 "text/plain"), (d) -> + file.write "text/plain", (d) -> return me.error "Error saving file #{file.basename}" if d.error file.dirty = false file.text = file.basename @@ -191,7 +192,6 @@ class NotePad extends this.OS.GUI.BaseApplication @currfile.selected = false file.selected = true #console.log cnt - @fileview.set "preventUpdate", true @tabarea.push file, true #@currfile = @file #TODO: fix problem : @tabarea.set "selected", cnt @@ -255,7 +255,15 @@ class NotePad extends this.OS.GUI.BaseApplication me = @ saveas = () -> me.openDialog "FileDiaLog", (d, n) -> - me.currfile.setPath "#{d}/#{n}" + file = "#{d}/#{n}".asFileHandler() + file.cache = me.currfile.cache + file.dirty = me.currfile.dirty + file.um = me.currfile.um + file.cursor = me.currfile.cursor + file.selected = me.currfile.selected + file.text = me.currfile.text + me.tabarea.replaceItem me.currfile, file, false + me.currfile = file me.save me.currfile , "Save as", { file: me.currfile } switch e