Skip to content

Commit fc9febe

Browse files
committed
update date of 1.0 version
fixed bug in mirror command so that it really excludes "__pycache__" dirs
1 parent 3dbb82e commit fc9febe

File tree

2 files changed

+91
-59
lines changed

2 files changed

+91
-59
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11-
## [1.0.0] - 2020-01-31
11+
## [1.0.0] - 2020-02-06
1212

1313
### ADDED
1414
- First production release which can be use with the thonny-ev3dev plugin in the Thonny IDE version 3.
1515
- the mirror/cleanup/upload/delete/download commands also allow you to use a subdir of the homedir on the EV3 robot instead
1616
of the homedir.
17+
- the mirror command by default doesn't mirror hidden files and directories. The mirror command has new option "-a" or "--all"
18+
to specify whether hidden files should also be mirrored.
1719
- added mkdir and rmdir commands
1820

1921
### CHANGED

ev3devcmd/__init__.py

Lines changed: 88 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,16 @@ def rexists(sftp, path):
472472
else:
473473
return True
474474

475-
def base_mirror(args,local_path,dest_path,mkdir_dest=False,rmdir_dest=False):
476-
# do extra connect only for nice error message in case of failure (don't want to hack sftpclone library for that)
475+
476+
def base_mirror(args, local_path, dest_subdir, cleanup=False, skip_all_hidden=False ):
477+
478+
dest_path=ev3rootdir(args)
479+
if dest_subdir:
480+
dest_path=dest_path + dest_subdir
481+
482+
483+
484+
# do extra connect only for nice error message in case of failure (don't want to hack sftpclone library for that)
477485
ssh=sshconnect(args)
478486

479487
remote_url=r'{username}:{password}@{server}:{dest_dir}'.format(username=args.username, password=args.password,server=args.address,dest_dir=dest_path)
@@ -483,37 +491,55 @@ def base_mirror(args,local_path,dest_path,mkdir_dest=False,rmdir_dest=False):
483491
sftpclone.logger = sftpclone.configure_logging(level=logging.ERROR)
484492
sync = sftpclone.SFTPClone(local_path,remote_url)
485493

486-
# exclude from syncing the files and dirs in root of sourcedir which start with '.'
487-
import glob
488-
# expand local path to real path => important for getting paths right in exclude list which is compared with the realpath
489-
# note: within sftpclone paths give are also converted to real paths
490-
local_path = os.path.realpath(os.path.expanduser(local_path))
491-
492-
# exclude files/dirs in root of sourcedir starting with '.'
493-
sync.exclude_list = {
494-
g
495-
for g in glob.glob(sftpclone.path_join(local_path, ".*"))
496-
}
494+
# exclude files/dirs starting with '.' in rootdir from syncing/removing
495+
mirroring_into_rootdir = not dest_subdir
496+
if mirroring_into_rootdir:
497+
# 1) exclude from removing the files/dirs starting with '.' in rootdir of destination dir
498+
# REASON: to protect hidden . files and dirs in homedir on ev3 (eg. /home/robot/.bashrc) from removal!
499+
# we implement this by patching sftp.remove and sftp.rmdir
500+
global orig_remove
501+
global base_remote_path
502+
base_remote_path=sync.remote_path
503+
global orig_rmdir
504+
orig_remove=sync.sftp.remove
505+
sync.sftp.remove=new_remove
506+
orig_rmdir=sync.sftp.rmdir
507+
sync.sftp.rmdir=new_rmdir
508+
509+
# 2) exclude from syncing files/dirs starting with '.' in root of sourcedir
510+
# REASON: to protect hidden . files and dirs in homedir on ev3 from be overwritten!
511+
import glob
512+
# expand local path to real path => important for getting paths right in exclude list which is compared with the realpath
513+
# note: within sftpclone paths give are also converted to real paths
514+
local_path = os.path.realpath(os.path.expanduser(local_path))
515+
sync.exclude_list = {
516+
g
517+
for g in glob.glob(sftpclone.path_join(local_path, ".*"))
518+
}
519+
520+
521+
522+
# https://en.wikipedia.org/wiki/Glob_(programming)
523+
# The “**” pattern means “this directory and all subdirectories, recursively”.
524+
# The"**/*" pattern means all files/dirs in “this directory and all subdirectories, recursively”.
525+
# `-> all files/dirs recursively within this directory
526+
# The"**/__pycache__" pattern means all files/dirs recursively within this directory with the exact name "__pycache__"
497527

