"use strict"; import { http_get, http_post, prepare, basename, dirname, pathBuilder } from '../helpers/'; import Path from 'path'; import { Observable } from 'rxjs/Observable'; import { cache } from '../helpers/'; class FileSystem{ constructor(){ this.obs = null; this.current_path = null; } ls(path, internal = false){ this.current_path = path; this.obs && this.obs.complete(); return Observable.create((obs) => { this.obs = obs; let keep_pulling_from_http = false; this._ls_from_cache(path, true) .then(() => { const fetch_from_http = (_path) => { return this._ls_from_http(_path) .then(() => new Promise((done, err) => { window.setTimeout(() => done(), 2000); })) .then(() => { if(keep_pulling_from_http === false) return Promise.resolve(); return fetch_from_http(_path); }) .catch(() => {}); }; fetch_from_http(path); }); return () => { keep_pulling_from_http = false; }; }); } _ls_from_http(path){ const url = '/api/files/ls?path='+prepare(path); return http_get(url).then((response) => { return cache.upsert(cache.FILE_PATH, path, (_files) => { let store = Object.assign({ path: path, results: null, access_count: 0, }, _files); store.access_count += 1; store.results = response.results; if(_files && _files.results){ // find out which entry we want to keep from the cache let _files_virtual_to_keep = _files.results.filter((file) => { return file.icon === 'loading'; }); // update file results when something is going on for(let i=0; i<_files_virtual_to_keep.length; i++){ for(let j=0; j e); store.results = store.results.concat(_files_virtual_to_keep); } if(this.current_path === path){ this.obs && this.obs.next({status: 'ok', results: store.results}); } store.last_update = new Date(); store.last_access = new Date(); return store; }); }).catch((_err) => { this.obs.next(_err); return Promise.reject(); }); } _ls_from_cache(path, _record_access = false){ if(_record_access === false){ return cache.get(cache.FILE_PATH, path).then((response) => { if(!response || !response.results) return null; if(this.current_path === path){ this.obs && this.obs.next({status: 'ok', results: response.results}); } return Promise.resolve(); }); }else{ return cache.upsert(cache.FILE_PATH, path, (response) => { if(!response || !response.results) return null; if(this.current_path === path){ this.obs && this.obs.next({status: 'ok', results: response.results}); } response.last_access = new Date(); response.access_count += 1; return response; }); } } rm(path){ const url = '/api/files/rm?path='+prepare(path); return this._replace(path, 'loading') .then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res)) .then(() => http_get(url)) .then((res) => { return cache.remove(cache.FILE_CONTENT, path) .then(cache.remove(cache.FILE_CONTENT, path, false)) .then(cache.remove(cache.FILE_PATH, dirname(path), false)) .then(this._remove(path, 'loading')) .then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res)) }) .catch((err) => { return this._replace(path, 'error', 'loading') .then((res) => this.current_path === dirname(path) ? this._ls_from_cache(dirname(path)) : Promise.resolve(res)) .then(() => Promise.reject(err)); }); } cat(path){ const url = '/api/files/cat?path='+prepare(path); return http_get(url, 'raw') .then((res) => { if(this.is_binary(res) === true){ return Promise.reject({code: 'BINARY_FILE'}); } return cache.upsert(cache.FILE_CONTENT, path, (response) => { let file = response? response : { path: path, last_update: null, last_access: null, access_count: -1, result: null }; file.result = res; file.access_count += 1; file.last_access = new Date(); return file; }).then((response) => Promise.resolve(response.result)); }) .catch((err) => { console.log("ERROR"); if(err.code === 'BINARY_FILE') return Promise.reject(err); return cache.update(cache.FILE_CONTENT, path, (response) => { response.last_access = new Date(); response.access_count += 1; return response; }).then((response) => { if(!response || !response.result) return Promise.reject(err); return Promise.resolve(response.result); }); }); } url(path){ const url = '/api/files/cat?path='+prepare(path); return Promise.resolve(url); } save(path, file){ const url = '/api/files/cat?path='+prepare(path); let formData = new window.FormData(); formData.append('file', file); return this._replace(path, 'loading') .then(() => http_post(url, formData, 'multipart')) .then(() => { return this._saveFileToCache(path, file) .then(() => this._replace(path, null, 'loading')) .then(() => this._refresh(path)) }) .catch((err) => { return this._replace(path, 'error', 'loading') .then(() => this._refresh(path)) .then(() => Promise.reject(err)) }); } mkdir(path){ const url = '/api/files/mkdir?path='+prepare(path), origin_path = pathBuilder(this.current_path, basename(path), 'directoy'), destination_path = path; return this._add(destination_path, 'loading') .then(() => origin_path !== destination_path ? this._add(origin_path, 'loading') : Promise.resolve()) .then(() => this._refresh(origin_path, destination_path)) .then(() => http_get(url)) .then(() => { return this._replace(destination_path, null, 'loading') .then(() => origin_path !== destination_path ? this._remove(origin_path, 'loading') : Promise.resolve()) .then(() => cache.add(cache.FILE_PATH, destination_path, { path: destination_path, results: [], access_count: 0, last_access: null, last_update: new Date() })) .then(() => this._refresh(origin_path, destination_path)); }) .catch((err) => { this._replace(origin_path, 'error', 'loading') .then(() => origin_path !== destination_path ? this._remove(destination_path, 'loading') : Promise.resolve()) .then(() => this._refresh(origin_path, destination_path)); return Promise.reject(err); }); } touch(path, file){ const origin_path = pathBuilder(this.current_path, basename(path), 'file'), destination_path = path; return this._add(destination_path, 'loading') .then(() => origin_path !== destination_path ? this._add(origin_path, 'loading') : Promise.resolve()) .then(() => this._refresh(origin_path, destination_path)) .then(() => { if(file){ const url = '/api/files/cat?path='+prepare(path); let formData = new window.FormData(); formData.append('file', file); return http_post(url, formData, 'multipart'); }else{ const url = '/api/files/touch?path='+prepare(path); return http_get(url); } }) .then(() => { this._saveFileToCache(path, file) .then(() => this._replace(destination_path, null, 'loading')) .then(() => origin_path !== destination_path ? this._remove(origin_path, 'loading') : Promise.resolve()) .then(() => this._refresh(origin_path, destination_path)) }) .catch((err) => { this._replace(origin_path, 'error', 'loading') .then(() => origin_path !== destination_path ? this._remove(destination_path, 'loading') : Promise.resolve()) .then(() => this._refresh(origin_path, destination_path)); return Promise.reject(err); }); } mv(from, to){ const url = '/api/files/mv?from='+prepare(from)+"&to="+prepare(to), origin_path = from, destination_path = to; return this._replace(origin_path, 'loading') .then(this._add(destination_path, 'loading')) .then(() => this._refresh(origin_path, destination_path)) .then(() => http_get(url)) .then((res) => { return this._remove(origin_path, 'loading') .then(() => this._replace(destination_path, null, 'loading')) .then(() => this._refresh(origin_path, destination_path)) .then(() => { cache.update(cache.FILE_PATH, origin_path, (data) => { data.path = data.path.replace(origin_path, destination_path); return data; }, false); cache.update(cache.FILE_CONTENT, origin_path, (data) => { data.path = data.path.replace(origin_path, destination_path); return data; }, false); return Promise.resolve(); }); }) .catch((err) => { this._replace(origin_path, 'error', 'loading') .then(() => this._remove(destination_path, 'loading')) .then(() => this._refresh(origin_path, destination_path)) return Promise.reject(err); }); } frequents(){ let data = []; return cache.fetchAll((value) => { if(value.access_count >= 1 && value.path !== "/"){ data.push(value); } }).then(() => { return Promise.resolve( data .sort((a,b) => a.access_count > b.access_count? -1 : 1) .map((a) => a.path) .slice(0,6) ); }); } _saveFileToCache(path, file){ if(!file) return update_cache(""); return new Promise((done, err) => { const reader = new FileReader(); reader.readAsText(file); reader.onload = () => this.is_binary(reader.result) === false? update_cache(reader.result).then(done) : done(); reader.onerror = (err) => err(err); }); function update_cache(result){ return cache.upsert(cache.FILE_CONTENT, path, (response) => { if(!response) response = { path: path, last_access: null, last_update: null, result: null, access_count: 0 }; response.last_update = new Date(); response.result = result; return response; }); } } _refresh(origin_path, destination_path){ if(this.current_path === dirname(origin_path) || this.current_path === dirname(destination_path)){ return this._ls_from_cache(this.current_path); } return Promise.resolve(); } _replace(path, icon, icon_previous){ return cache.update(cache.FILE_PATH, dirname(path), function(res){ res.results = res.results.map((file) => { if(file.name === basename(path) && file.icon == icon_previous){ if(!icon){ delete file.icon; } if(icon){ file.icon = icon; } } return file; }); return res; }); } _add(path, icon){ return cache.upsert(cache.FILE_PATH, dirname(path), function(res){ if(!res || !res.results){ res = { path: dirname(path), results: [], access_count: 0, last_access: null, last_update: new Date() }; } let file = { name: basename(path), type: /\/$/.test(path) ? 'directory' : 'file' }; if(icon) file.icon = icon; res.results.push(file); return res; }); } _remove(path, previous_icon){ return cache.update(cache.FILE_PATH, dirname(path), function(res){ if(!res) return null; res.results = res.results.filter((file) => { return file.name === basename(path) && file.icon == previous_icon ? false : true; }); return res; }); } is_binary(str){ // Reference: https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character return /\ufffd/.test(str); } } export const Files = new FileSystem();