diff --git a/Antunnel/build.json b/Antunnel/build.json
new file mode 100644
index 0000000..3afc3e6
--- /dev/null
+++ b/Antunnel/build.json
@@ -0,0 +1,81 @@
+{
+ "name": "Antunnel",
+ "targets": {
+ "init": {
+ "jobs": [
+ {
+ "name": "vfs-mkdir",
+ "data": [
+ "build",
+ "build/debug",
+ "build/release"
+ ]
+ }
+ ]
+ },
+ "coffee": {
+ "require": [
+ "coffee"
+ ],
+ "jobs": [
+ {
+ "name": "coffee-compile",
+ "data": {
+ "src": [
+ "coffees/Antunnel.coffee",
+ "coffees/AntunnelService.coffee"
+ ],
+ "dest": "build/debug/main.js"
+ }
+ }
+ ]
+ },
+ "uglify": {
+ "require": [
+ "terser"
+ ],
+ "jobs": [
+ {
+ "name": "terser-uglify",
+ "data": [
+ "build/debug/main.js"
+ ]
+ }
+ ]
+ },
+ "copy": {
+ "jobs": [
+ {
+ "name": "vfs-cp",
+ "data": {
+ "src": [
+ "package.json",
+ "README.md"
+ ],
+ "dest": "build/debug"
+ }
+ }
+ ]
+ },
+ "release": {
+ "require": [
+ "zip"
+ ],
+ "depend": [
+ "init",
+ "coffee",
+ "copy",
+ "uglify"
+ ],
+ "jobs": [
+ {
+ "name": "zip-mk",
+ "data": {
+ "src": "build/debug",
+ "dest": "build/release/Antunnel.zip"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/Antunnel/build/debug/main.js b/Antunnel/build/debug/main.js
index c337629..e478e97 100644
--- a/Antunnel/build/debug/main.js
+++ b/Antunnel/build/debug/main.js
@@ -1 +1 @@
-(function(){var e,n,t,s,i;(t=class e{constructor(){this.header={sid:0,cid:0,type:0,size:0},this.data=void 0}as_raw(){var n,t,s;return s=13+this.header.size,(n=new Uint8Array(s)).set(e.MAGIC_START,0),n[2]=this.header.type,t=e.bytes_of(this.header.cid),n.set(t,3),t=e.bytes_of(this.header.sid),n.set(t,5),t=e.bytes_of(this.header.size,4),n.set(t,7),this.data&&n.set(this.data,11),n.set(e.MAGIC_END,this.header.size+11),n.buffer}}).decode=function(e){return new Promise((function(n,s){var i;return i=new t,t.int_from(t.MAGIC_START,0)!==t.int_from(e,0)?s("Unmatch message begin magic number"):(i.header.type=e[2],i.header.cid=t.int_from(e,3),i.header.sid=t.int_from(e,5),i.header.size=t.int_from(e,7,4),i.data=e.slice(11,11+i.header.size),t.int_from(t.MAGIC_END,0)!==t.int_from(e,11+i.header.size)?s("Unmatch message end magic number"):n(i))}))},t.bytes_of=function(e,n){var t;return 4!==n&&(n=2),(t=new Uint8Array(n))[0]=255&e,e>>=8,t[1]=255&e,4!==n||(e>>=8,t[2]=255&e,e>>=8,t[3]=255&e),t},t.int_from=function(e,n,t){return 4!==t?e[n]|e[n+1]<<8:e[n]|e[n+1]<<8|e[n+2]<<16|e[n+3]<<24},t.OK=0,t.ERROR=1,t.DATA=6,t.CLOSE=5,t.SUBSCRIBE=2,t.UNSUBSCRIBE=3,t.CTRL=7,t.PING=8,t.MAGIC_END=[68,84],t.MAGIC_START=[78,65],s=class{constructor(e){this.channel=e,this.id=void 0,this.channel_id=void 0,this.onmessage=void 0,this.onerror=void 0,this.onopen=void 0,this.onclose=void 0,this.tunnel=void 0,this.is_opened=!1}send(e,n){if(this.tunnel){if(this.is_opened)return this.tunnel.send(this.genmsg(e,n));this.onerror&&this.onerror("Channel is not opened yet")}else this.onerror&&this.onerror("Tunnel is not opened")}genmsg(e,n){var s;return(s=new t).header.sid=this.id,s.header.cid=this.channel_id,s.header.type=e,s.header.size=n?n.length:0,s.data=n,s}close(e){if(this.is_opened=!1,this.tunnel)return this.tunnel.unsubscribe(this,e)}},e=class{constructor(e){this.uri=e,this.socket=void 0,this.pending={},this.subscribers={},this.onclose=void 0}ready(){return new Promise((e,n)=>this.uri?void 0!==this.socket?e():(console.log("Connect to "+this.uri),this.socket=new WebSocket(this.uri),this.socket.binaryType="arraybuffer",this.socket.onmessage=e=>this.process(e),this.socket.onclose=e=>{var n,t,s,i;for(n in this.socket=void 0,t=this.pending)(i=t[n]).tunnel=void 0,i.onclose&&i.onclose();for(n in s=this.subscribers)(i=s[n]).tunnel=void 0,i.is_opened=!1,i.onclose&&i.onclose();if(this.pending={},this.subscribe={},this.onclose())return this.onclose()},this.socket.onerror=e=>{var n,t,s,i,r;for(n in t=this.pending)(r=t[n]).onerror&&r.onerror(e.toString());for(n in i=[],s=this.subscribers)(r=s[n]).onerror&&i.push(r.onerror(e.toString()));return i},this.socket.onopen=n=>e()):n())}process(e){return t.decode(new Uint8Array(e.data)).then(e=>{var n,s;switch(n=(e,n)=>{var t;if(!(t=this.pending[e.header.sid]))return(t=this.subscribers[e.header.sid])&&t[n]?t[n](e):void 0;t[n]&&t[n](e)},e.header.type){case t.OK:if(!(s=this.pending[e.header.sid]))return n(e,"onmessage");if(delete this.pending[e.header.sid],s.id=t.int_from(e.data,0),s.channel_id=e.header.cid,this.subscribers[s.id]=s,s.is_opened=!0,s.onopen)return s.onopen();break;case t.DATA:return n(e,"onmessage");case t.CTRL:return n(e,"onctrl");case t.ERROR:return n(e,"onerror");case t.UNSUBSCRIBE:if(!(s=this.subscribers[e.header.sid]))return;return s.close(!0);case t.PING:break;default:return console.error(`Message of type ${e.header.type} is unsupported`,e)}}).catch(e=>{var n,t,s,i;for(n in t=this.pending)(i=t[n]).onerror&&i.onerror(e);for(n in s=this.subscribers)(i=s[n]).onerror&&i.onerror(e);return console.log(e)})}subscribe(e){return this.ready().then(()=>{for(e.tunnel=this,e.id=Math.floor(1e3*Math.random())+1;this.subscribers[e.id]||this.pending[e.id];)e.id=Math.floor(1e3*Math.random())+1;return this.pending[e.id]=e,this.send(e.genmsg(t.SUBSCRIBE,(new TextEncoder).encode(e.channel)))}).catch((function(n){if(e.onerror)return e.onerror(n.toString())}))}unsubscribe(e,n){return this.ready().then(()=>{if(this.subscribers[e.id])return n||this.send(e.genmsg(t.UNSUBSCRIBE,void 0)),e.onclose&&e.onclose(),delete this.subscribers[e.id],e.tunnel=void 0,e.is_opened=!1}).catch((function(n){if(e.onerror)return e.onerror(n.toString())}))}send(e){return this.socket.send(e.as_raw())}close(){if(console.log("Close connection to "+this.uri),this.socket&&this.socket.close(),this.onclose())return this.onclose()}},(i=this).Antunnel||(i.Antunnel={tunnel:void 0,init:function(n){return new Promise((function(t,s){return i.Antunnel.tunnel?t(i.Antunnel.tunnel):(i.Antunnel.tunnel=new e(n),i.Antunnel.tunnel.onclose=function(){return i.Antunnel.tunnel=void 0},i.Antunnel.tunnel.ready().then((function(){return t(i.Antunnel.tunnel)})).catch((function(e){return s(e)})))}))},Subscriber:s,Msg:t}),n=class extends OS.application.BaseService{constructor(e){super("AntunnelService",e),this.text=__("Tunnel"),this.iconclass="fa fa-close",this.is_connect=!1,this.nodes=[{text:__("Connect"),id:1},{text:__("Disconnect"),id:2},{text:__("Enter uri"),id:3},{text:__("Exit"),id:4}],this.onchildselect=e=>this.action(e)}init(){return this.watch(1500,()=>{var e;if(e=!1,void 0!==Antunnel.tunnel&&(e=!0),e!==this.is_connect)return this.is_connect=e,this.iconclass="fa fa-circle",this.is_connect||(this.iconclass="fa fa-close"),this.update()}),OS.onexit("cleanupAntunnel",()=>(Antunnel.tunnel&&Antunnel.tunnel.close(),this.quit()))}action(e){var n;switch(n=()=>this._gui.openDialog("PromptDialog",{title:__("Tunnel uri"),label:__("Please enter tunnel uri"),value:"wss://localhost/tunnel"}).then(e=>{if(e&&""!==e)return this.systemsetting.system.tunnel_uri=e,this.start()}),e.data.item.data.id){case 1:if(this.is_connect)return;return this.systemsetting.system.tunnel_uri?this.start():n();case 2:if(Antunnel.tunnel)return Antunnel.tunnel.close();break;case 3:return Antunnel.tunnel&&Antunnel.tunnel.close(),n();case 4:return Antunnel.tunnel&&Antunnel.tunnel.close(),this.quit()}}start(){if(this.systemsetting.system.tunnel_uri&&!Antunnel.tunnel)return Antunnel.init(this.systemsetting.system.tunnel_uri).then(e=>this.notify(__("Tunnel now connected to the server at: {0}",this.systemsetting.system.tunnel_uri))).catch(e=>(Antunnel.tunnel&&Antunnel.tunnel.close(),this.error(__("Unable to connect to the tunnel: {0}",e.toString()),e)))}awake(){}},this.OS.register("AntunnelService",n)}).call(this);
\ No newline at end of file
+(function(){var e,n,t,s,i;(t=class e{constructor(){this.header={sid:0,cid:0,type:0,size:0},this.data=void 0}as_raw(){var n,t,s;return s=13+this.header.size,(n=new Uint8Array(s)).set(e.MAGIC_START,0),n[2]=this.header.type,t=e.bytes_of(this.header.cid),n.set(t,3),t=e.bytes_of(this.header.sid),n.set(t,5),t=e.bytes_of(this.header.size,4),n.set(t,7),this.data&&n.set(this.data,11),n.set(e.MAGIC_END,this.header.size+11),n.buffer}}).decode=function(e){return new Promise((function(n,s){var i;return i=new t,t.int_from(t.MAGIC_START,0)!==t.int_from(e,0)?s("Unmatch message begin magic number"):(i.header.type=e[2],i.header.cid=t.int_from(e,3),i.header.sid=t.int_from(e,5),i.header.size=t.int_from(e,7,4),i.data=e.slice(11,11+i.header.size),t.int_from(t.MAGIC_END,0)!==t.int_from(e,11+i.header.size)?s("Unmatch message end magic number"):n(i))}))},t.bytes_of=function(e,n){var t;return 4!==n&&(n=2),(t=new Uint8Array(n))[0]=255&e,e>>=8,t[1]=255&e,4!==n||(e>>=8,t[2]=255&e,e>>=8,t[3]=255&e),t},t.int_from=function(e,n,t){return 4!==t?e[n]|e[n+1]<<8:e[n]|e[n+1]<<8|e[n+2]<<16|e[n+3]<<24},t.OK=0,t.ERROR=1,t.DATA=6,t.CLOSE=5,t.SUBSCRIBE=2,t.UNSUBSCRIBE=3,t.CTRL=7,t.PING=8,t.MAGIC_END=[68,84],t.MAGIC_START=[78,65],s=class{constructor(e){this.channel=e,this.id=void 0,this.channel_id=void 0,this.onmessage=void 0,this.onerror=void 0,this.onopen=void 0,this.onclose=void 0,this.tunnel=void 0,this.is_opened=!1}send(e,n){if(this.tunnel){if(this.is_opened)return this.tunnel.send(this.genmsg(e,n));this.onerror&&this.onerror("Channel is not opened yet")}else this.onerror&&this.onerror("Tunnel is not opened")}genmsg(e,n){var s;return(s=new t).header.sid=this.id,s.header.cid=this.channel_id,s.header.type=e,s.header.size=n?n.length:0,s.data=n,s}close(e){if(this.is_opened=!1,this.tunnel)return this.tunnel.unsubscribe(this,e)}},e=class{constructor(e){this.uri=e,this.socket=void 0,this.pending={},this.subscribers={},this.onclose=void 0}ready(){return new Promise((e,n)=>this.uri?void 0!==this.socket?e():(console.log("Connect to "+this.uri),this.socket=new WebSocket(this.uri),this.socket.binaryType="arraybuffer",this.socket.onmessage=e=>this.process(e),this.socket.onclose=e=>{var n,t,s,i;for(n in this.socket=void 0,t=this.pending)(i=t[n]).tunnel=void 0,i.onclose&&i.onclose();for(n in s=this.subscribers)(i=s[n]).tunnel=void 0,i.is_opened=!1,i.onclose&&i.onclose();if(this.pending={},this.subscribe={},this.onclose())return this.onclose()},this.socket.onerror=e=>{var n,t,s,i,r;for(n in t=this.pending)(r=t[n]).onerror&&r.onerror(e.toString());for(n in i=[],s=this.subscribers)(r=s[n]).onerror&&i.push(r.onerror(e.toString()));return i},this.socket.onopen=n=>e()):n())}process(e){return t.decode(new Uint8Array(e.data)).then(e=>{var n,s;switch(n=(e,n)=>{var t;if(!(t=this.pending[e.header.sid]))return(t=this.subscribers[e.header.sid])&&t[n]?t[n](e):void 0;t[n]&&t[n](e)},e.header.type){case t.OK:if(!(s=this.pending[e.header.sid]))return n(e,"onmessage");if(delete this.pending[e.header.sid],s.id=t.int_from(e.data,0),s.channel_id=e.header.cid,this.subscribers[s.id]=s,s.is_opened=!0,s.onopen)return s.onopen();break;case t.DATA:return n(e,"onmessage");case t.CTRL:return n(e,"onctrl");case t.ERROR:return n(e,"onerror");case t.UNSUBSCRIBE:if(!(s=this.subscribers[e.header.sid]))return;return s.close(!0);case t.PING:break;default:return console.error(`Message of type ${e.header.type} is unsupported`,e)}}).catch(e=>{var n,t,s,i;for(n in t=this.pending)(i=t[n]).onerror&&i.onerror(e);for(n in s=this.subscribers)(i=s[n]).onerror&&i.onerror(e);return console.log(e)})}subscribe(e){return this.ready().then(()=>{for(e.tunnel=this,e.id=Math.floor(1e3*Math.random())+1;this.subscribers[e.id]||this.pending[e.id];)e.id=Math.floor(1e3*Math.random())+1;return this.pending[e.id]=e,this.send(e.genmsg(t.SUBSCRIBE,(new TextEncoder).encode(e.channel)))}).catch((function(n){if(e.onerror)return e.onerror(n.toString())}))}unsubscribe(e,n){return this.ready().then(()=>{if(this.subscribers[e.id])return n||this.send(e.genmsg(t.UNSUBSCRIBE,void 0)),e.onclose&&e.onclose(),delete this.subscribers[e.id],e.tunnel=void 0,e.is_opened=!1}).catch((function(n){if(e.onerror)return e.onerror(n.toString())}))}send(e){return this.socket.send(e.as_raw())}close(){if(console.log("Close connection to "+this.uri),this.socket&&this.socket.close(),this.onclose())return this.onclose()}},(i=this).Antunnel||(i.Antunnel={tunnel:void 0,init:function(n){return new Promise((function(t,s){return i.Antunnel.tunnel?t(i.Antunnel.tunnel):(i.Antunnel.tunnel=new e(n),i.Antunnel.tunnel.onclose=function(){return i.Antunnel.tunnel=void 0},i.Antunnel.tunnel.ready().then((function(){return t(i.Antunnel.tunnel)})).catch((function(e){return s(e)})))}))},Subscriber:s,Msg:t}),n=class extends OS.application.BaseService{constructor(e){super("AntunnelService",e),this.text=__("Tunnel"),this.iconclass="fa fa-close",this.is_connect=!1,this.nodes=[{text:__("Connect"),id:1},{text:__("Disconnect"),id:2},{text:__("Enter uri"),id:3},{text:__("Exit"),id:4}],this.onchildselect=e=>this.action(e)}init(){return this.watch(1500,()=>{var e;if(e=!1,void 0!==Antunnel.tunnel&&(e=!0),e!==this.is_connect)return this.is_connect=e,this.iconclass="fa fa-circle",this.is_connect||(this.iconclass="fa fa-close"),this.update()}),OS.onexit("cleanupAntunnel",()=>new Promise((e,n)=>(Antunnel.tunnel&&Antunnel.tunnel.close(),this.quit(),e(!0))))}action(e){var n;switch(n=()=>this._gui.openDialog("PromptDialog",{title:__("Tunnel uri"),label:__("Please enter tunnel uri"),value:"wss://localhost/tunnel"}).then(e=>{if(e&&""!==e)return this.systemsetting.system.tunnel_uri=e,this.start()}),e.data.item.data.id){case 1:if(this.is_connect)return;return this.systemsetting.system.tunnel_uri?this.start():n();case 2:if(Antunnel.tunnel)return Antunnel.tunnel.close();break;case 3:return Antunnel.tunnel&&Antunnel.tunnel.close(),n();case 4:return Antunnel.tunnel&&Antunnel.tunnel.close(),this.quit()}}start(){if(this.systemsetting.system.tunnel_uri&&!Antunnel.tunnel)return Antunnel.init(this.systemsetting.system.tunnel_uri).then(e=>this.notify(__("Tunnel now connected to the server at: {0}",this.systemsetting.system.tunnel_uri))).catch(e=>(Antunnel.tunnel&&Antunnel.tunnel.close(),this.error(__("Unable to connect to the tunnel: {0}",e.toString()),e)))}awake(){}},this.OS.register("AntunnelService",n)}).call(this);
\ No newline at end of file
diff --git a/Antunnel/build/release/Antunnel.zip b/Antunnel/build/release/Antunnel.zip
index 1038ee9..96d012b 100644
Binary files a/Antunnel/build/release/Antunnel.zip and b/Antunnel/build/release/Antunnel.zip differ
diff --git a/Antunnel/coffees/AntunnelService.coffee b/Antunnel/coffees/AntunnelService.coffee
index f60c287..047ff40 100644
--- a/Antunnel/coffees/AntunnelService.coffee
+++ b/Antunnel/coffees/AntunnelService.coffee
@@ -24,8 +24,10 @@ class AntunnelService extends OS.application.BaseService
@iconclass = "fa fa-close" unless @is_connect
@update()
OS.onexit "cleanupAntunnel", () =>
- Antunnel.tunnel.close() if Antunnel.tunnel
- @quit()
+ return new Promise (resolve, reject) =>
+ Antunnel.tunnel.close() if Antunnel.tunnel
+ @quit()
+ resolve(true)
action: (e) ->
diff --git a/Antunnel/project.json b/Antunnel/project.json
deleted file mode 100644
index 84cfb1f..0000000
--- a/Antunnel/project.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "Antunnel",
- "css": [],
- "javascripts": [],
- "coffees": ["coffees/Antunnel.coffee", "coffees/AntunnelService.coffee"],
- "copies": ["package.json", "README.md"]
-}
\ No newline at end of file
diff --git a/packages.json b/packages.json
index 4f98cc8..e545460 100644
--- a/packages.json
+++ b/packages.json
@@ -389,6 +389,16 @@
"dependencies": [],
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/TinyEditor/build/release/TinyEditor.zip"
},
+ {
+ "pkgname": "vfsx",
+ "name": "AntOS VFS handles",
+ "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/vfsx/README.md",
+ "category": "Library",
+ "author": "Dany LE",
+ "version": "0.1.0-b",
+ "dependencies": [],
+ "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/vfsx/build/release/vfsx.zip"
+ },
{
"pkgname": "VizApp",
"name": "Viz editor",
diff --git a/vfsx/README.md b/vfsx/README.md
new file mode 100644
index 0000000..7af9f87
--- /dev/null
+++ b/vfsx/README.md
@@ -0,0 +1,10 @@
+# vfsx
+AntOS VFS handles for various file protocols which are not included by default
+int core release, such as:
+- GoogleDrive
+- Dropbox (TODO)
+
+This package is used mainly by the File application to communicate with different
+file hosting protocols
+
+## Change logs
diff --git a/vfsx/build.json b/vfsx/build.json
new file mode 100644
index 0000000..bb66f23
--- /dev/null
+++ b/vfsx/build.json
@@ -0,0 +1,83 @@
+{
+ "name": "vfsx",
+ "targets":{
+ "clean": {
+ "jobs": [
+ {
+ "name": "vfs-rm",
+ "data": ["build/debug","build/release"]
+ }
+ ]
+ },
+ "build": {
+ "require": ["ts"],
+ "jobs":[
+ {
+ "name": "vfs-mkdir",
+ "data": ["build","build/debug","build/release"]
+ },
+ {
+ "name": "ts-import",
+ "data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
+ },
+ {
+ "name": "ts-compile",
+ "data": {
+ "src": ["gdv.ts"],
+ "dest": "build/debug/vfsx.js"
+ }
+ }
+ ]
+ },
+ "uglify": {
+ "require": ["terser"],
+ "jobs": [
+ {
+ "name":"terser-uglify",
+ "data": ["build/debug/vfsx.js"]
+ }
+ ]
+ },
+ "copy": {
+ "jobs": [
+ {
+ "name": "vfs-cp",
+ "data": {
+ "src": [
+ "package.json",
+ "README.md"
+ ],
+ "dest":"build/debug"
+ }
+ }
+ ]
+ },
+ "locale": {
+ "require": ["locale"],
+ "jobs": [
+ {
+ "name":"locale-gen",
+ "data": {
+ "src": "",
+ "exclude": ["build/"],
+ "locale": "en_GB",
+ "dest": "package.json"
+ }
+ }
+ ]
+ },
+ "release": {
+ "depend": ["clean","build","uglify", "copy"],
+ "require": ["zip"],
+ "jobs": [
+ {
+ "name": "zip-mk",
+ "data": {
+ "src":"build/debug",
+ "dest":"build/release/vfsx.zip"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/vfsx/build/debug/README.md b/vfsx/build/debug/README.md
new file mode 100644
index 0000000..7af9f87
--- /dev/null
+++ b/vfsx/build/debug/README.md
@@ -0,0 +1,10 @@
+# vfsx
+AntOS VFS handles for various file protocols which are not included by default
+int core release, such as:
+- GoogleDrive
+- Dropbox (TODO)
+
+This package is used mainly by the File application to communicate with different
+file hosting protocols
+
+## Change logs
diff --git a/vfsx/build/debug/package.json b/vfsx/build/debug/package.json
new file mode 100644
index 0000000..c1c26c1
--- /dev/null
+++ b/vfsx/build/debug/package.json
@@ -0,0 +1,39 @@
+{
+ "pkgname": "vfsx",
+ "name": "AntOS VFS handles",
+ "description": "AntOS VFS handles for various file protocols which are not included by default int core release",
+ "info": {
+ "author": "Dany LE",
+ "email": "mrsang@iohub.dev"
+ },
+ "version": "0.1.0-b",
+ "category": "Library",
+ "iconclass": "fa fa-cog",
+ "mimes": [
+ "none"
+ ],
+ "dependencies": [],
+ "locale": {},
+ "locales": {
+ "en_GB": {
+ "Unknown API setting for GAPI": "Unknown API setting for GAPI",
+ "VFS cannot download file : {0}": "VFS cannot download file : {0}",
+ "VFS cannot get meta data for {0}": "VFS cannot get meta data for {0}",
+ "No GAPI meta found": "No GAPI meta found",
+ "Authentication": "Authentication",
+ "Would you like to login to GoogleDrive?": "Would you like to login to GoogleDrive?",
+ "User abort the authentication": "User abort the authentication",
+ "File ID is not valid": "File ID is not valid",
+ "File {0} not found": "File {0} not found",
+ "Cannot find local copy of file; {0}": "Cannot find local copy of file; {0}",
+ "VFS cannot save : {0}": "VFS cannot save : {0}",
+ "VFS cannot write : {0}": "VFS cannot write : {0}",
+ "{0} is not a directory": "{0} is not a directory",
+ "VFS cannot create : {0}": "VFS cannot create : {0}",
+ "Cannot identify file id of {0}": "Cannot identify file id of {0}",
+ "VFS cannot delete : {0}": "VFS cannot delete : {0}",
+ "VFS cannot move : {0}": "VFS cannot move : {0}",
+ "Target file should be a folder": "Target file should be a folder"
+ }
+ }
+}
\ No newline at end of file
diff --git a/vfsx/build/debug/vfsx.js b/vfsx/build/debug/vfsx.js
new file mode 100644
index 0000000..c34a3e8
--- /dev/null
+++ b/vfsx/build/debug/vfsx.js
@@ -0,0 +1 @@
+var OS;!function(e){let t;!function(t){let i;!function(i){let a={"gdv://":{id:"root",mime:"dir"}};class r extends i.BaseFileHandle{constructor(t){super(t),r.API_META?(this.isRoot()&&(this.gid="root"),this.cache="",this.local_copy=void 0):e.announcer.oserror(__("Unknown API setting for GAPI"),e.API.throwe("OS.VFS"))}fields(){return"webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size, version"}isFolder(){return"application/vnd.google-apps.folder"===this.info.mimeType}load(e){const i=t.mid();return new Promise(async(a,r)=>{t.loading(i,"GAPI");try{let r=await e;return t.loaded(i,"GAPI","OK"),a(r)}catch(e){return t.loaded(i,"GAPI","FAIL"),r(__e(e))}})}sync(t){return new Promise(async(a,r)=>{try{if(this.info&&this.info.version==t.version||"application/vnd.google-apps.folder"===t.mimeType)a(!0);else{await i.mkdirAll(["home://.gdv_cache","home://.gdv_cache/"+t.id],!0);let e=`home://.gdv_cache/${t.id}/${t.version}.${t.fullFileExtension}`.asFileHandle();try{await e.onready()}catch(a){await("home://.gdv_cache/"+t.id).asFileHandle().remove(),await i.mkdirAll(["home://.gdv_cache/"+t.id]);let r=await this.load(gapi.client.drive.files.get({fileId:t.id,alt:"media"}));if(!r.body)throw new Error(__("VFS cannot download file : {0}",this.path).__());e.cache=new Blob([r.body.asUint8Array()],{type:"octet/stream"}),await e.write(t.mimeType)}this.local_copy=e,a(!0)}}catch(t){e.announcer.oserror(t.toString(),t),r(__e(t))}})}meta(){return new Promise(async(t,i)=>{try{if(await this.oninit(),a[this.path]&&(this.gid=a[this.path].id),this.gid){let e=await this.load(gapi.client.drive.files.get({fileId:this.gid,fields:this.fields()}));if(!e.result)throw new Error(__("VFS cannot get meta data for {0}",this.gid).__());e.result.mime=e.result.mimeType,await this.load(this.sync(e.result)),t(e)}else{const e=this.parent().asFileHandle(),i=(await e.meta()).result;a[e.path]={id:i.id,mime:i.mimeType};let r=await this.load(gapi.client.drive.files.list({q:`name = '${this.basename}' and '${i.id}' in parents and trashed = false`,fields:`files(${this.fields()})`}));if(!(r.result.files&&r.result.files.length>0))throw new Error(__("VFS cannot get meta data for {0}",this.path).__());a[this.path]={id:r.result.files[0].id,mime:r.result.files[0].mimeType},r.result.files[0].mime=r.result.files[0].mimeType,this.gid=a[this.path].id,await this.load(this.sync(r.result.files[0])),t({result:r.result.files[0],error:!1})}}catch(t){e.announcer.oserror(t.toString(),t),i(__e(t))}})}oninit(){return new Promise(async(i,n)=>{const o=async function(e){if(e)return i(!0);a={"gdv://":{id:"root",mime:"dir"}};try{let e=await gapi.auth2.getAuthInstance().signIn();i(e)}catch(e){n(__e(e))}};try{if(!r.API_META)throw new Error(__("No GAPI meta found").__());if(t.libready(r.API_META.apilink))gapi.auth2.getAuthInstance().isSignedIn.listen(e=>o(e)),o(gapi.auth2.getAuthInstance().isSignedIn.get());else{if(await this.load(t.requires(r.API_META.apilink,!1)),await this.load(new Promise((e,t)=>{gapi.load("client:auth2",e)})),await this.load(gapi.client.init({apiKey:r.API_META.API_KEY,clientId:r.API_META.CLIENT_ID,discoveryDocs:r.API_META.DISCOVERY_DOCS,scope:r.API_META.SCOPES})),gapi.auth2.getAuthInstance().isSignedIn.listen(e=>o(e)),!await e.GUI.openDialog("YesNoDialog",{title:__("Authentication"),text:__("Would you like to login to GoogleDrive?")}))throw new Error(__("User abort the authentication").__());o(gapi.auth2.getAuthInstance().isSignedIn.get())}}catch(t){e.announcer.oserror(t.toString(),t),n(__e(t))}})}getlink(){if(this.local_copy)return this.local_copy.getlink()}child(e){if(this.isFolder())return`${this.path}/${e}`}_rd(t){return new Promise(async(i,r)=>{try{if(!this.info.id)throw new Error(__("File ID is not valid").__());if(this.isFolder()){let e=await this.load(gapi.client.drive.files.list({q:`'${this.info.id}' in parents and trashed = false`,fields:`files(${this.fields()})`}));if(!e.result.files)throw new Error(__("File {0} not found",this.info.id).__());for(let t of e.result.files)t.path=this.child(t.name),t.mime=t.mimeType,t.filename=t.name,t.type="file",t.gid=t.id,"application/vnd.google-apps.folder"===t.mimeType&&(t.mime="dir",t.type="dir",t.size=0),a[t.path]={id:t.gid,mime:t.mime};i({result:e.result.files,error:!1})}else{if(!this.local_copy)throw new Error(__("Cannot find local copy of file; {0}",this.path).__());i(await this.local_copy.read(t))}}catch(t){e.announcer.oserror(t.toString(),t),r(__e(t))}})}save(t,i){return new Promise(async(a,n)=>{try{const o=gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().access_token,s=new XMLHttpRequest,l=__(r.API_META.uploadlink,t).__();s.open("PATCH",l),s.setRequestHeader("Authorization","Bearer "+o),s.setRequestHeader("Content-Type",i),s.setRequestHeader("Content-Encoding","base64"),s.setRequestHeader("Content-Transfer-Encoding","base64");let d=t=>(e.announcer.oserror(__("VFS cannot save : {0}",this.path),t),n(t));s.onreadystatechange=()=>{if(4===s.readyState){if(200===s.status)return a({result:JSON.parse(s.responseText),error:!1});d(e.API.throwe("OS.VFS"))}},s.onerror=()=>d(e.API.throwe("OS.VFS"));let c=this.cache;"base64"!==i&&(c=await this.b64(i)),s.send(c.replace(/^data:[^;]+;base64,/g,"")),a(!0)}catch(e){n(__e(e))}})}_wr(t,i){return new Promise(async(i,r)=>{try{var n=void 0;if(a[this.path]&&(n=a[this.path].id),n)i(await this.load(this.save(n,t)));else{const e=this.parent().asFileHandle();await e.onready();const r={name:this.basename,mimeType:t,parents:[e.info.id]};let n=await this.load(gapi.client.drive.files.create({resource:r,fields:"id"}));if(!n||!n.result)throw new Error(__("VFS cannot write : {0}",this.path).__());a[this.path]={id:n.result.id,mime:t},i(this.load(this.save(n.result.id,t)))}}catch(t){e.announcer.oserror(t.toString(),t),r(__e(t))}})}_mk(t){return new Promise(async(i,r)=>{try{if(!this.isFolder())throw new Error(__("{0} is not a directory",this.path).__());var n={name:t,parents:[this.info.id],mimeType:"application/vnd.google-apps.folder"};let e=await this.load(gapi.client.drive.files.create({resource:n,fields:"id"}));if(!e||!e.result)throw new Error(__("VFS cannot create : {0}",t).__());a[this.child(t)]={id:e.result.id,mime:"dir"},i(e)}catch(t){e.announcer.oserror(t.toString(),t),r(__e(t))}})}_rm(){return new Promise(async(t,i)=>{try{if(!this.info.id)throw new Error(__("Cannot identify file id of {0}",this.path).__());if(!await this.load(gapi.client.drive.files.delete({fileId:this.info.id})))throw new Error(__("VFS cannot delete : {0}",this.path).__());a[this.path]=null,delete a[this.path],t({result:!0,error:!1})}catch(t){e.announcer.oserror(t.toString(),t),i(__e(t))}})}_mv(t){return new Promise(async(i,a)=>{try{var r=t.asFileHandle().parent().asFileHandle();await r.onready();const e=this.info.parents.join(",");let a=await this.load(gapi.client.drive.files.update({fileId:this.info.id,addParents:r.info.id,removeParents:e,fields:"id"}));if(!a)throw new Error(__("VFS cannot move : {0}",this.path).__());i(a)}catch(t){e.announcer.oserror(t.toString(),t),a(__e(t))}})}_up(){return new Promise(async(t,i)=>{try{if(!this.isFolder())throw new Error(__("Target file should be a folder").__());var a=$("").attr("type","file").css("display","none");a.on("change",async()=>{const e=a[0].files[0],t=this.child(e.name).asFileHandle();return t.cache=e,await this.load(t.write(e.type)),a.remove()}),a.trigger("click")}catch(t){e.announcer.oserror(t.toString(),t),i(__e(t))}})}_down(){return new Promise(async(t,i)=>{try{let i=await this.load(gapi.client.drive.files.get({fileId:this.info.id,alt:"media"}));if(!i.body)throw new Error(__("VFS cannot download file : {0}",this.path).__());let a=[];for(let e=0,t=i.body.length-1,r=0<=t;r?e<=t:e>=t;r?e++:e--)a.push(i.body.charCodeAt(e));let r=new Uint8Array(a);const n=new Blob([r],{type:"octet/stream"});e.API.saveblob(this.basename,n),t(!0)}catch(t){e.announcer.oserror(t.toString(),t),i(__e(t))}})}}i.GoogleDriveHandle=r,r.API_META={CLIENT_ID:"1006507170703-l322pfkrhf9cgta4l4jh2p8ughtc14id.apps.googleusercontent.com",API_KEY:"AIzaSyBZhM5KbARvT10acWC8JQKlRn2WbSsmfLc",apilink:"https://apis.google.com/js/api.js",DISCOVERY_DOCS:["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],SCOPES:"https://www.googleapis.com/auth/drive",uploadlink:"https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=media",logout:"https://www.google.com/accounts/Logout"},i.register("^gdv$",r),t.onsearch("Google Drive",(function(e){const t=[],i=new RegExp(e,"i");for(let e in a){const r=a[e];if(e.match(i)||r&&r.mime.match(i)){const i=e.asFileHandle();i.text=i.basename,i.mime=r.mime,i.iconclass="fa fa-file","dir"===i.mime&&(i.iconclass="fa fa-folder"),i.complex=!0,i.detail=[{text:i.path}],t.push(i)}}return t})),e.onexit("cleanUpGoogleDrive",(function(){return new Promise(async(e,t)=>{try{if(await"home://.gdv_cache".asFileHandle().remove(),a={"gdv://":{id:"root",mime:"dir"}},!Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink))return e(!0);const t=gapi.auth2.getAuthInstance();if(!t)throw new Error(__("Unable to get OATH instance").__());t.isSignedIn.get()&&$("",{src:r.API_META.logout,frameborder:0,onload:()=>t.disconnect()}),e(!0)}catch(t){console.log(t),e(!0)}})}))}(i=t.VFS||(t.VFS={}))}(t=e.API||(e.API={}))}(OS||(OS={}));
\ No newline at end of file
diff --git a/vfsx/build/release/vfsx.zip b/vfsx/build/release/vfsx.zip
new file mode 100644
index 0000000..1ffc065
Binary files /dev/null and b/vfsx/build/release/vfsx.zip differ
diff --git a/vfsx/gdv.ts b/vfsx/gdv.ts
new file mode 100644
index 0000000..954d9d1
--- /dev/null
+++ b/vfsx/gdv.ts
@@ -0,0 +1,619 @@
+namespace OS {
+ export namespace API {
+ export namespace VFS {
+ declare var gapi: any;
+
+ interface GAPITYPE {
+ CLIENT_ID: string;
+ API_KEY: string;
+ apilink: string;
+ DISCOVERY_DOCS: string[];
+ SCOPES: string;
+ uploadlink: string; //
+ logout: string;
+ };
+ let G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
+
+ export class GoogleDriveHandle extends BaseFileHandle {
+ private gid: string;
+ static API_META: GAPITYPE;
+ private local_copy: BaseFileHandle;
+ constructor(path: string) {
+ super(path);
+ if (!GoogleDriveHandle.API_META) {
+ OS.announcer.oserror( __("Unknown API setting for GAPI"),
+ OS.API.throwe("OS.VFS"));
+ return undefined;
+ }
+ if (this.isRoot()) {
+ this.gid = 'root';
+ }
+ this.cache = "";
+ this.local_copy = undefined;
+ }
+ private fields(): string {
+ return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size, version";
+ }
+ private isFolder(): boolean {
+ return this.info.mimeType === "application/vnd.google-apps.folder";
+ }
+ private load(promise: Promise): Promise {
+ const q = API.mid();
+ return new Promise(async (resolve, reject) => {
+ API.loading(q, "GAPI");
+ try {
+ let ret = await promise;
+ API.loaded(q, "GAPI", "OK");
+ return resolve(ret);
+ } catch (e) {
+ API.loaded(q, "GAPI", "FAIL");
+ return reject(__e(e));
+ }
+ });
+ }
+ private sync(meta_data?: GenericObject): Promise
+ {
+ return new Promise(async (resolve, reject) => {
+ try{
+ if((!this.info || this.info.version != meta_data.version) && meta_data.mimeType !== "application/vnd.google-apps.folder")
+ {
+ await VFS.mkdirAll([
+ "home://.gdv_cache",
+ `home://.gdv_cache/${meta_data.id}`
+ ], true);
+ let copy = `home://.gdv_cache/${meta_data.id}/${meta_data.version}.${meta_data.fullFileExtension}`.asFileHandle();
+ try
+ {
+ let r = await copy.onready();
+ }
+ catch(e1)
+ {
+ await `home://.gdv_cache/${meta_data.id}`.asFileHandle().remove();
+ await VFS.mkdirAll([`home://.gdv_cache/${meta_data.id}`]);
+ let r = await this.load(gapi.client.drive.files.get({
+ fileId: meta_data.id,
+ alt: 'media'
+ }));
+ if (!r.body) {
+ throw new Error(__("VFS cannot download file : {0}", this.path).__());
+ }
+
+ copy.cache = new Blob([r.body.asUint8Array()], { type: "octet/stream" });
+ await copy.write(meta_data.mimeType);
+ }
+ this.local_copy = copy;
+ resolve(true);
+ }
+ else
+ {
+ resolve(true);
+ }
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+ meta(): Promise {
+ return new Promise(async (resolve, reject) => {
+ try{
+ await this.oninit();
+ if (G_CACHE[this.path]) { this.gid = G_CACHE[this.path].id; };
+ if(this.gid)
+ {
+ let ret = await this.load(gapi.client.drive.files.get({
+ fileId: this.gid,
+ fields: this.fields()
+ }));
+ if (ret.result) {
+ ret.result.mime = ret.result.mimeType;
+ await this.load(this.sync(ret.result));
+ resolve(ret);
+ }
+ else
+ {
+ throw new Error(__("VFS cannot get meta data for {0}", this.gid).__());
+ }
+ }
+ else
+ {
+ const fp = this.parent().asFileHandle();
+ let d = await fp.meta();
+ const file: any = d.result;
+ G_CACHE[fp.path] = { id: file.id, mime: file.mimeType };
+ let r = await this.load(gapi.client.drive.files.list({
+ q: `name = '${this.basename}' and '${file.id}' in parents and trashed = false`,
+ fields: `files(${this.fields()})`
+ }));
+ if (!r.result.files || !(r.result.files.length > 0)) {
+ throw new Error(__("VFS cannot get meta data for {0}", this.path).__());
+ }
+ else
+ {
+ G_CACHE[this.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType };
+ r.result.files[0].mime = r.result.files[0].mimeType;
+ this.gid = G_CACHE[this.path].id;
+ await this.load(this.sync(r.result.files[0]));
+ resolve({ result: r.result.files[0], error: false});
+ }
+
+ }
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ private oninit(): Promise {
+ return new Promise(async (resolve, reject) => {
+ const fn = async function(r: boolean) {
+ if (r) { return resolve(true); }
+ // perform the login
+ G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
+ try {
+ let ret = await gapi.auth2.getAuthInstance().signIn();
+ resolve(ret);
+ }
+ catch(e)
+ {
+ reject(__e(e));
+ }
+ };
+ try {
+ if(!GoogleDriveHandle.API_META)
+ {
+ throw new Error(__("No GAPI meta found").__());
+ }
+ if(!API.libready(GoogleDriveHandle.API_META.apilink))
+ {
+ await this.load(API.requires(GoogleDriveHandle.API_META.apilink, false));
+ // load the api
+ await this.load(new Promise((res,rej) => {
+ gapi.load('client:auth2', res);
+ }));
+ await this.load(gapi.client.init({
+ apiKey: GoogleDriveHandle.API_META.API_KEY,
+ clientId: GoogleDriveHandle.API_META.CLIENT_ID,
+ discoveryDocs: GoogleDriveHandle.API_META.DISCOVERY_DOCS,
+ scope: GoogleDriveHandle.API_META.SCOPES
+ }));
+ gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
+ let ret = await GUI.openDialog("YesNoDialog", {
+ title: __("Authentication"),
+ text: __("Would you like to login to GoogleDrive?")
+ });
+ if(!ret)
+ {
+ throw new Error(__("User abort the authentication").__());
+ }
+ else
+ {
+ fn(gapi.auth2.getAuthInstance().isSignedIn.get());
+ }
+ }
+ else
+ {
+ gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
+ fn(gapi.auth2.getAuthInstance().isSignedIn.get());
+ }
+ }
+ catch (e) {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ })
+ }
+
+ getlink() {
+ if (this.local_copy) { return this.local_copy.getlink(); }
+ return undefined;
+ }
+
+ private child(name: string): string
+ {
+ if(this.isFolder())
+ return `${this.path}/${name}`
+ return undefined
+ }
+
+ /**
+ * Low level protocol-specific read operation
+ *
+ * @protected
+ * @param {string} t data type, see [[read]]
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _rd(t: string): Promise {
+ return new Promise(async (resolve, reject) => {
+ try{
+ if(!this.info.id)
+ {
+ throw new Error(__("File ID is not valid").__());
+ }
+ if(this.isFolder())
+ {
+ let r = await this.load(gapi.client.drive.files.list({
+ q: `'${this.info.id}' in parents and trashed = false`,
+ fields: `files(${this.fields()})`
+ }));
+ if(!r.result.files)
+ {
+ throw new Error(__("File {0} not found", this.info.id).__());
+ }
+ for (let file of r.result.files) {
+ file.path = this.child(file.name);
+ file.mime = file.mimeType;
+ file.filename = file.name;
+ file.type = "file";
+ file.gid = file.id;
+ if (file.mimeType === "application/vnd.google-apps.folder") {
+ file.mime = "dir";
+ file.type = "dir";
+ file.size = 0;
+ }
+ G_CACHE[file.path] = { id: file.gid, mime: file.mime };
+ }
+ resolve({ result: r.result.files, error: false});
+ }
+ else
+ {
+ if(!this.local_copy)
+ {
+ throw new Error(__("Cannot find local copy of file; {0}", this.path).__());
+ }
+ /*
+ let r = await this.load(gapi.client.drive.files.get({
+ fileId: this.info.id,
+ alt: 'media'
+ }));
+ if (t !== "binary") {
+ resolve(r.body);
+ }
+ else
+ {
+ resolve(r.body.asUint8Array());
+ }*/
+ let r = await this.local_copy.read(t);
+ resolve(r);
+ }
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ private save(gid: string, t: string): Promise
+ {
+ return new Promise(async (resolve, reject) => {
+ try
+ {
+ const user = gapi.auth2.getAuthInstance().currentUser.get();
+ const oauthToken = user.getAuthResponse().access_token;
+ const xhr = new XMLHttpRequest();
+ const url = __(GoogleDriveHandle.API_META.uploadlink,gid).__();
+ xhr.open('PATCH', url);
+ xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
+ xhr.setRequestHeader('Content-Type', t);
+ xhr.setRequestHeader('Content-Encoding', 'base64');
+ xhr.setRequestHeader('Content-Transfer-Encoding', 'base64');
+ let error = (e:Error) => {
+ OS.announcer.oserror(__("VFS cannot save : {0}", this.path), e);
+ return reject(e);
+ };
+ xhr.onreadystatechange = () => {
+ if ( xhr.readyState === 4 ) {
+ if ( xhr.status === 200 ) {
+ return resolve({ result: JSON.parse(xhr.responseText), error: false});
+ } else {
+ error(OS.API.throwe("OS.VFS"));
+ }
+ }
+ };
+ xhr.onerror = () => error(OS.API.throwe("OS.VFS"));
+ let data = this.cache;
+ if (t !== "base64") {
+ data = await this.b64(t);
+ }
+ xhr.send(data.replace(/^data:[^;]+;base64,/g, ""));
+ resolve(true);
+ }
+ catch(e)
+ {
+ reject(__e(e));
+ }
+ });
+ }
+ /**
+ * Low level protocol-specific write operation
+ *
+ * @protected
+ * @param {string} t data type, see [[write]]
+ * @param {*} [d]
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _wr(t: string, d?: any): Promise {
+ return new Promise(async (resolve, reject) => {
+ try{
+ var gid = undefined;
+ if (G_CACHE[this.path]) {
+ gid = G_CACHE[this.path].id;
+ }
+ if (gid) {
+ resolve(await this.load(this.save(gid, t)));
+ }
+ else
+ {
+ const dir = this.parent().asFileHandle();
+ await dir.onready();
+ const meta = {
+ name: this.basename,
+ mimeType: t,
+ parents: [dir.info.id]
+ };
+
+ let r = await this.load(gapi.client.drive.files.create({
+ resource: meta,
+ fields: 'id'
+ }));
+ if (!r || !r.result) {
+ throw new Error(__("VFS cannot write : {0}", this.path).__());
+ }
+ G_CACHE[this.path] = { id: r.result.id, mime: t };
+ resolve(this.load(this.save(r.result.id, t)));
+ }
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ /**
+ * Low level protocol-specific sub-directory creation
+ *
+ * @protected
+ * @param {string} d sub directory name
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _mk(d: string): Promise {
+ return new Promise(async (resolve, reject) => {
+ try
+ {
+ if (!this.isFolder()) {
+ throw new Error(__("{0} is not a directory", this.path).__());
+ }
+ var meta = {
+ name: d,
+ parents: [this.info.id],
+ mimeType: 'application/vnd.google-apps.folder'
+ };
+ let r = await this.load(gapi.client.drive.files.create({
+ resource: meta,
+ fields: 'id'
+ }));
+ if (!r || !r.result) {
+ throw new Error(__("VFS cannot create : {0}", d).__());
+ }
+ G_CACHE[this.child(d)] = { id: r.result.id, mime: "dir" };
+ resolve(r);
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+ /**
+ * Low level protocol-specific delete operation
+ *
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _rm(): Promise {
+ return new Promise(async (resolve, reject) => {
+ try{
+ if (!this.info.id) {
+ throw new Error(__("Cannot identify file id of {0}", this.path).__());
+ }
+ let r = await this.load(gapi.client.drive.files.delete({
+ fileId: this.info.id
+ }));
+ if (!r) {
+ throw new Error(__("VFS cannot delete : {0}", this.path).__());
+ }
+ G_CACHE[this.path] = null;
+ delete G_CACHE[this.path];
+ resolve({ result: true, error: false});
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ /**
+ * Low level protocol-specific move operation
+ *
+ * @protected
+ * @param {string} d
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _mv(d: string): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ var dest = d.asFileHandle().parent().asFileHandle();
+ await dest.onready();
+ const previousParents = this.info.parents.join(',');
+ let r = await this.load(gapi.client.drive.files.update({
+ fileId: this.info.id,
+ addParents: dest.info.id,
+ removeParents: previousParents,
+ fields: "id"
+ }));
+ if (!r) {
+ throw new Error(__("VFS cannot move : {0}", this.path).__());
+ }
+ resolve(r);
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ /**
+ * Low level protocol-specific upload operation
+ *
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _up(): Promise {
+ return new Promise(async (resolve, reject) => {
+ try{
+ if (!this.isFolder()) {
+ throw new Error(__("Target file should be a folder").__());
+ }
+ var o = ($('')).attr('type', 'file').css("display", "none");
+ o.on("change", async () => {
+ //Ant.OS.API.loading q, p
+ const fo = (o[0] as HTMLInputElement).files[0];
+ const file = (this.child(fo.name)).asFileHandle();
+ file.cache = fo;
+ let ret = await this.load(file.write(fo.type));
+ return o.remove();
+ resolve(ret);
+ });
+ o.trigger("click");
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+
+ /**
+ * Low level protocol-specific download operation
+ *
+ * @returns {Promise}
+ * @memberof BaseFileHandle
+ */
+ protected _down(): Promise {
+ return new Promise(async (resolve,reject) => {
+ try {
+ let r = await this.load(gapi.client.drive.files.get({
+ fileId: this.info.id,
+ alt: 'media'
+ }));
+ if (!r.body) {
+ throw new Error(__("VFS cannot download file : {0}", this.path).__());
+ }
+ let bs = [];
+ for (let i = 0, end = r.body.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
+ bs.push(r.body.charCodeAt(i));
+ }
+ let bytes = new Uint8Array(bs);
+ const blob = new Blob([bytes], { type: "octet/stream" });
+ OS.API.saveblob(this.basename, blob);
+ resolve(true);
+ }
+ catch(e)
+ {
+ OS.announcer.oserror(e.toString(), e);
+ reject(__e(e));
+ }
+ });
+ }
+ }
+
+ GoogleDriveHandle.API_META = {
+ CLIENT_ID: "1006507170703-l322pfkrhf9cgta4l4jh2p8ughtc14id.apps.googleusercontent.com",
+ API_KEY: "AIzaSyBZhM5KbARvT10acWC8JQKlRn2WbSsmfLc",
+ apilink: "https://apis.google.com/js/api.js",
+ DISCOVERY_DOCS: [
+ "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
+ ],
+ SCOPES: "https://www.googleapis.com/auth/drive",
+ uploadlink: "https://www.googleapis.com/upload/drive/v3/files/{0}?uploadType=media",
+ logout: "https://www.google.com/accounts/Logout"
+ };
+
+ register("^gdv$", GoogleDriveHandle);
+
+ API.onsearch("Google Drive", function(t) {
+ const arr = [];
+ const term = new RegExp(t, "i");
+ for (let k in G_CACHE) {
+ const v = G_CACHE[k];
+ if ((k.match(term)) || (v && v.mime.match(term))) {
+ const file = k.asFileHandle() as any;
+ file.text = file.basename;
+ file.mime = v.mime;
+ file.iconclass = "fa fa-file";
+ if (file.mime === "dir") { file.iconclass = "fa fa-folder"; }
+ file.complex = true;
+ file.detail = [{ text: file.path }];
+ arr.push(file);
+ }
+ }
+ return arr;
+ });
+
+ /**
+ * FIXME: proper way to logout
+ */
+ OS.onexit("cleanUpGoogleDrive", function() {
+ return new Promise(async (resolve, reject) =>{
+ try{
+ await "home://.gdv_cache".asFileHandle().remove();
+ G_CACHE = { "gdv://": { id: "root", mime: 'dir' } };
+ if (!Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink))
+ {
+ return resolve(true);
+ }
+ const auth2 = gapi.auth2.getAuthInstance();
+ if (!auth2) {
+ throw new Error(__("Unable to get OATH instance").__());
+ }
+ if (auth2.isSignedIn.get()) {
+ $('', {
+ src: GoogleDriveHandle.API_META.logout,
+ frameborder: 0,
+ onload() {
+ //console.log("disconnect")
+ return auth2.disconnect();
+ }
+ //$(this).remove()
+ });
+ }
+ resolve(true);
+ }
+ catch(e){
+ console.log(e);
+ resolve(true);
+ }
+ });
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/vfsx/package.json b/vfsx/package.json
new file mode 100644
index 0000000..c1c26c1
--- /dev/null
+++ b/vfsx/package.json
@@ -0,0 +1,39 @@
+{
+ "pkgname": "vfsx",
+ "name": "AntOS VFS handles",
+ "description": "AntOS VFS handles for various file protocols which are not included by default int core release",
+ "info": {
+ "author": "Dany LE",
+ "email": "mrsang@iohub.dev"
+ },
+ "version": "0.1.0-b",
+ "category": "Library",
+ "iconclass": "fa fa-cog",
+ "mimes": [
+ "none"
+ ],
+ "dependencies": [],
+ "locale": {},
+ "locales": {
+ "en_GB": {
+ "Unknown API setting for GAPI": "Unknown API setting for GAPI",
+ "VFS cannot download file : {0}": "VFS cannot download file : {0}",
+ "VFS cannot get meta data for {0}": "VFS cannot get meta data for {0}",
+ "No GAPI meta found": "No GAPI meta found",
+ "Authentication": "Authentication",
+ "Would you like to login to GoogleDrive?": "Would you like to login to GoogleDrive?",
+ "User abort the authentication": "User abort the authentication",
+ "File ID is not valid": "File ID is not valid",
+ "File {0} not found": "File {0} not found",
+ "Cannot find local copy of file; {0}": "Cannot find local copy of file; {0}",
+ "VFS cannot save : {0}": "VFS cannot save : {0}",
+ "VFS cannot write : {0}": "VFS cannot write : {0}",
+ "{0} is not a directory": "{0} is not a directory",
+ "VFS cannot create : {0}": "VFS cannot create : {0}",
+ "Cannot identify file id of {0}": "Cannot identify file id of {0}",
+ "VFS cannot delete : {0}": "VFS cannot delete : {0}",
+ "VFS cannot move : {0}": "VFS cannot move : {0}",
+ "Target file should be a folder": "Target file should be a folder"
+ }
+ }
+}
\ No newline at end of file