From 8c2498fcc9a61d110bf227ac6d7deb794f2b3652 Mon Sep 17 00:00:00 2001 From: maxpat78 Date: Mon, 28 Oct 2024 15:58:32 +0100 Subject: [PATCH] changing directory supported - implement cd command - internal commands fixed to work with relative (vaukt's) dirs - pycryptomator script is now generated in Python's Scripts dir during setup - glob supports setting a root_dir --- pycryptomator/__init__.py | 2 +- pycryptomator/cmshell.py | 83 ++++++++++++++++++++++++++++-------- pycryptomator/cryptomator.py | 47 +++++++++++--------- pyproject.toml | 3 ++ 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/pycryptomator/__init__.py b/pycryptomator/__init__.py index fe6688b..1dfcace 100644 --- a/pycryptomator/__init__.py +++ b/pycryptomator/__init__.py @@ -1,4 +1,4 @@ COPYRIGHT = '''Copyright (C)2024, by maxpat78.''' -__version__ = '1.8' +__version__ = '1.9' __all__ = ["Vault", "init_vault", "backupDirIds"] from .cryptomator import * diff --git a/pycryptomator/cmshell.py b/pycryptomator/cmshell.py index 84a3919..7ee367f 100644 --- a/pycryptomator/cmshell.py +++ b/pycryptomator/cmshell.py @@ -1,4 +1,5 @@ -import cmd, sys, os, glob +import cmd, sys, os +from glob import glob as sysglob from os.path import * from .cryptomator import * @@ -8,6 +9,7 @@ from shlex import split, join + class Options: pass @@ -16,12 +18,15 @@ class CMShell(cmd.Cmd): prompt = 'PCM:> ' vault = None + def _join(*args): return os.path.join(*args).replace('\\','/') + def __init__ (p, vault): p.vault = vault + p.cd = '/' # vault's root is default current directory super(CMShell, p).__init__() def preloop(p): - p.prompt = '%s:> ' % p.vault.base + p.prompt = ':%s$ ' % p.cd def precmd(p, line): #~ print('debug: cmdline=', line) @@ -30,15 +35,27 @@ def precmd(p, line): for arg in split(line): if '?' in arg or '*' in arg: if argl[0] == 'encrypt': - argl += glob.glob(arg) # probably, we want globbing "real" pathnames + argl += sysglob(arg) # probably, we want globbing "real" pathnames else: - argl += p.vault.glob(arg) + argl += p.vault.glob(arg, root_dir=p.cd) else: argl += [arg] line = join(argl) #~ print('debug: final cmdline=', line) return line + def postcmd(p, stop, line): + p.prompt = ':%s$ ' % p.cd + return stop + + def _prep_cd(p, arg): + narg = arg + if arg and arg[0] != '/': + if arg == '.': return p.cd + narg = CMShell._join(p.cd, arg) + narg = os.path.normpath(narg).replace('\\','/') + return narg + def do_quit(p, arg): 'Quit the PyCryptomator Shell' sys.exit(0) @@ -50,7 +67,7 @@ def do_alias(p, arg): print('use: alias ') return for it in argl: - i = p.vault.getInfo(it) + i = p.vault.getInfo(p._prep_cd(it)) print(i.realPathName) def do_backup(p, arg): @@ -60,7 +77,20 @@ def do_backup(p, arg): print('use: backup ') return backupDirIds(p.vault.base, argl[0]) - + + def do_cd(p, arg): + 'Change current vault directory' + argl = split(arg) + if not argl or len(argl) > 1: + print('Use: cd ') + return + narg = p._prep_cd(argl[0]) + x = p.vault.getInfo(narg) + if not x.isDir: + print(narg, 'is not a directory') + return + p.cd = narg + def do_decrypt(p, arg): 'Decrypt files or directories from the vault' argl = split(arg) @@ -74,11 +104,18 @@ def do_decrypt(p, arg): return try: for it in argl[:-1]: - is_dir = p.vault.getInfo(it).isDir + is_dir = p.vault.getInfo(p._prep_cd(it)).isDir if is_dir: - p.vault.decryptDir(it, argl[-1], force, move) + p.vault.decryptDir(p._prep_cd(it), argl[-1], force, move, root_dir=p.cd) else: - p.vault.decryptFile(it, argl[-1], force, move) + dest = argl[-1] + if len(argl) > 2: + if not os.path.isdir(dest): + print('Destination directory %s does not exist!' % dest) + return + dest = CMShell._join(dest, it) + print(dest) + p.vault.decryptFile(p._prep_cd(it), dest, force, move) if argl[-1] == '-': print() except: print(sys.exception()) @@ -94,9 +131,17 @@ def do_encrypt(p, arg): try: for it in argl[:-1]: if isdir(it): - p.vault.encryptDir(it, argl[-1], move=move) + p.vault.encryptDir(it, p._prep_cd(argl[-1]), move=move) else: - p.vault.encryptFile(it, argl[-1], move=move) + dest = p._prep_cd(argl[-1]) + if len(argl) > 2: + x = p.vault.getInfo(dest) + if not x.isDir: + print('Destination directory %s does not exist!' % dest) + return + dest = CMShell._join(dest, it) + print(dest) + p.vault.encryptFile(it, dest, move=move) except: print(sys.exception()) @@ -121,11 +166,12 @@ def do_ls(p, arg): return argl.remove('-s') argl.remove(o.sorting) - if not argl: argl += ['/'] # implicit argument + if not argl: argl += [p.cd] # current directory is the implicit argument if argl[0] == '-h': print('use: ls [-b] [-r] [-s NSDE-!] [...]') return try: + argl = list(map(lambda x:p._prep_cd(x), argl)) p.vault.ls(argl, o) except: print(sys.exception()) @@ -149,7 +195,7 @@ def do_mkdir(p, arg): return for it in argl: try: - p.vault.mkdir(it) + p.vault.mkdir(p._prep_cd(it)) except: print(sys.exception()) @@ -160,7 +206,7 @@ def do_mv(p, arg): print('please use: mv [...] ') return for it in argl[:-1]: - p.vault.mv(it, argl[-1]) + p.vault.mv(p._prep_cd(it), p._prep_cd(argl[-1])) def do_rm(p, arg): 'Remove files and directories' @@ -175,13 +221,14 @@ def do_rm(p, arg): print("Won't erase root directory.") return try: - i = p.vault.getInfo(it) + narg = p._prep_cd(it) + i = p.vault.getInfo(narg) if not i.isDir: - p.vault.remove(it) # del file + p.vault.remove(narg) # del file continue if force: - p.vault.rmtree(it) # del dir, even if nonempty + p.vault.rmtree(narg) # del dir, even if nonempty continue - p.vault.rmdir(it) # del empty dir + p.vault.rmdir(narg) # del empty dir except: print(sys.exception()) diff --git a/pycryptomator/cryptomator.py b/pycryptomator/cryptomator.py index 1eca4f7..eaee617 100644 --- a/pycryptomator/cryptomator.py +++ b/pycryptomator/cryptomator.py @@ -229,7 +229,11 @@ def getInfo(p, virtualpath): def resolveSymlink(p, virtualpath, symlink): src = open(symlink, 'rb') sl = io.BytesIO() - Vault._decryptf(p.pk, src, sl) + try: + Vault._decryptf(p.pk, src, sl) + except: + print("Corrupted symbolic link file") + return (symlink, symlink) sl.seek(0) symlink = target = sl.read().decode() if target[0] != '/': @@ -357,6 +361,10 @@ def decryptFile(p, virtualpath, dest, force=False, move=False): else: if exists(dest) and not force: raise BaseException('destination file "%s" exists and won\'t get overwritten!'%dest) + # creates destination tree if necessary + bn = dirname(dest) + if not exists(bn): + os.makedirs(bn) out = open(dest, 'wb') Vault._decryptf(p.pk, f, out) @@ -371,7 +379,7 @@ def decryptFile(p, virtualpath, dest, force=False, move=False): p.remove(info.pathname) return st.st_size - def decryptDir(p, virtualpath, dest, force=False, move=False): + def decryptDir(p, virtualpath, dest, force=False, move=False, root_dir=None): if (virtualpath[0] != '/'): raise BaseException('the vault path to decrypt must be absolute!') x = p.getInfo(virtualpath) @@ -385,7 +393,9 @@ def decryptDir(p, virtualpath, dest, force=False, move=False): nn+=1 for it in files+dirs: fn = join(root, it) - dn = join(dest, fn[1:]) # target pathname + #~ if root_dir: + dn = join(dest, stripr(fn, root_dir)) # target pathname + #~ dn = join(dest, fn[1:]) # target pathname bn = dirname(dn) # target base dir if not exists(bn): os.makedirs(bn) @@ -668,9 +678,16 @@ def walk(p, virtualpath): "Traverse the virtual file system like os.walk" yield from p._walker(virtualpath, mode='walk') - def glob(p, pathname): + def glob(p, pathname, root_dir=None): "Expand wildcards in pathname returning a list" - return [x for x in p._walker(pathname, mode='glob')] + if root_dir: + L = [] + pathname = join(root_dir, pathname) + for x in p._walker(pathname, mode='glob'): + L +=[stripr(x, root_dir)] + return L + else: + return [x for x in p._walker(pathname, mode='glob')] def iglob(p, pathname): "Expand wildcards in pathname returning a generator" @@ -684,14 +701,6 @@ def _walker(p, pathname, mode='walk'): # pred becomes the exact name base, pred = dirname(pathname) or '/', [basename(pathname)] x = p.getInfo(base) - #~ print('debug: pathname, base, pred',pathname, base, pred) - #~ if mode == 'glob': - #~ if not x.exists: - #~ yield '' - #~ return - #~ if not x.isDir or base == pred: - #~ yield pathname - #~ return realpath = x.realDir dirId = x.dirId root = base @@ -714,13 +723,10 @@ def _walker(p, pathname, mode='walk'): resolved = p.resolveSymlink(join(root, dname), sl) is_dir = False if pred: - #~ print('testing %s against %s' % (dname, pred[0])) if not match(dname, pred[0]): - #~ print('no match') continue # intermediate predicate matches directories only if not is_dir and len(pred) > 1: - #~ print('is file') continue if is_dir: dirs += [dname] else: files += [dname] @@ -732,7 +738,6 @@ def _walker(p, pathname, mode='walk'): else: pred = pred[1:] if not pred: - #~ print('predicate exhausted, building result') for it in dirs+files: yield join(root, it) return @@ -921,12 +926,16 @@ def match(s, p=None): while 1: if i in (len(aa), len(bb)): break if not fnmatch.fnmatch(aa[i], bb[i]): - #~ print ('fnmatch',aa,'against',bb,': does not match') return 0 i+=1 - #~ print ('fnmatch',aa,'against',bb,': matches') return 1 +def stripr(pathname, root): + "Strip 'root' directory from 'pathname'" + i = len(root) + if root[-1] != '/': i+=1 + return pathname[i:] + def calc_rel_path(base, child): "returns the path of base relative to child" base_parts = re.split(r'[\\/]+', abspath(base)) diff --git a/pyproject.toml b/pyproject.toml index 440fc14..58c3046 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ dependencies = [ "Homepage" = "https://github.com/maxpat78/pycryptomator" "Source" = "https://github.com/maxpat78/pycryptomator" +[project.scripts] +pycryptomator = "pycryptomator:__main__" + [tool.setuptools] packages = ["pycryptomator", "pycryptomator.w32lex"] package-data = {"pycryptomator" = ["*.txt"]}