parent
							
								
									a477f659f8
								
							
						
					
					
						commit
						896f79861d
					
				
				 1 changed files with 129 additions and 0 deletions
			
			
		@ -0,0 +1,129 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					# Depends on 7z in path | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import libarchive | 
				
			||||||
 | 
					import argparse | 
				
			||||||
 | 
					import pathlib | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_args(argv): | 
				
			||||||
 | 
					    parser = argparse.ArgumentParser(prog=argv[0], description=""" | 
				
			||||||
 | 
					        Patch a folder structure with files from an archive. | 
				
			||||||
 | 
					        This will replace existing files with those of the same name in an archive, | 
				
			||||||
 | 
					        with the option to back up the old versions and generate a script to revert the changes. | 
				
			||||||
 | 
					    """) | 
				
			||||||
 | 
					    parser.add_argument("-p", "--strip", type=int, default=0, help="Strip NUM leading components from archived file names.") | 
				
			||||||
 | 
					    parser.add_argument("-C", "--directory", default=".", help="Operate in <direcory>") | 
				
			||||||
 | 
					    parser.add_argument("-b", "--backup", default=None, help="Create backup copies of overwritten files") | 
				
			||||||
 | 
					    parser.add_argument("-m", "--match", default=None, help="Only extract files matching GLOB", metavar="GLOB") | 
				
			||||||
 | 
					    parser.add_argument("-u", "--uninstall-script", default=os.devnull, help="Filename to save an uninstall-scipt to.", metavar="FILE") | 
				
			||||||
 | 
					    parser.add_argument("-n", "--dry-run", action="store_true", help="Perform a dry run") | 
				
			||||||
 | 
					    parser.add_argument("archive", help="Achive file name") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parser.parse_args(argv[1:]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def makedirs(path, dryrun=False): | 
				
			||||||
 | 
					    if path.is_dir(): | 
				
			||||||
 | 
					        return set() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    created = set() | 
				
			||||||
 | 
					    stack = [path] | 
				
			||||||
 | 
					    while stack: | 
				
			||||||
 | 
					        path = stack[-1] | 
				
			||||||
 | 
					        if path.parent.is_dir(): | 
				
			||||||
 | 
					            if path.exists(): | 
				
			||||||
 | 
					                raise IOError("Exists but not a directory: '%s'" % path) | 
				
			||||||
 | 
					            if dryrun: | 
				
			||||||
 | 
					                return set(stack) | 
				
			||||||
 | 
					            os.mkdir(path) | 
				
			||||||
 | 
					            created.add(stack.pop()) | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					            stack.append(path.parent) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return created | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(argv): | 
				
			||||||
 | 
					    args = parse_args(argv) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    output_path = pathlib.Path(args.directory) | 
				
			||||||
 | 
					    backup_path = pathlib.Path(args.backup) if args.backup else None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    folders = set() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(args.uninstall_script, "w") as us: | 
				
			||||||
 | 
					        # Uninstall Header | 
				
			||||||
 | 
					        if args.uninstall_script != os.devnull: | 
				
			||||||
 | 
					            us.write("#!/bin/sh\n" | 
				
			||||||
 | 
					                    "# Automated patchdir uninstall script\n" | 
				
			||||||
 | 
					                    "# Run from inside patchdir's target directory (-C)\n" | 
				
			||||||
 | 
					                    "remove() {\n" | 
				
			||||||
 | 
					                    "   echo Removing $1\n" | 
				
			||||||
 | 
					                    "   rm \"$1\"\n" | 
				
			||||||
 | 
					                    "}\n\n") | 
				
			||||||
 | 
					            if backup_path: | 
				
			||||||
 | 
					                us.write(("BACKUP_PATH='%s'\n\n" | 
				
			||||||
 | 
					                        "restore() {\n" | 
				
			||||||
 | 
					                        "   echo Restoring $1 from $BACKUP_PATH\n" | 
				
			||||||
 | 
					                        "   mv \"$BACKUP_PATH/$1\" \"$1\"\n" | 
				
			||||||
 | 
					                        "}\n\n") % backup_path.relative_to(output_path)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else: | 
				
			||||||
 | 
					                us.write("remove-unsafe() {\n" | 
				
			||||||
 | 
					                        "   echo Removing $1\n" | 
				
			||||||
 | 
					                        "   rm \"$1\"\n" | 
				
			||||||
 | 
					                        "   echo WARNING: Previously existing file $1 is now missing!\n" | 
				
			||||||
 | 
					                        "}\n\n") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            us.write("\n# Restore files\n") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with libarchive.file_reader(args.archive) as archive: | 
				
			||||||
 | 
					            for entry in archive: | 
				
			||||||
 | 
					                epath = pathlib.PurePath(entry.path) | 
				
			||||||
 | 
					                if args.match and not epath.match(args.match): | 
				
			||||||
 | 
					                    continue | 
				
			||||||
 | 
					                if args.strip: | 
				
			||||||
 | 
					                    epath = pathlib.PurePath(*epath.parts[args.strip:]) | 
				
			||||||
 | 
					                dpath = output_path.joinpath(epath) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if entry.isdir: | 
				
			||||||
 | 
					                    folders |= makedirs(dpath, args.dry_run) | 
				
			||||||
 | 
					                else: | 
				
			||||||
 | 
					                    folders |= makedirs(dpath.parent, args.dry_run) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if dpath.exists(): | 
				
			||||||
 | 
					                        # Backup | 
				
			||||||
 | 
					                        if backup_path: | 
				
			||||||
 | 
					                            print("Backing up existing %s" % epath) | 
				
			||||||
 | 
					                            bpath = backup_path.joinpath(epath) | 
				
			||||||
 | 
					                            folders |= makedirs(bpath.parent, args.dry_run) | 
				
			||||||
 | 
					                            if not args.dry_run: | 
				
			||||||
 | 
					                                os.rename(dpath, bpath) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            us.write("restore '%s'\n" % epath) | 
				
			||||||
 | 
					                        else: | 
				
			||||||
 | 
					                            us.write("remove-unsafe '%s'\n" % epath) | 
				
			||||||
 | 
					                    else: | 
				
			||||||
 | 
					                        us.write("remove '%s'\n" % epath) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    print("Extracting %s" % epath) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if not args.dry_run: | 
				
			||||||
 | 
					                        with open(dpath, "wb") as f: | 
				
			||||||
 | 
					                            for chunk in entry.get_blocks(): | 
				
			||||||
 | 
					                                f.write(chunk) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if args.uninstall_script != os.devnull and folders: | 
				
			||||||
 | 
					            us.write("\n# Remove folders\n") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for dir in sorted(folders, key=lambda x: len(x.parts), reverse=True): | 
				
			||||||
 | 
					                us.write("rmdir '%s'\n" % dir) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					    import sys | 
				
			||||||
 | 
					    sys.exit(main(sys.argv)) | 
				
			||||||
 | 
					
 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue