コード例 #1
0
class Node(object):
    """
    General node. Inherited by header nodes or entry nodes.
    """
    State = Enum('NodeState', 'NORMAL, PICKED, UNDEROP')
    ToggleOpRes = Enum('NodeToggleOpRes', 'INVALID, ON, OFF')

    def __init__(self, fullpath, name, highlight, level=0):
        self.fullpath = fullpath
        self.name = name
        self.set_highlight(highlight)
        self.level = level
        self.state = Node.State.NORMAL
        self.is_cursor_on = False

    def set_highlight(self, highlight):
        if type(highlight) is str:
            highlight = colortbl[highlight]
        self.highlight = highlight

    @property
    def highlight_content(self):
        return c256(self.name, self.highlight, self.is_cursor_on)

    @property
    def isDir(self):
        return False

    @property
    def isINFO(self):
        return False

    def cursor_on(self):
        self.is_cursor_on = True

    def cursor_off(self):
        self.is_cursor_on = False

    def toggle_pick(self):
        return Node.ToggleOpRes.INVALID

    def re_stat(self, fs):
        pass
コード例 #2
0
ファイル: netranger.py プロジェクト: veirus/vim-netranger
class Node(object):
    """
    General node. Inherited by header nodes or entry nodes.
    """
    State = Enum('NodeState', 'NORMAL, PICKED, UNDEROP')
    ToggleOpRes = Enum('NodeToggleOpRes', 'INVALID, ON, OFF')

    def __init__(self, fullpath, name, highlight, level=0):
        self.fullpath = fullpath
        self.name = name
        self.set_highlight(highlight)
        self.level = level
        self.state = Node.State.NORMAL

    def set_highlight(self, highlight, cursor_on=False):
        if type(highlight) is str:
            highlight = colortbl[highlight]
        if cursor_on:
            self.highlight = '[38;5;{};7'.format(highlight)
        else:
            self.highlight = '[38;5;{}'.format(highlight)

    @property
    def highlight_content(self):
        return '{}m{}'.format(self.highlight, self.name)

    @property
    def isDir(self):
        return False

    @property
    def isHeader(self):
        return False

    def cursor_on(self):
        pass

    def cursor_off(self):
        pass

    def toggle_pick(self):
        return Node.ToggleOpRes.INVALID
