def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions) -> None: ddir = cache_dir() os.makedirs(ddir, exist_ok=True) last_used_store_path = os.path.join(ddir, 'remote-file-last-used.txt') try: with open(last_used_store_path) as f: last_used_path = f.read() except FileNotFoundError: last_used_path = tempfile.gettempdir() last_used_file = os.path.join(last_used_path, os.path.basename(remote_path)) print( 'Where do you wish to save the file? Leaving it blank will save it as:', styled(last_used_file, fg='yellow')) print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True)) print() from ..tui.path_completer import PathCompleter try: dest = PathCompleter().input() except (KeyboardInterrupt, EOFError): return if dest: dest = os.path.expandvars(os.path.expanduser(dest)) if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(remote_path)) with open(last_used_store_path, 'w') as f: f.write(os.path.dirname(os.path.abspath(dest))) else: dest = last_used_file if os.path.exists(dest): print(reset_terminal(), end='') print( f'The file {styled(dest, fg="yellow")} already exists. What would you like to do?' ) print( f'{key("O")}verwrite {key("A")}bort Auto {key("R")}ename {key("N")}ew name' ) response = get_key_press('anor', 'a') if response == 'a': return if response == 'n': print(reset_terminal(), end='') return save_as(conn_data, remote_path, cli_opts) if response == 'r': q = dest c = 0 while os.path.exists(q): c += 1 b, ext = os.path.splitext(dest) q = f'{b}-{c}{ext}' dest = q if os.path.dirname(dest): os.makedirs(os.path.dirname(dest), exist_ok=True) with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master: if master.check_hostname_matches(): if not master.download(): show_error('Failed to copy file from remote machine')
def __init__(self, name: Optional[str] = None): self.matches: List[str] = [] self.history_path = None if name: ddir = os.path.join(cache_dir(), 'ask') with suppress(FileExistsError): os.makedirs(ddir) self.history_path = os.path.join(ddir, name)
def __init__(self, name=None): self.matches = [] self.history_path = None if name: ddir = os.path.join(cache_dir(), 'ask') try: os.makedirs(ddir) except FileExistsError: pass self.history_path = os.path.join(ddir, name)
def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple[str, ...]) -> NoReturn: cmd = [ssh_exe()] + ssh_args hostname, remote_args = server_args[0], server_args[1:] if not remote_args: cmd.append('-t') insertion_point = len(cmd) cmd.append('--') cmd.append(hostname) uname = getuser() if hostname.startswith('ssh://'): from urllib.parse import urlparse purl = urlparse(hostname) hostname_for_match = purl.hostname or hostname[6:].split('/', 1)[0] uname = purl.username or uname elif '@' in hostname and hostname[0] != '@': uname, hostname_for_match = hostname.split('@', 1) else: hostname_for_match = hostname hostname_for_match = hostname_for_match.split('@', 1)[-1].split(':', 1)[0] overrides: List[str] = [] literal_env: Dict[str, str] = {} pat = re.compile(r'^([a-zA-Z0-9_]+)[ \t]*=') for i, a in enumerate(found_extra_args): if i % 2 == 1: aq = pat.sub(r'\1 ', a.lstrip()) key = aq.split(maxsplit=1)[0] if key == 'clone_env': literal_env = add_cloned_env(aq.split(maxsplit=1)[1]) elif key != 'hostname': overrides.append(aq) if overrides: overrides.insert(0, f'hostname {uname}@{hostname_for_match}') host_opts = init_config(hostname_for_match, uname, overrides) if host_opts.share_connections: cmd[insertion_point:insertion_point] = connection_sharing_args( int(os.environ['KITTY_PID'])) use_kitty_askpass = host_opts.askpass == 'native' or ( host_opts.askpass == 'unless-set' and 'SSH_ASKPASS' not in os.environ) need_to_request_data = True if use_kitty_askpass: sentinel = os.path.join(cache_dir(), 'openssh-is-new-enough-for-askpass') sentinel_exists = os.path.exists(sentinel) if sentinel_exists or ssh_version() >= (8, 4): if not sentinel_exists: open(sentinel, 'w').close() # SSH_ASKPASS_REQUIRE was introduced in 8.4 release on 2020-09-27 need_to_request_data = False os.environ['SSH_ASKPASS_REQUIRE'] = 'force' os.environ['SSH_ASKPASS'] = os.path.join(shell_integration_dir, 'ssh', 'askpass.py') if need_to_request_data and host_opts.share_connections: cp = subprocess.run(cmd[:1] + ['-O', 'check'] + cmd[1:], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if cp.returncode == 0: # we will use the master connection so SSH does not need to use the tty need_to_request_data = False with restore_terminal_state() as echo_on: rcmd, replacements, shm_name = get_remote_command( remote_args, host_opts, hostname_for_match, uname, echo_on, request_data=need_to_request_data, literal_env=literal_env) cmd += rcmd colors_changed = change_colors(host_opts.color_scheme) try: p = subprocess.Popen(cmd) except FileNotFoundError: raise SystemExit( 'Could not find the ssh executable, is it in your PATH?') else: rq = '' if need_to_request_data else 'id={REQUEST_ID}:pwfile={PASSWORD_FILENAME}:pw={DATA_PASSWORD}'.format( **replacements) with drain_potential_tty_garbage(p, rq): try: raise SystemExit(p.wait()) except KeyboardInterrupt: raise SystemExit(1) finally: if colors_changed: print(end=restore_colors(), flush=True)
def tearDown(self): os.rmdir(cache_dir()) cache_dir.clear_override()
def fetch_themes( name: str = 'kitty-themes', url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master', cache_age: float = 1, ) -> str: now = datetime.datetime.now(datetime.timezone.utc) cache_age_delta = datetime.timedelta(days=cache_age) class Metadata: def __init__(self) -> None: self.etag = '' self.timestamp = now def __str__(self) -> str: return json.dumps({ 'etag': self.etag, 'timestamp': self.timestamp.isoformat() }) dest_path = os.path.join(cache_dir(), f'{name}.zip') m = Metadata() with suppress(Exception), zipfile.ZipFile(dest_path, 'r') as zf: q = json.loads(zf.comment) m.etag = str(q.get('etag') or '') m.timestamp = datetime.datetime.fromisoformat(q['timestamp']) if cache_age < 0 or (now - m.timestamp) < cache_age_delta: return dest_path if cache_age < 0: raise NoCacheFound( 'No local themes cache found and negative cache age specified, aborting' ) rq = Request(url) m.timestamp = now if m.etag: rq.add_header('If-None-Match', m.etag) try: res = urlopen(rq, timeout=30) except HTTPError as e: if m.etag and e.code == http.HTTPStatus.NOT_MODIFIED: set_comment_in_zip_file(dest_path, str(m)) return dest_path raise m.etag = res.headers.get('etag') or '' needs_delete = False try: with tempfile.NamedTemporaryFile( suffix=f'-{os.path.basename(dest_path)}', dir=os.path.dirname(dest_path), delete=False) as f: needs_delete = True shutil.copyfileobj(res, f) f.flush() set_comment_in_zip_file(f.name, str(m)) os.replace(f.name, dest_path) needs_delete = False finally: if needs_delete: os.unlink(f.name) return dest_path
def history_path() -> str: return os.path.join(cache_dir(), 'transfer-ask.history')