498528
# exclude directories named __pycache__
529+
# note: if a directory is in the sync.exclude_list then anything below it is also excluded!
530+
# REASON: efficiency!!
499531
from pathlib import Path
500-
for item in Path(local_path).glob( '**/__pycache__'):
501-
sync.exclude_list.add(os.path.join(local_path,item))
502-
503-
global orig_remove
504-
global base_remote_path
505-
base_remote_path=sync.remote_path
506-
507-
global orig_rmdir
508-
509-
510-
orig_remove=sync.sftp.remove
511-
sync.sftp.remove=new_remove
532+
for item in Path(local_path).glob('**/__pycache__'):
533+
sync.exclude_list.add(os.path.realpath(item))
512534

513-
orig_rmdir=sync.sftp.rmdir
514-
sync.sftp.rmdir=new_rmdir
535+
# if we are mirroring then we can skip all hidden files from mirror when skip_all_hidden==True
536+
if (not cleanup) and skip_all_hidden:
537+
for item in Path(local_path).glob( '**/.*'):
538+
sync.exclude_list.add(os.path.realpath(item))
515539

516-
if mkdir_dest and not rexists(sync.sftp,dest_path):
540+
# create destination subdirectory before mirroring if it not yet exists
541+
# note: we skip this when doing a cleanup
542+
if (not cleanup) and dest_subdir and (not rexists(sync.sftp, dest_path)):
517543
try:
518544
sync.sftp.mkdir(dest_path)
519545
except Exception as ex:
@@ -523,7 +549,9 @@ def base_mirror(args,local_path,dest_path,mkdir_dest=False,rmdir_dest=False):
523549

524550
sync.run()
525551

526-
if rmdir_dest:
552+
# if cleanup a directory(mirroring with an empty dir) and destination is a subdirectory
553+
# then we remove the empty directory afterwards
554+
if cleanup and dest_subdir:
527555
try:
528556
sync.sftp.rmdir(dest_path)
529557
except Exception as ex:
@@ -535,43 +563,35 @@ def base_mirror(args,local_path,dest_path,mkdir_dest=False,rmdir_dest=False):
535563
#----------------------------------------------------
536564

537565

566+
def check_subdir(subdir):
567+
if subdir is not None:
568+
if( os.path.isabs(subdir) ):
569+
print("Subdir argument '{0}' is not a relative path".format(subdir),file=sys.stderr)
570+
sys.exit(1)
571+
538572
def mirror(args):
539573

540-
src_path=args.sourcedir
541-
dest_path=ev3rootdir(args)
542-
make_subdir=False
543-
if args.subdir is not None:
544-
if( os.path.isabs(args.subdir) ):
545-
print("Subdir argument '{0}' is not a relative path".format(args.subdir),file=sys.stderr)
546-
sys.exit(1)
547-
dest_path=ev3rootdir(args) + args.subdir
548-
make_subdir=True
574+
dest_subdir= args.subdir
575+
check_subdir(dest_subdir)
549576

577+
src_path=args.sourcedir
578+
skip_all_hidden= not args.all
550579

551580
print("Mirror")
552-
base_mirror(args,src_path,dest_path,mkdir_dest=make_subdir)
581+
base_mirror(args, src_path, dest_subdir,skip_all_hidden=skip_all_hidden)
553582
print("\n\nmirror succesfull")
554583

555584
def cleanup(args):
585+
# cleanup of homedir[/SUBDIR]; other locations are not cleanable (because to dangerous);
556586

557-
# cleanup of homedir; other locations are not cleanable (because to dangerous);
558-
# however we can also only cleanup a subdir of homedir
559-
# note: also removes subdirs in homedir
560-
561-
dest_path=ev3rootdir(args)
562-
remove_subdir=False
563-
if args.subdir is not None:
564-
if( os.path.isabs(args.subdir) ):
565-
print("Subdir argument '{0}' is not a relative path".format(args.subdir),file=sys.stderr)
566-
sys.exit(1)
567-
dest_path=ev3rootdir(args) + args.subdir
568-
remove_subdir=True
587+
dest_subdir= args.subdir
588+
check_subdir(dest_subdir)
569589