コード例 #3
0
ファイル: fs.py プロジェクト: ipod825/vim-netranger
class Rclone(LocalFS):
    ServerCmd = util.GenNetRangerScriptCmd('rclone_server')
    is_remote = True
    SyncDirection = Enum('SyncDirection', 'DOWN, UP')

    @classmethod
    def sync_src_dst(self, lpath, direction):
        rpath = self.rpath(lpath)
        if direction == Rclone.SyncDirection.UP:
            return lpath, rpath
        else:
            return rpath, lpath

    @classmethod
    def init(self, root_dir, remote_remap):
        self._flags = Vim.Var("_NETRRcloneFlags", default="")
        self.rclone_rcd_port = Vim.Var('NETRcloneRcdPort')
        if root_dir[-1] == '/':
            root_dir = root_dir[:-1]

        self.root_dir = root_dir
        self.rplen = len(root_dir) + 1
        self.rcd_started = False
        for remote, root in remote_remap.items():
            if root[-1] != '/':
                remote_remap[remote] += '/'
        self.remote_remap = remote_remap
        Shell.mkdir(root_dir)

    @classmethod
    def is_remote_path(self, path):
        return path.startswith(self.root_dir)

    @classmethod
    def rpath(self, lpath):
        if not lpath.startswith(self.root_dir):
            return lpath
        rpath = lpath[self.rplen:]
        if '/' not in rpath:
            rpath += ':/'
        else:
            rpath = rpath.replace('/', ':/', 1)

        for remote, root in self.remote_remap.items():
            if rpath.startswith(remote):
                rpath = rpath.replace('/', root, 1)
        return rpath

    @classmethod
    def ls(self, dirname, cheap_remote_ls=False):
        if not cheap_remote_ls and len(dirname) > len(self.root_dir):
            local_files = set([
                name for name in Shell.run(f'ls -p "{dirname}"').split('\n')
                if len(name) > 0
            ])

            remote_files = set([
                name for name in Shell.run(
                    f'rclone {self._flags} lsf "{self.rpath(dirname)}"').split(
                        '\n') if len(name) > 0
            ])
            for name in remote_files.difference(local_files):
                if name[-1] == '/':
                    Shell.mkdir(os.path.join(dirname, name))
                else:
                    Shell.touch(os.path.join(dirname, name))

            for name in local_files.difference(remote_files):
                # TODO use Vim.AsyncRun instead
                Shell.run_async(f'rclone {self._flags} copyto --tpslimit=10 \
                                "{os.path.join(dirname, name)}" \
                                "{os.path.join(self.rpath(dirname), name)}"')
        return super(Rclone, self).ls(dirname)

    @classmethod
    def ensure_remote_downloaded(self, lpath):
        if os.stat(lpath).st_size == 0:
            src, dst = self.sync_src_dst(lpath, Rclone.SyncDirection.DOWN)
            Shell.run(
                f'rclone {self._flags} copyto --tpslimit=10 "{src}" "{dst}"')

    @classmethod
    def rename(self, src, dst):
        Shell.run(
            f'rclone {self._flags} moveto "{self.rpath(src)}" "{self.rpath(dst)}"'
        )
        super(Rclone, self).rename(src, dst)

    @classmethod
    def mv(self, src_arr, dst, on_exit):
        self.exec_server_cmd(
            'mv', on_exit, {
                'rsrc': [self.rpath(s) for s in src_arr],
                'src': src_arr,
                'rdst': self.rpath(dst),
                'dst': dst,
                'flags': self._flags
            })

    @classmethod
    def cp(self, src_arr, dst, on_exit):
        self.exec_server_cmd(
            'cp', on_exit, {
                'rsrc': [self.rpath(s) for s in src_arr],
                'src': src_arr,
                'rdst': self.rpath(dst),
                'dst': dst,
                'flags': self._flags
            })

    @classmethod
    def rm(self, src_arr, force=False, on_exit=None):
        self.exec_server_cmd(
            'rm', on_exit, {
                'rsrc': [self.rpath(s) for s in src_arr],
                'src': src_arr,
                'flags': self._flags
            })

    @classmethod
    def touch(self, name):
        Shell.touch(name)

    @classmethod
    def mkdir(self, name):
        Shell.mkdir(name)

    @classmethod
    def sync(self, lpath, direction):
        src, dst = self.sync_src_dst(lpath, direction)
        Shell.run(f'rclone {self._flags} sync "{src}" "{dst}"')

    @classmethod
    def run_cmd(self, cmd):
        if not self.rcd_started:
            Vim.AsyncRun(
                f'rclone {self._flags} rcd --rc-no-auth --rc-addr=localhost:{self.rclone_rcd_port}'
            )
            self.rcd_started = True
            # Ensure the server running before executing the next command.
            time.sleep(.1)

        return json.loads(
            Shell.run(
                f'rclone {self._flags} rc --rc-addr=localhost:{self.rclone_rcd_port} {cmd}'
            ))

    @classmethod
    def cmd_listremotes(self):
        return self.run_cmd('config/listremotes')['remotes']

    @classmethod
    def list_remotes_in_vim_buffer(self):
        if not Shell.isinPATH('rclone'):
            self.install_rclone()

        remotes = set(self.cmd_listremotes())
        local_remotes = set(super(Rclone, self).ls(self.root_dir))
        for remote in remotes.difference(local_remotes):
            Shell.mkdir(os.path.join(self.root_dir, remote))
        for remote in local_remotes.difference(remotes):
            Shell.rm(os.path.join(self.root_dir, remote))

        if len(remotes) > 0:
            Vim.command(f'NETRTabdrop {self.root_dir}')
        else:
            Vim.ErrorMsg(
                "There's no remote now. Run 'rclone config' in a terminal to "
                "setup remotes and restart vim again.")

    @classmethod
    def install_rclone(self):
        import platform
        import zipfile

        rclone_dir = Vim.UserInput(
            'Rclone not in PATH. Install it at (modify/enter)',
            os.path.expanduser('~/rclone'))
        Shell.mkdir(rclone_dir)

        system = platform.system().lower()
        processor = 'amd64'
        if '386' in platform.processor():
            processor = '386'
        else:
            # Should support arm??
            pass

        url = f'https://downloads.rclone.org/rclone-current-{system}-{processor}.zip'
        zip_fname = os.path.join(rclone_dir, 'rclone.zip')
        Shell.urldownload(url, zip_fname)
        zip_ref = zipfile.ZipFile(zip_fname, 'r')
        zip_ref.extractall(rclone_dir)
        for entry in zip_ref.NameToInfo:
            if entry.endswith('rclone'):
                Shell.cp(os.path.join(rclone_dir, entry), rclone_dir)
                Shell.chmod(os.path.join(rclone_dir, 'rclone'), 755)

        zip_ref.close()
        os.remove(zip_fname)

        shellrc = Vim.UserInput(
            'Update PATH in (leave blank to set manually later)',
            Shell.shellrc())
        if len(shellrc) > 0:
            with open(shellrc, 'a') as f:
                f.write(f'PATH={rclone_dir}:$PATH\n')
        os.environ['PATH'] += ':' + rclone_dir
