class Archive extends this.OS.application.BaseApplication constructor: ( args ) -> super "Archive", args @currfile = "Untitled".asFileHandle() if @args and @args.length > 0 and @args[0].path @currfile = args[0].path.asFileHandle() main: () -> @btadd = @find "btaradd" @btdel = @find "btardel" @btxtract = @find "btarxtract" @filetree = @find "filetree" @zip = undefined @bindKey "ALT-N", () => @fileMenuHandle "new" @bindKey "ALT-O", () => @fileMenuHandle "open" @bindKey "CTRL-S", () => @fileMenuHandle "save" @bindKey "ALT-S", () => @fileMenuHandle "saveas" @btxtract.onbtclick = (e) => item = @filetree.selectedItem return @notify __("Please select file/folder to extract") unless item treedata = item.data @openDialog "FileDialog", { title: __("Select a folder"), mimes: ["dir"] } .then (d) => @xtract(treedata, d.file.path) .then () => @notify __("extract successful: {0}", treedata.path) .catch (e) => @error e.toString(), e .catch (e) => @error e.toString(), e @btadd.onbtclick = (e) => @actionAdd() @btdel.onbtclick = (e) => @actionDel() @filetree.contextmenuHandle = (e, m) => item = @filetree.selectedItem return unless item mdata = [ { text: "__(Delete)", onmenuselect: () => @actionDel()}, { text: "__(Info)", onmenuselect: () => @actionInfo()} ] if item.data.type is "dir" mdata.unshift { text: "__(Add)", onmenuselect: () => @actionAdd() } m.items = mdata m.show(e) @openar(@currfile) actionAdd: () -> item = @filetree.selectedItem return @notify __("Please select a destination folder") unless item and item.data.type is "dir" treedata = item.data @openDialog "FileDialog", { title: __("Select a file/folder") } .then (d) => @addToZip d.file, "#{treedata.path}/#{d.file.path.asFileHandle().basename}" .then () => @currfile.dirty = true @refreshTreeFile() .catch (e) => @error e.toString(), e .catch (e) => @error e.toString(), e actionDel: () -> item = @filetree.selectedItem return @notify __("Please select a destination folder") unless item treedata = item.data return @notify __("You cannot delete the root node") if treedata.root @ask {title: "__(Delete)", text: __("Do you really want to delete: {0}?", treedata.text) } .then (d) => return unless d @zip.remove(treedata.path.trimBy("/")) @currfile.dirty = true @refreshTreeFile() .catch (e) => @error e.toString(), e actionInfo: () -> item = @filetree.selectedItem return @notify __("Please select a file/folder") unless item key = item.data.path.trimBy("/") key = "#{key}/" if item.data.type is "dir" meta = @zip.files[key] return @notify __("Cannot get entry meta data") unless meta @openDialog "InfoDialog", { title: "About: #{meta.name}", name: meta.name, date: meta.date, dir: meta.dir, dataBinary: meta._dataBinary, size: meta._data.uncompressedSize } openar: (file) -> @zip = undefined if file.filename is "Untitled" @zip = new JSZip() @currfile = file @refreshTreeFile() else # open the file and refresh filetree file.read("binary") .then (data) => JSZip.loadAsync(data) .then (zip) => @zip = zip @currfile = file @refreshTreeFile() .catch (e) => @error __("Wrong zip format: {0}", e.toString()), e .catch (e) => @error __("Unable to read archive: {0}", file.path), e refreshTreeFile: () -> return unless @zip treedata = { text: @currfile.filename.trimFromRight(".zip"), type: "dir" path: "", open: true, root: true, nodes: [] } for k,v of @zip.files leaf = @putFileInTree k.split("/"), treedata if not v.dir leaf.type = "file" delete leaf.nodes @filetree.data = treedata putFileInTree: (patharr, treedata) -> names = (v.text for v in treedata.nodes) rep = patharr.shift() return treedata unless rep if names.includes rep @putFileInTree patharr, treedata.nodes[names.indexOf rep] else subtree = { text: rep, path: "#{treedata.path}/#{rep}", type: "dir", nodes: [] } treedata.nodes.push subtree return @putFileInTree patharr, subtree xtract: (treedata, to) -> new Promise (resolve, reject) => if treedata.type is "file" @zip.file(treedata.path.trimBy("/")) .async("uint8array") .then (data) => fp = "#{to}/#{treedata.text}".asFileHandle() fp.cache = new Blob([data], { type: "octet/stream" }) fp.write() .then () -> resolve() .catch (e) -> reject __e e .catch (e) -> reject __e e else #make the dir before extract to.asFileHandle().mk treedata.text .then () => nodes = (v for v in treedata.nodes) @xtractall nodes, "#{to}/#{treedata.text}" .then -> resolve() .catch (e) -> reject __e e .catch (e) -> reject __e e xtractall: (list, to) -> new Promise (resolve, reject) => return resolve() if list.length is 0 el = list.shift() @xtract el, to .then () => @xtractall list, to .then () -> resolve() .catch (e) -> reject __e e .catch (e) -> reject __e e addToZip: (file, to) -> new Promise (resolve, reject) => if file.type is "dir" file.path.asFileHandle().read().then (data) => return reject __e @throwe data.error if data.error @addFilesTozip data.result, to .then () -> resolve() .catch (e) => reject __e e else file.path.asFileHandle() .read("binary") .then (data) => @zip.file(to.trimBy("/"), data, { binary: true }) resolve() .catch (e) -> resolve __e e addFilesTozip: (list, to) -> new Promise (resolve, reject) => return resolve() if list.length is 0 el = list.shift() @addToZip el, "#{to}/#{el.path.asFileHandle().basename}" .then () => @addFilesTozip list, to .then () -> resolve() .catch (e) -> reject __e e .catch (e) -> reject __e e saveZipAs: () -> @openDialog("FileDialog", { title: __("Save as"), file: @currfile }).then (f) => d = f.file.path.asFileHandle() d = d.parent() if f.file.type is "file" @currfile.setPath "#{d.path}/#{f.name}" @write() write: () -> return unless @zip and @currfile.path isnt "Untitled" @zip .generateAsync({ type: "base64" }) .then (data) => @currfile .setCache( "data:application/zip;base64," + data ) .write("base64") .then () => @currfile.dirty = false @refreshTreeFile() @notify __("zip file saved in {0}", @currfile.path) .catch (e) => @error __("Unable to save zip file: {0}", @currfile.path) fileMenuHandle:(id) -> switch id when "open" @openDialog "FileDialog", { title: __("Select a zip file"), mimes: ["application/zip"] } .then (d) => @openar(d.file.path.asFileHandle()) .catch (e) => @error e.toString(), e when "save" return @write() if @currfile.path isnt "Untitled" @saveZipAs() when "saveas" @saveZipAs() menu: () -> [ { text: "__(File)", nodes: [ { text: "__(New)", id:"new", shortcut: "A-N" }, { text: "__(Open)", id:"open", shortcut: "A-O"}, { text: "__(Save)", id:"save", shortcut: "C-S"}, { text: "__(Save as)", id:"saveas", shortcut: "A-S"} ], onchildselect: (e) => @fileMenuHandle e.data.item.data.id } ] cleanup: (e) -> return unless @currfile.dirty e.preventDefault() @ask { title: "__(Quit)", text: "__(Zip file has been modified. Quit without saving?)" } .then (d) => return unless d @currfile.dirty = false @quit() Archive.dependencies = [ "os://scripts/jszip.min.js" ] this.OS.register "Archive", Archive