570590
import tempfile
571591
src_path=tempfile.mkdtemp()
572592

573593
print("Cleanup")
574-
base_mirror(args,src_path,dest_path,rmdir_dest=remove_subdir)
594+
base_mirror(args, src_path, dest_subdir, cleanup=True)
575595
print("\n\ncleanup succesfull")
576596

577597

@@ -739,19 +759,29 @@ def main(argv=None):
739759
# create the parser for the "clean" command
740760
parser_clean_description='delete all files in homedir[/subdir] on EV3'
741761
parser_clean = subparsers.add_parser('cleanup',description=parser_clean_description, help=parser_clean_description,formatter_class=CustomFormatter)
742-
parser_clean.add_argument('subdir', nargs='?', type=str,help="subdirectory in homedir on EV3; if specified only that directory gets cleaned instead of the whole homedir. Must be relative path.")
762+
parser_clean.add_argument('subdir', nargs='?', type=str,help="subdirectory in homedir on EV3; if specified only that directory gets cleaned instead of the whole homedir. Must be relative path.\nHidden files/dirs starting with '.' in homedir on EV3 are preserved by excluding them from cleanup.")
743763
parser_clean.set_defaults(func=cleanup)
744764
# create the parser for the "mirror" command
765+
# note: help message is shown for general help "ev3dev -h", long description for specific help "ev3dev mirror -h"
766+
parser_mirror_help= "Mirror sourcedir into homedir[/subdir] on EV3. Subdirs within sourcedir are recursively mirrored."
745767
parser_mirror_description= \
746-
'mirror sourcedir into homedir[/subdir] on EV3.\nSubdirs within sourcedir are recursively mirrored.\nFiles/dirs within homedir[/subdir] but not in sourcedir are removed.\n\n'\
768+
"Mirror sourcedir into homedir[/subdir] on EV3.\nSubdirs within sourcedir are recursively mirrored.\n"\
769+
+ "Files/dirs within homedir[/subdir] but not in sourcedir are removed.\n"\
770+
+ "Hidden files/dirs starting with '.' in homedir on EV3 are ALWAYS PRESERVED by excluding mirroring\n"\
771+
+ "of hidden files at the root of the sourcedir into the homedir.\n"\
772+
+ "Directories with the name '__pycache__' are ALWAYS excluded from mirroring.\n"\
773+
+ "\n"\
747774
+ "When uploading a script then the executable is set and a shebang added if not yet there.\n" \
748-
+ "If using mirror on linux/macos then make sure main script is executable and has shebang line,\n" \
749-
+ "or otherwise upload main script separately afterwards.\n" \
750-
+ "If using mirror on windows then upload main script separately afterwards. Note that on windows\n" \
775+
+ "If using mirror on linux/macos then make sure the main script is executable and has a shebang line,\n" \
776+
+ "or otherwise upload the main script separately afterwards.\n" \
777+
+ "If using mirror on windows then upload the main script separately afterwards. Note that on windows\n" \
751778
+ "you cannot set an executable bit on a file.\n"
752-
parser_mirror = subparsers.add_parser('mirror',description=parser_mirror_description, help=parser_mirror_description,formatter_class=CustomFormatter)
779+
parser_mirror = subparsers.add_parser('mirror',description=parser_mirror_description, help=parser_mirror_help,formatter_class=CustomFormatter)
780+
parser_mirror.add_argument('-a', '--all',action='store_true',help="do not exclude hidden files/dirs starting with '.' from mirroring. Hidden files/dirs can only be mirrored within subdirectories. In the root of the home directory of the EV3 they are ALWAYS excluded from mirroring to protect the user's configuration files in his home directory.")
753781
parser_mirror.add_argument('sourcedir', type=str,help="source directory which gets mirrored.")
754782
parser_mirror.add_argument('subdir', nargs='?', type=str,help="subdirectory in homedir on EV3 where it gets mirrored instead of the homedir. Must be relative path.")
783+
784+
755785
parser_mirror.set_defaults(func=mirror)
756786

757787
# create the parser for the "rmdir" command

0 commit comments

Comments
 (0)