diff --git a/common/modules/mixly-modules/common/file-tree.js b/common/modules/mixly-modules/common/file-tree.js index fc51e5f3..96b6fb4c 100644 --- a/common/modules/mixly-modules/common/file-tree.js +++ b/common/modules/mixly-modules/common/file-tree.js @@ -869,16 +869,18 @@ class FileTree extends Component { let startPath = oldNode.id; let endPath = path.join(folderPath, oldNode.text); if (mode === 'move_node') { - if (type === 'file') { - pastePromise = this.#fs_.moveFile(startPath, endPath); + const relativePath = path.relative(startPath, endPath); + if (relativePath.indexOf('..') === -1) { + pastePromise = Promise.resolve(); } else { - pastePromise = this.#fs_.createDirectory(endPath) - .then(() => { - return this.#fs_.moveDirectory(startPath, endPath); - }) - .then(() => { - return this.#fs_.deleteDirectory(startPath); - }); + if (type === 'file') { + pastePromise = this.#fs_.moveFile(startPath, endPath); + } else { + pastePromise = this.#fs_.createDirectory(endPath) + .then(() => { + return this.#fs_.moveDirectory(startPath, endPath); + }); + } } } else if (mode === 'copy_node') { if (type === 'file') { @@ -891,21 +893,62 @@ class FileTree extends Component { } } pastePromise - .catch(Debug.error) - .finally(() => { - this.clearFolderTemp(folderPath); - this.#jstree_.refresh_node(folderPath); - this.openNode(folderPath); - this.hideProgress(); + .then(async () => { + if (mode === 'move_node') { + const temp = path.join(startPath, '..'); + const relativePath = path.relative(temp, endPath); + if (relativePath.indexOf('..') === -1) { + this.clearFolderTemp(temp); + await this.loadNode(temp); + await this.openNode(temp); + this.clearFolderTemp(folderPath); + await this.loadNode(folderPath); + await this.openNode(folderPath); + } else { + this.clearFolderTemp(folderPath); + await this.loadNode(folderPath); + await this.openNode(folderPath); + this.clearFolderTemp(temp); + await this.loadNode(temp); + await this.openNode(temp); + } + } else { + this.clearFolderTemp(folderPath); + this.#jstree_.refresh_node(folderPath); + this.openNode(folderPath); + } + }) + .catch(Debug.error) + .finally(() => { + this.hideProgress(); + }); + } + + loadNode(inPath) { + return new Promise((resolve, reject) => { + if (inPath === '/') { + inPath = '#'; + } + const node = this.#jstree_.get_node(inPath); + if (!node) { + resolve(); + } + this.#jstree_.load_node(node, () => { + resolve(); + }); }); } openNode(folderPath) { - const node = this.#jstree_.get_node(folderPath); - if (!node) { - return; - } - this.#jstree_.open_node(node); + return new Promise((resolve, reject) => { + const node = this.#jstree_.get_node(folderPath); + if (!node) { + resolve(); + } + this.#jstree_.open_node(node, () => { + resolve(); + }); + }); } dispose() { diff --git a/common/modules/mixly-modules/common/statusbar-ampy.js b/common/modules/mixly-modules/common/statusbar-ampy.js index 8d2db35b..75aadbfc 100644 --- a/common/modules/mixly-modules/common/statusbar-ampy.js +++ b/common/modules/mixly-modules/common/statusbar-ampy.js @@ -351,11 +351,6 @@ class StatusBarAmpy extends PageBase { } }); - fileTreeMenu.remove('copy'); - fileTreeMenu.remove('cut'); - fileTreeMenu.remove('paste'); - fileTreeMenu.remove('sep2'); - const editorContextMenu = this.#editor_.getContextMenu(); const editorMenu = editorContextMenu.getItem('code'); diff --git a/common/modules/mixly-modules/electron/ampy-fs.js b/common/modules/mixly-modules/electron/ampy-fs.js index 4a5ad65a..9c9897b9 100644 --- a/common/modules/mixly-modules/electron/ampy-fs.js +++ b/common/modules/mixly-modules/electron/ampy-fs.js @@ -106,9 +106,21 @@ class AmpyFS extends FS { return this.rename(oldFilePath, newFilePath); } - // async moveFile(oldFilePath, newFilePath) {} + async moveFile(oldFilePath, newFilePath) { + return this.rename(oldFilePath, newFilePath); + } - // async copyFile(oldFilePath, newFilePath) {} + async copyFile(oldFilePath, newFilePath) { + let stdout = '', error = null; + try { + const output = await this.#ampy_.cpfile(this.#port_, this.#baud_, oldFilePath, newFilePath); + stdout = output.stdout; + } catch (e) { + error = e; + Debug.error(error); + } + return [error, stdout]; + } async deleteFile(filePath) { let stdout = '', error = null; @@ -179,9 +191,21 @@ class AmpyFS extends FS { return this.rename(oldFolderPath, newFolderPath); } - // async moveDirectory(oldFolderPath, newFolderPath) {} + async moveDirectory(oldFolderPath, newFolderPath) { + return this.rename(oldFolderPath, newFolderPath); + } - // async copyDirectory(oldFolderPath, newFolderPath) {} + async copyDirectory(oldFolderPath, newFolderPath) { + let stdout = '', error = null; + try { + const output = await this.#ampy_.cpdir(this.#port_, this.#baud_, oldFolderPath, newFolderPath); + stdout = output.stdout; + } catch (e) { + error = e; + Debug.error(error); + } + return [error, stdout]; + } async deleteDirectory(folderPath) { let stdout = '', error = null; diff --git a/common/modules/mixly-modules/electron/ampy.js b/common/modules/mixly-modules/electron/ampy.js index 7f81a2b4..75cad19c 100644 --- a/common/modules/mixly-modules/electron/ampy.js +++ b/common/modules/mixly-modules/electron/ampy.js @@ -33,6 +33,8 @@ class AmpyExt extends Ampy { rm: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rm "{{&filePath}}"', rmdir: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rmdir "{{&folderPath}}"', rename: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rename "{{&oldPath}}" "{{&newPath}}"', + cpdir: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 cpdir "{{&startPath}}" "{{&endPath}}"', + cpfile: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 cpfile "{{&startPath}}" "{{&endPath}}"', run: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 run "{{&filePath}}"' } @@ -90,6 +92,14 @@ class AmpyExt extends Ampy { return this.exec(port, this.render('rename', { port, baud, oldPath, newPath })); } + async cpdir(port, baud, startPath, endPath) { + return this.exec(port, this.render('cpdir', { port, baud, startPath, endPath })); + } + + async cpfile(port, baud, startPath, endPath) { + return this.exec(port, this.render('cpfile', { port, baud, startPath, endPath })); + } + async run(port, baud, filePath) { return this.exec(port, this.render('run', { port, baud, filePath })); } diff --git a/common/modules/mixly-modules/web/ampy-fs.js b/common/modules/mixly-modules/web/ampy-fs.js index 1071496b..37762015 100644 --- a/common/modules/mixly-modules/web/ampy-fs.js +++ b/common/modules/mixly-modules/web/ampy-fs.js @@ -125,9 +125,29 @@ class AmpyFS extends FS { return this.rename(oldFilePath, newFilePath); } - // async moveFile(oldFilePath, newFilePath) {} + async moveFile(oldFilePath, newFilePath) { + return this.rename(oldFilePath, newFilePath); + } - // async copyFile(oldFilePath, newFilePath) {} + async copyFile(oldFilePath, newFilePath) { + let stdout = '', error = null, ampy = null; + try { + ampy = await this.getAmpy(); + await ampy.enter(); + stdout = await ampy.cpfile(oldFilePath, newFilePath); + } catch (e) { + error = e; + Debug.error(error); + } + try { + await ampy.exit(); + await ampy.dispose(); + } catch (e) { + error = e; + Debug.error(error); + } + return [error, stdout]; + } async deleteFile(filePath) { let stdout = '', error = null, ampy = null; @@ -206,9 +226,29 @@ class AmpyFS extends FS { return this.rename(oldFolderPath, newFolderPath); } - // async moveDirectory(oldFolderPath, newFolderPath) {} + async moveDirectory(oldFolderPath, newFolderPath) { + return this.rename(oldFolderPath, newFolderPath); + } - // async copyDirectory(oldFolderPath, newFolderPath) {} + async copyDirectory(oldFolderPath, newFolderPath) { + let stdout = '', error = null, ampy = null; + try { + ampy = await this.getAmpy(); + await ampy.enter(); + stdout = await ampy.cpdir(oldFolderPath, newFolderPath); + } catch (e) { + error = e; + Debug.error(error); + } + try { + await ampy.exit(); + await ampy.dispose(); + } catch (e) { + error = e; + Debug.error(error); + } + return [error, stdout]; + } async deleteDirectory(folderPath) { let stdout = '', error = null, ampy = null; diff --git a/common/modules/mixly-modules/web/ampy.js b/common/modules/mixly-modules/web/ampy.js index 8ec9c875..db348eac 100644 --- a/common/modules/mixly-modules/web/ampy.js +++ b/common/modules/mixly-modules/web/ampy.js @@ -28,6 +28,8 @@ class AmpyExt extends Ampy { this.RMDIR = goog.readFileSync(path.join(Env.templatePath, 'python/rmdir.py')); this.GET = goog.readFileSync(path.join(Env.templatePath, 'python/get.py')); this.CWD = goog.readFileSync(path.join(Env.templatePath, 'python/cwd.py')); + this.CPDIR = goog.readFileSync(path.join(Env.templatePath, 'python/cpdir.py')); + this.CPFILE = goog.readFileSync(path.join(Env.templatePath, 'python/cpfile.py')); } #device_ = null; @@ -338,6 +340,31 @@ class AmpyExt extends Ampy { return !dataError; } + async cpdir(oldname, newname, timeout = 5000) { + if (!this.isActive()) { + throw new Error(Msg.Lang['ampy.portIsNotOpen']); + } + const code = Mustache.render(AmpyExt.CPDIR, { + oldPath: oldname, + newPath: newname + }); + const { data, dataError } = await this.exec(code, timeout); + console.log(data, dataError) + return !dataError; + } + + async cpfile(oldname, newname, timeout = 5000) { + if (!this.isActive()) { + throw new Error(Msg.Lang['ampy.portIsNotOpen']); + } + const code = Mustache.render(AmpyExt.CPFILE, { + oldPath: oldname, + newPath: newname + }); + const { dataError } = await this.exec(code, timeout); + return !dataError; + } + async cwd(timeout = 5000) { if (!this.isActive()) { throw new Error(Msg.Lang['ampy.portIsNotOpen']); diff --git a/common/templates/python/cpdir.py b/common/templates/python/cpdir.py new file mode 100644 index 00000000..ae254558 --- /dev/null +++ b/common/templates/python/cpdir.py @@ -0,0 +1,27 @@ +try: + import os +except ImportError: + import uos as os + +def cpfile(src, dst): + with open(src, 'rb') as src_file: + content = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(content) + +def cpdir(src, dst): + try: + os.mkdir(dst) + except: + pass + for item in os.listdir(src): + src_path = '{}/{}'.format(src, item) + dst_path = '{}/{}'.format(dst, item) + stat = os.stat(src_path) + mode = stat[0] + if mode & 0o170000 == 0o040000: + cpdir(src_path, dst_path) + else: + cpfile(src_path, dst_path) + +cpdir('{{&oldPath}}', '{{&newPath}}') \ No newline at end of file diff --git a/common/templates/python/cpfile.py b/common/templates/python/cpfile.py new file mode 100644 index 00000000..de5f6352 --- /dev/null +++ b/common/templates/python/cpfile.py @@ -0,0 +1,12 @@ +try: + import os +except ImportError: + import uos as os + +def cpfile(src, dst): + with open(src, 'rb') as src_file: + content = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(content) + +cpfile('{{&oldPath}}', '{{&newPath}}') \ No newline at end of file diff --git a/common/templates/python/get.py b/common/templates/python/get.py index 7039b8c9..cf6ea9b2 100644 --- a/common/templates/python/get.py +++ b/common/templates/python/get.py @@ -1,5 +1,6 @@ import sys import ubinascii + with open('{{&path}}', 'rb') as infile: while True: result = infile.read(32) diff --git a/common/templates/python/mkdir.py b/common/templates/python/mkdir.py index dad2a798..2e417110 100644 --- a/common/templates/python/mkdir.py +++ b/common/templates/python/mkdir.py @@ -2,4 +2,5 @@ try: import os except ImportError: import uos as os + os.mkdir('{{&path}}') \ No newline at end of file diff --git a/common/templates/python/rm.py b/common/templates/python/rm.py index 8e6b9bc9..461c0d3a 100644 --- a/common/templates/python/rm.py +++ b/common/templates/python/rm.py @@ -2,4 +2,5 @@ try: import os except ImportError: import uos as os + os.remove('{{&path}}') \ No newline at end of file diff --git a/common/templates/python/rmdir.py b/common/templates/python/rmdir.py index 18d7ebe7..540010d3 100644 --- a/common/templates/python/rmdir.py +++ b/common/templates/python/rmdir.py @@ -2,6 +2,7 @@ try: import os except ImportError: import uos as os + def rmdir(directory): os.chdir(directory) for f in os.listdir(): @@ -13,4 +14,5 @@ def rmdir(directory): rmdir(f) os.chdir('..') os.rmdir(directory) + rmdir('{{&path}}') \ No newline at end of file diff --git a/tools/python/ampy/cli.py b/tools/python/ampy/cli.py index 238aecb4..6dec35f7 100644 --- a/tools/python/ampy/cli.py +++ b/tools/python/ampy/cli.py @@ -337,7 +337,7 @@ def put(local, remote): else: sys.stdout.write("Skip " + filename + "\n") sys.stdout.flush() - board_files.putDir(file_name_list, data_list, False) + board_files.putdir(file_name_list, data_list, False) ''' for filename in child_files: @@ -404,6 +404,22 @@ def rename(oldname, newname): board_files.rename(oldname, newname) +@cli.command() +@click.argument("local", required=True) +@click.argument("remote", required=True) +def cpdir(local, remote): + board_files = files.Files(_board) + board_files.cpdir(local, remote) + + +@cli.command() +@click.argument("local", required=True) +@click.argument("remote", required=True) +def cpfile(local, remote): + board_files = files.Files(_board) + board_files.cpfile(local, remote) + + @cli.command() @click.argument("local_file") @click.option( diff --git a/tools/python/ampy/files.py b/tools/python/ampy/files.py index 91beb231..da130546 100644 --- a/tools/python/ampy/files.py +++ b/tools/python/ampy/files.py @@ -388,7 +388,7 @@ class Files(object): if exit_repl: self._pyboard.exit_raw_repl() - def putDir(self, fileNameList, dataList, enter_repl=True, exit_repl=True): + def putdir(self, fileNameList, dataList, enter_repl=True, exit_repl=True): """Create or update the specified file with the provided data. """ # Open the file for writing on the board and write chunks of data. @@ -458,6 +458,7 @@ class Files(object): import os except ImportError: import uos as os + def rmdir(directory): os.chdir(directory) for f in os.listdir(): @@ -506,6 +507,70 @@ class Files(object): raise ex self._pyboard.exit_raw_repl() + def cpdir(self, oldpath, newpath): + command = """ + try: + import os + except ImportError: + import uos as os + + def cpfile(src, dst): + with open(src, 'rb') as src_file: + content = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(content) + + def cpdir(src, dst): + try: + os.mkdir(dst) + except: + pass + for item in os.listdir(src): + src_path = src + '/' + item + dst_path = dst + '/' + item + stat = os.stat(src_path) + mode = stat[0] + if mode & 0o170000 == 0o040000: + cpdir(src_path, dst_path) + else: + cpfile(src_path, dst_path) + + cpdir('{0}', '{1}') + """.format( + oldpath, newpath + ) + self._pyboard.enter_raw_repl() + try: + out = self._pyboard.exec_(textwrap.dedent(command)) + except PyboardError as ex: + message = ex.args[2].decode("utf-8") + raise ex + self._pyboard.exit_raw_repl() + + def cpfile(self, oldpath, newpath): + command = """ + try: + import os + except ImportError: + import uos as os + def cpfile(src, dst): + with open(src, 'rb') as src_file: + content = src_file.read() + with open(dst, 'wb') as dst_file: + dst_file.write(content) + + cpfile('{0}', '{1}') + """.format( + oldpath, newpath + ) + self._pyboard.enter_raw_repl() + try: + out = self._pyboard.exec_(textwrap.dedent(command)) + except PyboardError as ex: + message = ex.args[2].decode("utf-8") + raise ex + self._pyboard.exit_raw_repl() + def run(self, filename, wait_output=True, stream_output=True): """Run the provided script and return its output. If wait_output is True (default) then wait for the script to finish and then return its output,