Skip to content

Commit

Permalink
changing directory supported
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
maxpat78 committed Oct 28, 2024
1 parent c626d2f commit 8c2498f
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 38 deletions.
2 changes: 1 addition & 1 deletion pycryptomator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
COPYRIGHT = '''Copyright (C)2024, by maxpat78.'''
__version__ = '1.8'
__version__ = '1.9'
__all__ = ["Vault", "init_vault", "backupDirIds"]
from .cryptomator import *
83 changes: 65 additions & 18 deletions pycryptomator/cmshell.py
Original file line number Diff line number Diff line change
@@ -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 *

Expand All @@ -8,6 +9,7 @@
from shlex import split, join



class Options:
pass

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -50,7 +67,7 @@ def do_alias(p, arg):
print('use: alias <virtual pathname>')
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):
Expand All @@ -60,7 +77,20 @@ def do_backup(p, arg):
print('use: backup <ZIP archive>')
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 <directory>')
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)
Expand All @@ -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())
Expand All @@ -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())

Expand All @@ -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-!] <virtual_path1> [...<virtual_pathN>]')
return
try:
argl = list(map(lambda x:p._prep_cd(x), argl))
p.vault.ls(argl, o)
except:
print(sys.exception())
Expand All @@ -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())

Expand All @@ -160,7 +206,7 @@ def do_mv(p, arg):
print('please use: mv <source> [<source2>...<sourceN>] <destination>')
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'
Expand All @@ -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())
47 changes: 28 additions & 19 deletions pycryptomator/cryptomator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] != '/':
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
Expand Down

0 comments on commit 8c2498f

Please sign in to comment.