コード例 #4
0
ファイル: fs.py プロジェクト: ipod825/vim-netranger
from __future__ import absolute_import

import json
import os
import pickle
import re
import shutil
import tempfile
import time

from netranger import Vim, util
from netranger.config import file_sz_display_wid
from netranger.enum import Enum
from netranger.shell import Shell

FType = Enum('FileType', 'SOCK, LNK, REG, BLK, DIR, CHR, FIFO')


def do_nothing():
    pass


class FSTarget(object):
    def __init__(self, target_path=''):
        """ This is a help class for separating local files and remote files
        for mv, cp, rm commands. Though the logic in this class can be done
        in the caller side, it makes the caller has higher branch number and
        hard-to-read code.
        """
        self.remote_targets = []
        self.local_targets = []
コード例 #5
0
ファイル: netrangerGit.py プロジェクト: ipod825/netranger-git
class Repo(object):
    State = Enum(
        'GitState',
        'INVALID, IGNORED, UNTRACKED, UNMODIFIED, MODIFIED, STAGED, '
        'STAGEDMODIFIED, UNMERGED')

    def __init__(self, path):
        self.path = path
        self.path_len = len(path) + 1

        self.staged_str = None
        self.modified_str = None
        self.ignored_str = None
        self.commit_edit_msg = os.path.join(self.path, '.git/COMMIT_EDITMSG')

    def run_cmd(self, cmd):
        return Shell.run('git -C {} {}'.format(self.path, cmd))

    def get_state(self, fullpath):
        rel_path = fullpath[self.path_len:]
        if os.path.isdir(fullpath):
            if not rel_path:
                return Repo.State.INVALID
            if self.staged_str is None:
                self.staged_str = self.run_cmd('diff --name-only --cached')
                self.modified_str = self.run_cmd('ls-files -m')
                self.ignored_str = self.run_cmd(
                    'ls-files --others -i --exclude-standard')
            if re.search(rel_path, self.staged_str):
                if re.search(rel_path, self.modified_str):
                    return Repo.State.STAGEDMODIFIED
                else:
                    return Repo.State.STAGED
            elif re.search(rel_path, self.modified_str):
                return Repo.State.MODIFIED
            elif re.search(rel_path + '\n', self.ignored_str):
                return Repo.State.IGNORED
            else:
                return Repo.State.INVALID
        else:
            state_str = self.run_cmd(
                'status --porcelain --ignored -uall {}'.format(rel_path))
            if state_str:
                return {
                    "!!": Repo.State.IGNORED,
                    '??': Repo.State.UNTRACKED,
                    ' M': Repo.State.MODIFIED,
                    'MM': Repo.State.STAGEDMODIFIED,
                    'M ': Repo.State.STAGED
                }[state_str[:2]]
            else:
                return Repo.State.INVALID

    def get_prev_and_next_state(self, fullpath):
        cur_state = self.get_state(fullpath)
        if cur_state == Repo.State.UNTRACKED:
            return Repo.State.INVALID, Repo.State.STAGED
        elif (cur_state == Repo.State.STAGEDMODIFIED
              or cur_state == Repo.State.STAGED):
            return Repo.State.MODIFIED, Repo.State.STAGED
        elif cur_state == Repo.State.MODIFIED:
            return Repo.State.UNMODIFIED, Repo.State.STAGED
        elif cur_state == Repo.State.IGNORED:
            return Repo.State.INVALID, Repo.State.STAGED
        elif cur_state == Repo.State.INVALID:
            return Repo.State.INVALID, Repo.State.INVALID
        else:
            assert False, "get_prev_and_next_state: "
            "Unhandled case: " + cur_state

    def stage(self, fullpath):
        self.run_cmd('add {}'.format(fullpath))

    def unstage(self, fullpath):
        self.run_cmd('reset HEAD {}'.format(fullpath))

    def unmodify(self, fullpath):

        ans = Vim.UserInput("This will discard any made changes. Proceed "
                            "anyway? (y/n)")
        if ans == 'y':
            self.run_cmd('checkout {}'.format(fullpath))

    def commit(self, amend=False):
        if amend:
            # TODO Should we check if already pushed?
            return self.run_cmd('commit --amend --no-edit')
        else:
            with open(self.commit_edit_msg, 'w') as file:
                lines = []
                for line in file:
                    line = line.strip()
                    if line and line[0] != '#':
                        lines.append(line)
                if len(lines) > 0:
                    return self.run_cmd('commit -m "{}"'.format(
                        ''.join(lines)))

    def stage_file_content(self, fullpath):
        rel_path = fullpath[self.path_len:]
        return self.run_cmd('cat-file -p :{}'.format(rel_path))
