feat(ftp): implement ftp browser, most things are functioning except archiving and some actions of multiple files
This commit is contained in:
parent
9aa5bcf384
commit
2eaf2ac1f0
1463
build/main.js
1463
build/main.js
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,9 @@
|
|||||||
"device-storage:music": {
|
"device-storage:music": {
|
||||||
"access": "readwrite",
|
"access": "readwrite",
|
||||||
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
||||||
|
},
|
||||||
|
"tcp-socket": {
|
||||||
|
"description": "FTP Browser: Used to connect to FTP servers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installs_allowed_from": [
|
"installs_allowed_from": [
|
||||||
|
14
src/js/api/auto.js
Normal file
14
src/js/api/auto.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as ftp from './ftp';
|
||||||
|
import * as files from './files';
|
||||||
|
|
||||||
|
['getFile', 'children', 'isDirectory', 'readFile', 'writeFile',
|
||||||
|
'createFile', 'createDirectory', 'remove', 'move', 'copy'].forEach(method => {
|
||||||
|
exports[method] = (...args) => {
|
||||||
|
return window.ftpMode ? ftp[method](...args) : files[method](...args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let CACHE = files.CACHE;
|
||||||
|
let FTP_CACHE = ftp.FTP_CACHE;
|
||||||
|
|
||||||
|
export { CACHE, FTP_CACHE };
|
@ -34,7 +34,14 @@ export async function getFile(dir = '/') {
|
|||||||
|
|
||||||
if (dir === '/' || !dir) return parent;
|
if (dir === '/' || !dir) return parent;
|
||||||
|
|
||||||
return await parent.get(normalize(dir));
|
let file = await parent.get(normalize(dir));
|
||||||
|
|
||||||
|
Object.defineProperty(file, 'type', {
|
||||||
|
value: type(file),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function children(dir, gatherInfo) {
|
export async function children(dir, gatherInfo) {
|
||||||
@ -46,9 +53,16 @@ export async function children(dir, gatherInfo) {
|
|||||||
}
|
}
|
||||||
let childs = await parent.getFilesAndDirectories();
|
let childs = await parent.getFilesAndDirectories();
|
||||||
|
|
||||||
|
for (let child of childs) {
|
||||||
|
Object.defineProperty(child, 'type', {
|
||||||
|
value: type(child),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (gatherInfo && !window.needsShim) {
|
if (gatherInfo && !window.needsShim) {
|
||||||
for (let child of childs) {
|
for (let child of childs) {
|
||||||
if (type(child) === 'Directory') {
|
if (child.type === 'Directory') {
|
||||||
let subchildren;
|
let subchildren;
|
||||||
try {
|
try {
|
||||||
subchildren = await shimDirectory(child).getFilesAndDirectories();
|
subchildren = await shimDirectory(child).getFilesAndDirectories();
|
||||||
|
267
src/js/api/ftp.js
Normal file
267
src/js/api/ftp.js
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import { refresh } from 'actions/files-view';
|
||||||
|
import { bind } from 'store';
|
||||||
|
import Eventconnection from 'events';
|
||||||
|
import { humanSize, reportError, normalize, type } from 'utils';
|
||||||
|
|
||||||
|
export let FTP_CACHE = {};
|
||||||
|
let socket;
|
||||||
|
let connection = new Eventconnection();
|
||||||
|
connection.setMaxListeners(99);
|
||||||
|
let wd = '';
|
||||||
|
let currentRequest;
|
||||||
|
let queue = 0;
|
||||||
|
|
||||||
|
export async function connect(properties = {}) {
|
||||||
|
let { host, port, username, password } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
socket = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
socket.ondata = e => {
|
||||||
|
console.log('<', e.data);
|
||||||
|
connection.emit('data', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = e => {
|
||||||
|
connection.emit('error', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = e => {
|
||||||
|
connection.emit('close', e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
socket.onopen = () => {
|
||||||
|
send(`USER ${username}`);
|
||||||
|
send(`PASS ${password}`);
|
||||||
|
resolve(socket);
|
||||||
|
|
||||||
|
window.ftpMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = reject;
|
||||||
|
socket.onclose = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disconnect() {
|
||||||
|
socket.close();
|
||||||
|
window.ftpMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listen(ev, fn) {
|
||||||
|
socket.listen(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function send(command, ...args) {
|
||||||
|
args = args.filter(arg => arg);
|
||||||
|
let cmd = command + (args.length ? ' ' : '') + args.join(' ');
|
||||||
|
|
||||||
|
console.log('>', cmd);
|
||||||
|
socket.send(cmd + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cwd(dir = '') {
|
||||||
|
send('CWD', dir);
|
||||||
|
wd = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PWD_REGEX = /257 "(.*)"/;
|
||||||
|
export async function pwd() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.on('data', function listener(data) {
|
||||||
|
if (data.indexOf('current directory') === -1) return;
|
||||||
|
let dir = data.match(PWD_REGEX)[1];
|
||||||
|
resolve(normalize(dir));
|
||||||
|
|
||||||
|
connection.removeListener('data', listener);
|
||||||
|
});
|
||||||
|
send('PWD');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pasv() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connection.on('data', function listener(data) {
|
||||||
|
if (data.indexOf('Passive') === -1) return;
|
||||||
|
|
||||||
|
// format: |||port|
|
||||||
|
let port = parseInt(data.match(/\|{3}(\d+)\|/)[1]);
|
||||||
|
|
||||||
|
connection.removeListener('data', listener);
|
||||||
|
|
||||||
|
return resolve(port);
|
||||||
|
});
|
||||||
|
|
||||||
|
send('EPSV');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIST_EXTRACTOR = /(.*?)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\d+:?\d+)+\s+(.*)/;
|
||||||
|
export async function list(dir = '') {
|
||||||
|
return pasv().then(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('LIST', dir);
|
||||||
|
|
||||||
|
return data.then(items => {
|
||||||
|
return items.split('\n').map(item => {
|
||||||
|
if (item.indexOf('total') > -1 || !item) return;
|
||||||
|
|
||||||
|
let match = item.match(LIST_EXTRACTOR);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: normalize(wd) + '/',
|
||||||
|
type: match[1][0] === 'd' ? 'Directory' : 'File',
|
||||||
|
permissions: match[1].slice(1),
|
||||||
|
links: +match[2],
|
||||||
|
owner: match[3],
|
||||||
|
group: match[4],
|
||||||
|
size: +match[5],
|
||||||
|
lastModification: {
|
||||||
|
month: match[6],
|
||||||
|
day: match[7],
|
||||||
|
time: match[8]
|
||||||
|
},
|
||||||
|
name: match[9]
|
||||||
|
}
|
||||||
|
}).filter(item => item);
|
||||||
|
}, reportError)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function namelist(dir = '') {
|
||||||
|
return pasv().then(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('NLST', dir);
|
||||||
|
|
||||||
|
return data.then(names => names.split('\n'), reportError);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function secondary(properties = {}) {
|
||||||
|
let { host, port } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let alt = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
alt.onopen = () => {
|
||||||
|
let data = new Promise((resolve, reject) => {
|
||||||
|
alt.ondata = e => {
|
||||||
|
resolve(e.data);
|
||||||
|
}
|
||||||
|
alt.onerror = e => {
|
||||||
|
reject(e.data);
|
||||||
|
}
|
||||||
|
alt.onclose = e => {
|
||||||
|
resolve('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve({data});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function secondaryWrite(properties = {}, content) {
|
||||||
|
let { host, port } = properties;
|
||||||
|
|
||||||
|
let url = encodeURI(host);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let alt = navigator.mozTCPSocket.open(url, port);
|
||||||
|
|
||||||
|
alt.onopen = () => {
|
||||||
|
alt.send(content);
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
alt.close();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
alt.onclose = () => {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function children(dir = '', gatherInfo) {
|
||||||
|
dir = normalize(dir);
|
||||||
|
if (FTP_CACHE[dir]) return FTP_CACHE[dir];
|
||||||
|
|
||||||
|
let childs = gatherInfo ? await list(dir) : await namelist();
|
||||||
|
|
||||||
|
FTP_CACHE[dir] = childs;
|
||||||
|
|
||||||
|
return childs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFile(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
let ls = await list(path);
|
||||||
|
|
||||||
|
return ls[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isDirectory(path = '') {
|
||||||
|
return (await getFile(path)).type === 'Directory';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readFile(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
return pasv().then(port => {
|
||||||
|
return secondary({ host: socket.host, port }).then(({data}) => {
|
||||||
|
send('RETR', path);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}).catch(reportError);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeFile(path = '', content) {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
return pasv().then(port => {
|
||||||
|
send('STOR', path);
|
||||||
|
return secondaryWrite({ host: socket.host, port }, content).then(() => {
|
||||||
|
})
|
||||||
|
}).catch(reportError);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createFile(path = '') {
|
||||||
|
return writeFile(path, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDirectory(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
send('MKD', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(path = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
|
||||||
|
send('RMD', path);
|
||||||
|
send('DELE', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function move(path = '', newPath = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
newPath = normalize(newPath);
|
||||||
|
|
||||||
|
send('RNFR', path);
|
||||||
|
send('RNTO', newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copy(path = '', newPath = '') {
|
||||||
|
path = normalize(path);
|
||||||
|
newPath = normalize(newPath);
|
||||||
|
|
||||||
|
let content = await readFile(path);
|
||||||
|
|
||||||
|
return writeFile(newPath, content);
|
||||||
|
}
|
@ -20,7 +20,7 @@ export default class FileList extends Component {
|
|||||||
|
|
||||||
let els = files.map((file, index) => {
|
let els = files.map((file, index) => {
|
||||||
let selected = activeFile.indexOf(file) > -1;
|
let selected = activeFile.indexOf(file) > -1;
|
||||||
if (type(file) === 'File') {
|
if (file.type === 'File') {
|
||||||
return <File selectView={selectView} selected={selected} key={index} index={index} name={file.name} size={file.size} type={file.type} />;
|
return <File selectView={selectView} selected={selected} key={index} index={index} name={file.name} size={file.size} type={file.type} />;
|
||||||
} else {
|
} else {
|
||||||
return <Directory selectView={selectView} selected={selected} key={index} index={index} name={file.name} children={file.children} type={file.type} />
|
return <Directory selectView={selectView} selected={selected} key={index} index={index} name={file.name} children={file.children} type={file.type} />
|
||||||
@ -59,10 +59,3 @@ function props(state) {
|
|||||||
view: state.get('settings').view || 'list'
|
view: state.get('settings').view || 'list'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFiles(dir) {
|
|
||||||
let storage = navigator.getDeviceStorage('sdcard');
|
|
||||||
let root = await storage.get(dir);
|
|
||||||
|
|
||||||
return await root.getFilesAndDirectories();
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import * as ftp from 'api/ftp';
|
||||||
import Root from 'components/root';
|
import Root from 'components/root';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import './activities';
|
import './activities';
|
||||||
|
|
||||||
|
ftp.connect({
|
||||||
|
host: '192.168.1.76',
|
||||||
|
port: 21,
|
||||||
|
username: 'mahdi',
|
||||||
|
password: 'heater0!'
|
||||||
|
}).then(socket => {
|
||||||
|
window.socket = socket;
|
||||||
|
window.ftp = ftp;
|
||||||
|
}, console.error)
|
||||||
|
|
||||||
let wrapper = document.getElementById('wrapper');
|
let wrapper = document.getElementById('wrapper');
|
||||||
React.render(<Provider store={store}>{() => <Root />}</Provider>, wrapper);
|
React.render(<Provider store={store}>{() => <Root />}</Provider>, wrapper);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CHANGE_DIRECTORY, REFRESH, SETTINGS } from 'actions/types';
|
import { CHANGE_DIRECTORY, REFRESH, SETTINGS } from 'actions/types';
|
||||||
import { children, CACHE } from 'api/files';
|
import { children, CACHE, FTP_CACHE } from 'api/auto';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { reportError, normalize } from 'utils';
|
import { reportError, normalize } from 'utils';
|
||||||
import { listFiles } from 'actions/files-view';
|
import { listFiles } from 'actions/files-view';
|
||||||
@ -13,6 +13,7 @@ export default function(state = '', action) {
|
|||||||
|
|
||||||
if (action.type === REFRESH) {
|
if (action.type === REFRESH) {
|
||||||
CACHE[state] = null;
|
CACHE[state] = null;
|
||||||
|
FTP_CACHE[state] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === REFRESH || action.type === SETTINGS) {
|
if (action.type === REFRESH || action.type === SETTINGS) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { LIST_FILES, RENAME_FILE, DELETE_FILE, CREATE_FILE, MOVE_FILE, COPY_FILE, SEARCH, COMPRESS, DECOMPRESS } from 'actions/types';
|
import { LIST_FILES, RENAME_FILE, DELETE_FILE, CREATE_FILE, MOVE_FILE, COPY_FILE, SEARCH, COMPRESS, DECOMPRESS } from 'actions/types';
|
||||||
import zip from 'jszip';
|
import zip from 'jszip';
|
||||||
import { refresh } from 'actions/files-view';
|
import { refresh } from 'actions/files-view';
|
||||||
import { move, remove, sdcard, createFile, readFile, writeFile, createDirectory, getFile, copy, children } from 'api/files';
|
import * as auto from 'api/auto';
|
||||||
import { show } from 'actions/dialog';
|
import { show } from 'actions/dialog';
|
||||||
import store, { bind } from 'store';
|
import store, { bind } from 'store';
|
||||||
import { reportError, type, normalize } from 'utils';
|
import { reportError, type, normalize } from 'utils';
|
||||||
@ -39,7 +39,7 @@ export default function(state = [], action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === CREATE_FILE) {
|
if (action.type === CREATE_FILE) {
|
||||||
let fn = action.directory ? createDirectory : createFile;
|
let fn = action.directory ? auto.createDirectory : auto.createFile;
|
||||||
fn(action.path).then(boundRefresh, reportError);
|
fn(action.path).then(boundRefresh, reportError);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@ -48,7 +48,7 @@ export default function(state = [], action) {
|
|||||||
if (action.type === RENAME_FILE) {
|
if (action.type === RENAME_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
let cwd = store.getState().get('cwd');
|
let cwd = store.getState().get('cwd');
|
||||||
return move(file, cwd + '/' + action.name);
|
return auto.move(file, cwd + '/' + action.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -57,7 +57,7 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
if (action.type === MOVE_FILE) {
|
if (action.type === MOVE_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
return move(file, action.newPath + '/' + file.name);
|
return auto.move(file, action.newPath + '/' + file.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -66,7 +66,7 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
if (action.type === COPY_FILE) {
|
if (action.type === COPY_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
return copy(file, action.newPath + '/' + file.name);
|
return auto.copy(file, action.newPath + '/' + file.name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -76,7 +76,7 @@ export default function(state = [], action) {
|
|||||||
if (action.type === DELETE_FILE) {
|
if (action.type === DELETE_FILE) {
|
||||||
let all = Promise.all(action.file.map(file => {
|
let all = Promise.all(action.file.map(file => {
|
||||||
let path = normalize((file.path || '') + file.name);
|
let path = normalize((file.path || '') + file.name);
|
||||||
return remove(path, true);
|
return auto.remove(path, true);
|
||||||
}))
|
}))
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -95,14 +95,14 @@ export default function(state = [], action) {
|
|||||||
if (!(file instanceof Blob)) {
|
if (!(file instanceof Blob)) {
|
||||||
let folder = archive.folder(file.name);
|
let folder = archive.folder(file.name);
|
||||||
|
|
||||||
return children(path).then(files => {
|
return auto.children(path).then(files => {
|
||||||
return Promise.all(files.map(child => {
|
return Promise.all(files.map(child => {
|
||||||
return addFile(child);
|
return addFile(child);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return readFile(path).then(content => {
|
return auto.readFile(path).then(content => {
|
||||||
archive.file(archivePath, content);
|
archive.file(archivePath, content);
|
||||||
});
|
});
|
||||||
}))
|
}))
|
||||||
@ -114,7 +114,7 @@ export default function(state = [], action) {
|
|||||||
let cwd = store.getState().get('cwd');
|
let cwd = store.getState().get('cwd');
|
||||||
let path = normalize(cwd + '/' + action.name);
|
let path = normalize(cwd + '/' + action.name);
|
||||||
console.log(path);
|
console.log(path);
|
||||||
return writeFile(path, blob);
|
return auto.writeFile(path, blob);
|
||||||
}).then(boundRefresh).catch(reportError);
|
}).then(boundRefresh).catch(reportError);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@ -123,7 +123,7 @@ export default function(state = [], action) {
|
|||||||
if (action.type === DECOMPRESS) {
|
if (action.type === DECOMPRESS) {
|
||||||
let file = action.file[0];
|
let file = action.file[0];
|
||||||
let path = normalize((file.path || '') + file.name);
|
let path = normalize((file.path || '') + file.name);
|
||||||
readFile(path).then(content => {
|
auto.readFile(path).then(content => {
|
||||||
let archive = new zip(content);
|
let archive = new zip(content);
|
||||||
let files = Object.keys(archive.files);
|
let files = Object.keys(archive.files);
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ export default function(state = [], action) {
|
|||||||
let cwd = store.getState().get('cwd');
|
let cwd = store.getState().get('cwd');
|
||||||
let filePath = normalize(cwd + '/' + name);
|
let filePath = normalize(cwd + '/' + name);
|
||||||
|
|
||||||
return writeFile(filePath, blob);
|
return auto.writeFile(filePath, blob);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
all.then(boundRefresh, reportError);
|
all.then(boundRefresh, reportError);
|
||||||
@ -143,7 +143,3 @@ export default function(state = [], action) {
|
|||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mov(file, newPath) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@ import { SEARCH, CHANGE_DIRECTORY, REFRESH } from 'actions/types';
|
|||||||
import store from 'store';
|
import store from 'store';
|
||||||
import { reportError } from 'utils';
|
import { reportError } from 'utils';
|
||||||
import { listFiles } from 'actions/files-view';
|
import { listFiles } from 'actions/files-view';
|
||||||
import { children } from 'api/files';
|
import { children } from 'api/auto';
|
||||||
import { type, normalize } from 'utils';
|
import { type, normalize } from 'utils';
|
||||||
|
|
||||||
export default function(state = '', action) {
|
export default function(state = '', action) {
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
"device-storage:music": {
|
"device-storage:music": {
|
||||||
"access": "readwrite",
|
"access": "readwrite",
|
||||||
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
"description": "We need access to your files in order to give you the functionality of listing, reading, opening and writing files in your storage"
|
||||||
|
},
|
||||||
|
"tcp-socket": {
|
||||||
|
"description": "FTP Browser: Used to connect to FTP servers"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installs_allowed_from": [
|
"installs_allowed_from": [
|
||||||
|
Loading…
Reference in New Issue
Block a user