コード例 #6
0
class Rclone(FS):
    SyncDirection = Enum('SyncDirection', 'DOWN, UP')

    def sync_src_dst(self, lpath, direction):
        rpath = self.rpath(lpath)
        if direction == Rclone.SyncDirection.UP:
            return lpath, rpath
        else:
            return rpath, lpath

    def __init__(self, root_dir, remote_remap):
        if root_dir[-1] == '/':
            root_dir = root_dir[:-1]

        self.root_dir = root_dir
        self.rplen = len(root_dir) + 1
        for remote, root in remote_remap.items():
            if root[-1] != '/':
                remote_remap[remote] += '/'
        self.remote_remap = remote_remap
        Shell.mkdir(root_dir)
        remotes = set(Shell.run('rclone listremotes').split(':\n'))
        local_remotes = set(super(Rclone, self).ls(root_dir))
        for remote in remotes.difference(local_remotes):
            Shell.mkdir(os.path.join(root_dir, remote))
        for remote in local_remotes.difference(remotes):
            super(Rclone, self).rm(os.path.join(root_dir, remote), True)

        self.has_remote = len(remotes) > 0
        self.ls_time_stamp = {}

    def isRemotePath(self, lpath):
        return lpath.startswith(self.root_dir)

    def rpath(self, lpath):
        if not lpath.startswith(self.root_dir):
            return lpath
        rpath = lpath[self.rplen:]
        if '/' not in rpath:
            rpath += ':/'
        else:
            rpath = rpath.replace('/', ':/', 1)

        for remote, root in self.remote_remap.items():
            if rpath.startswith(remote):
                rpath = rpath.replace('/', root, 1)
        return rpath

    def ls(self, dirname, cheap_remote_ls=False):
        if not cheap_remote_ls and len(dirname) > len(self.root_dir):
            local_files = set([
                name
                for name in Shell.run('ls -p {}'.format(dirname)).split('\n')
                if len(name) > 0
            ])
            remote_files = set([
                name for name in Shell.run('rclone lsf "{}"'.format(
                    self.rpath(dirname))).split('\n') if len(name) > 0
            ])

            for name in remote_files.difference(local_files):
                if name[-1] == '/':
                    Shell.mkdir(os.path.join(dirname, name))
                else:
                    Shell.touch(os.path.join(dirname, name))

            for name in local_files.difference(remote_files):
                log('rclone copyto "{}" "{}"'.format(
                    os.path.join(dirname, name),
                    os.path.join(self.rpath(dirname), name)))
                Shell.run('rclone copyto "{}" "{}"'.format(
                    os.path.join(dirname, name),
                    os.path.join(self.rpath(dirname), name)))
        return super(Rclone, self).ls(dirname)

    def ensure_downloaded(self, lpath):
        if os.stat(lpath).st_size == 0:
            src, dst = self.sync_src_dst(lpath, Rclone.SyncDirection.DOWN)
            Shell.run('rclone copyto "{}" "{}"'.format(src, dst))

    def rename(self, src, dst):
        Shell.run('rclone moveto "{}" "{}"'.format(self.rpath(src),
                                                   self.rpath(dst)))
        super(Rclone, self).rename(src, dst)

    def mv(self, src, dst):
        # TODO could be more efficient without calling rclone two times
        self.cp(src, dst)
        self.rm(src)

    def cp(self, src, dst):
        Shell.run('rclone copyto "{}" "{}"'.format(
            self.rpath(src),
            os.path.join(self.rpath(dst), os.path.basename(src))))
        super(Rclone, self).cp(src, dst)

    def rm(self, target, force=False):
        cmd = 'purge' if os.path.isdir(target) else 'delete'
        Shell.run('rclone {} "{}"'.format(cmd, self.rpath(target)))
        super(Rclone, self).rm(target, force=True)

    def sync(self, lpath, direction):
        src, dst = self.sync_src_dst(lpath, direction)
        Shell.run('rclone sync "{}" "{}"'.format(src, dst))

    def parent_dir(self, cwd):
        if len(cwd) == self.rplen - 1:
            return cwd
        return os.path.abspath(os.path.join(cwd, os.pardir))

    @classmethod
    def valid_or_install(cls, vim):
        import platform
        import zipfile

        if Shell.isinPATH('rclone'):
            return True
        else:
            rclone_dir = VimUserInput(
                'Rclone not in PATH. Install it at (modify/enter)',
                os.path.expanduser('~/rclone'))
            Shell.mkdir(rclone_dir)

            system = platform.system().lower()
            processor = 'amd64'
            if '386' in platform.processor():
                processor = '386'
            else:
                # Should support arm??
                pass

            url = 'https://downloads.rclone.org/rclone-current-{}-{}.zip'.format(
                system, processor)
            zip_fname = os.path.join(rclone_dir, 'rclone.zip')
            Shell.urldownload(url, zip_fname)
            zip_ref = zipfile.ZipFile(zip_fname, 'r')
            zip_ref.extractall(rclone_dir)
            for entry in zip_ref.NameToInfo:
                if entry.endswith('rclone'):
                    Shell.cp(os.path.join(rclone_dir, entry), rclone_dir)
                    Shell.chmod(os.path.join(rclone_dir, 'rclone'), 755)

            zip_ref.close()
            os.remove(zip_fname)

            shellrc = VimUserInput(
                'Update PATH in (leave blank to set manually later)',
                Shell.shellrc())
            if len(shellrc) > 0:
                with open(shellrc, 'a') as f:
                    f.write('PATH={}:$PATH\n'.format(rclone_dir))
            os.environ['PATH'] += ':' + rclone_dir
コード例 #7
0
ファイル: fs.py プロジェクト: mwcz/vim-netranger
class Rclone(FS):
    FScmds = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                          '../rclone_server.py')
    SyncDirection = Enum('SyncDirection', 'DOWN, UP')

    def sync_src_dst(self, lpath, direction):
        rpath = self.rpath(lpath)
        if direction == Rclone.SyncDirection.UP:
            return lpath, rpath
        else:
            return rpath, lpath

    def __init__(self, root_dir, remote_remap):
        if root_dir[-1] == '/':
            root_dir = root_dir[:-1]

        self.root_dir = root_dir
        self.rplen = len(root_dir) + 1
        for remote, root in remote_remap.items():
            if root[-1] != '/':
                remote_remap[remote] += '/'
        self.remote_remap = remote_remap
        Shell.mkdir(root_dir)

        remotes = set([
            line for line in Shell.run('rclone listremotes').split(':\n')
            if line
        ])
        local_remotes = set(super(Rclone, self).ls(root_dir))
        for remote in remotes.difference(local_remotes):
            Shell.mkdir(os.path.join(root_dir, remote))
        for remote in local_remotes.difference(remotes):
            Shell.rm(os.path.join(root_dir, remote))

        self.has_remote = len(remotes) > 0
        self.ls_time_stamp = {}

    def rpath(self, lpath):
        if not lpath.startswith(self.root_dir):
            return lpath
        rpath = lpath[self.rplen:]
        if '/' not in rpath:
            rpath += ':/'
        else:
            rpath = rpath.replace('/', ':/', 1)

        for remote, root in self.remote_remap.items():
            if rpath.startswith(remote):
                rpath = rpath.replace('/', root, 1)
        return rpath

    def ls(self, dirname, cheap_remote_ls=False):
        if not cheap_remote_ls and len(dirname) > len(self.root_dir):
            local_files = set([
                name
                for name in Shell.run('ls -p {}'.format(dirname)).split('\n')
                if len(name) > 0
            ])
            remote_files = set([
                name for name in Shell.run('rclone lsf "{}"'.format(
                    self.rpath(dirname))).split('\n') if len(name) > 0
            ])
            for name in remote_files.difference(local_files):
                if name[-1] == '/':
                    Shell.mkdir(os.path.join(dirname, name))
                else:
                    Shell.touch(os.path.join(dirname, name))

            for name in local_files.difference(remote_files):
                Shell.run('rclone copyto --tpslimit=10 "{}" "{}"'.format(
                    os.path.join(dirname, name),
                    os.path.join(self.rpath(dirname), name)))
        return super(Rclone, self).ls(dirname)

    def ensure_downloaded(self, lpath):
        if os.stat(lpath).st_size == 0:
            src, dst = self.sync_src_dst(lpath, Rclone.SyncDirection.DOWN)
            Shell.run('rclone copyto --tpslimit=10 "{}" "{}"'.format(src, dst))

    def rename(self, src, dst):
        Shell.run('rclone moveto "{}" "{}"'.format(self.rpath(src),
                                                   self.rpath(dst)))
        super(Rclone, self).rename(src, dst)

    def exec_rclone_server_cmd(self, cmd, on_exit, arguments):
        fname = tempfile.mkstemp()[1]
        with open(fname, 'ab') as f:
            pickle.dump(arguments, f)
        Shell.run_async('python {} {} {}'.format(Rclone.FScmds, cmd, fname),
                        on_exit=on_exit)

    def mv(self, src_arr, dst, on_exit):
        self.exec_rclone_server_cmd(
            'mv', on_exit, {
                'rsrc': [self.rpath(s) for s in src_arr],
                'src': src_arr,
                'rdst': self.rpath(dst),
                'dst': dst
            })

    def cp(self, src_arr, dst, on_exit):
        self.exec_rclone_server_cmd(
            'cp', on_exit, {
                'rsrc': [self.rpath(s) for s in src_arr],
                'src': src_arr,
                'rdst': self.rpath(dst),
                'dst': dst
            })

    def rm(self, src_arr, force=False, on_exit=None):
        self.exec_rclone_server_cmd('rm', on_exit, {
            'rsrc': [self.rpath(s) for s in src_arr],
            'src': src_arr
        })

    def touch(self, name):
        Shell.touch(name)
        Shell.run('rclone copyto "{}" "{}"'.format(
            name, os.path.join(self.rpath(name), os.path.basename(name))))

    def mkdir(self, name):
        Shell.mkdir(name)

    def sync(self, lpath, direction):
        src, dst = self.sync_src_dst(lpath, direction)
        Shell.run('rclone sync "{}" "{}"'.format(src, dst))

    def parent_dir(self, cwd):
        if len(cwd) == self.rplen - 1:
            return cwd
        return os.path.abspath(os.path.join(cwd, os.pardir))

    @classmethod
    def valid_or_install(cls, vim):
        import platform
        import zipfile

        if Shell.isinPATH('rclone'):
            return True
        else:
            rclone_dir = VimUserInput(
                'Rclone not in PATH. Install it at (modify/enter)',
                os.path.expanduser('~/rclone'))
            Shell.mkdir(rclone_dir)

            system = platform.system().lower()
            processor = 'amd64'
            if '386' in platform.processor():
                processor = '386'
            else:
                # Should support arm??
                pass

            url = 'https://downloads.rclone.org/rclone-current-{}-{}.zip'\
                .format(system, processor)
            zip_fname = os.path.join(rclone_dir, 'rclone.zip')
            Shell.urldownload(url, zip_fname)
            zip_ref = zipfile.ZipFile(zip_fname, 'r')
            zip_ref.extractall(rclone_dir)
            for entry in zip_ref.NameToInfo:
                if entry.endswith('rclone'):
                    Shell.cp(os.path.join(rclone_dir, entry), rclone_dir)
                    Shell.chmod(os.path.join(rclone_dir, 'rclone'), 755)

            zip_ref.close()
            os.remove(zip_fname)

            shellrc = VimUserInput(
                'Update PATH in (leave blank to set manually later)',
                Shell.shellrc())
            if len(shellrc) > 0:
                with open(shellrc, 'a') as f:
                    f.write('PATH={}:$PATH\n'.format(rclone_dir))
            os.environ['PATH'] += ':' + rclone_dir