major refactoring
This commit is contained in:
@@ -26,9 +26,7 @@
|
|||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from .grab import grab
|
from .main import main
|
||||||
from .sync import sync
|
|
||||||
from .trash import manage_trash
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-a', '--all', action="store_true")
|
parser.add_argument('-a', '--all', action="store_true")
|
||||||
@@ -38,31 +36,13 @@ parser.add_argument('-t', '--clean-trash', action="store_true")
|
|||||||
parser.add_argument('-u', '--update-card', action="store_true")
|
parser.add_argument('-u', '--update-card', action="store_true")
|
||||||
parser.add_argument('--wipe-card', action="store_true")
|
parser.add_argument('--wipe-card', action="store_true")
|
||||||
opts = parser.parse_args()
|
opts = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
# --all, -a : Perform all actions (except --wipe-card !)
|
# --all, -a : Perform all actions (except --wipe-card !)
|
||||||
if opts.all:
|
if opts.all:
|
||||||
opts.grab = True
|
opts.grab = True
|
||||||
opts.sync = True
|
opts.sync = True
|
||||||
opts.clean_trash = True
|
opts.clean_trash = True
|
||||||
|
|
||||||
# Do not update card if we are going to wipe it anyway.
|
# Do not update card if we are going to wipe it anyway.
|
||||||
if opts.wipe_card:
|
if opts.wipe_card:
|
||||||
opts.update_card = False
|
opts.update_card = False
|
||||||
|
|
||||||
print(opts)
|
print(opts)
|
||||||
|
main(opts)
|
||||||
# --grab, -g : Grab the files
|
|
||||||
if opts.grab:
|
|
||||||
grab()
|
|
||||||
# --sync, -c : Synchronize deleted files
|
|
||||||
# --update-card, -u : Update the card too.
|
|
||||||
if opts.sync:
|
|
||||||
sync(update_card=opts.update_card)
|
|
||||||
# --wipe : Completely wipe the card.
|
|
||||||
if opts.wipe_card:
|
|
||||||
# TODO
|
|
||||||
pass
|
|
||||||
# --clean-trash, -t : Clean the trash.
|
|
||||||
if opts.clean_trash:
|
|
||||||
manage_trash()
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
|
|
||||||
from .config import BACKUP_PATH, TRASH_NAME
|
|
||||||
from .utils import copy_files, get_content
|
|
||||||
|
|
||||||
|
|
||||||
class Backup:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
from .config import BACKUP_CONF
|
|
||||||
self.stores = BACKUP_CONF
|
|
||||||
|
|
||||||
def add(self, files):
|
|
||||||
""" Add a set of files to backup folder """
|
|
||||||
|
|
||||||
def sort_by_ext(content):
|
|
||||||
# Returns separate sets of files with valid extensions
|
|
||||||
return {}
|
|
||||||
|
|
||||||
for ext, files in sort_by_ext(files):
|
|
||||||
backup_path = self.stores[ext]
|
|
||||||
copy_files(files, backup_path)
|
|
||||||
|
|
||||||
|
|
||||||
def list(self, exclude_trash=True):
|
|
||||||
""" List files in backup folders """
|
|
||||||
return get_content(
|
|
||||||
BACKUP_PATH,
|
|
||||||
exclude=TRASH_NAME if exclude_trash else None,
|
|
||||||
)
|
|
||||||
@@ -3,17 +3,8 @@
|
|||||||
import configparser
|
import configparser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
CONFIG = configparser.ConfigParser()
|
||||||
config.read("config.ini")
|
CONFIG.read("config.ini")
|
||||||
|
|
||||||
IMPORT_PATH = Path(config["GLOBAL"]["Import"])
|
IMPORT_PATH = Path(CONFIG["GLOBAL"]["Import"])
|
||||||
CAMERA_PATH = Path(config["GLOBAL"]["Camera"])
|
CAMERA_PATH = Path(CONFIG["GLOBAL"]["Camera"])
|
||||||
BACKUP_CONF = dict()
|
|
||||||
for ext in config.sections():
|
|
||||||
if ext == "GLOBAL":
|
|
||||||
continue
|
|
||||||
path = config[ext].get("Backup", None)
|
|
||||||
if path:
|
|
||||||
BACKUP_CONF[ext] = Path(path)
|
|
||||||
|
|
||||||
TRASH_NAME = ".trash"
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Grab photos from camera and store them.
|
|
||||||
"""
|
|
||||||
from .utils import copy_files, get_content
|
|
||||||
from .config import CAMERA_PATH, IMPORT_PATH
|
|
||||||
from . import backup
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_import(content):
|
|
||||||
copy_files(content, IMPORT_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
def grab():
|
|
||||||
# Get new files from camera
|
|
||||||
camera_files = get_content(CAMERA_PATH)
|
|
||||||
backup_files = backup.list(exclude_trash=False)
|
|
||||||
new_files = camera_files.difference(backup_files)
|
|
||||||
backup.add(new_files)
|
|
||||||
prepare_import(new_files)
|
|
||||||
39
photograb/main.py
Normal file
39
photograb/main.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
from .store import Content, Store, Backup
|
||||||
|
from .config import CONFIG, CAMERA_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def find_deleted():
|
||||||
|
return Content()
|
||||||
|
|
||||||
|
|
||||||
|
def main(opts):
|
||||||
|
camera = Store(CAMERA_PATH)
|
||||||
|
backups = {
|
||||||
|
ext: Backup(CONFIG[ext]['Backup'])
|
||||||
|
for ext in CONFIG.sections()
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.grab: # Grab new files from camera
|
||||||
|
camera_files = camera.read_content()
|
||||||
|
for ext, backup in backups.items():
|
||||||
|
bkp_files = backup.read_content(exclude_trash=False)
|
||||||
|
with_ext = camera_files.filter_by_ext(ext)
|
||||||
|
new_files = with_ext.difference(bkp_files)
|
||||||
|
backup.copy_files(new_files)
|
||||||
|
# prepare_import(new_files)
|
||||||
|
|
||||||
|
if opts.sync: # Synchronize deleted files
|
||||||
|
deleted = find_deleted()
|
||||||
|
for ext, backup in backups.items():
|
||||||
|
backup.trash_content(deleted.filter_by_ext(ext))
|
||||||
|
if opts.update_card:
|
||||||
|
camera.remove_content(deleted)
|
||||||
|
|
||||||
|
if opts.wipe_card: # Wipe the camera memory
|
||||||
|
pass
|
||||||
|
|
||||||
|
if opts.clean_trash: # Clean the bins
|
||||||
|
for _, backup in backups.items():
|
||||||
|
backup.clean_trash()
|
||||||
92
photograb/store.py
Normal file
92
photograb/store.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class Content(dict):
|
||||||
|
|
||||||
|
def difference(self, other):
|
||||||
|
"""
|
||||||
|
Returns a new view containing only items from
|
||||||
|
this instance that are not present in the other
|
||||||
|
"""
|
||||||
|
my_keys = set(self.keys())
|
||||||
|
your_keys = set(other.keys())
|
||||||
|
new_keys = my_keys.difference(your_keys)
|
||||||
|
|
||||||
|
def in_new_keys(item):
|
||||||
|
return item[0] in new_keys
|
||||||
|
|
||||||
|
return Content(filter(in_new_keys, self.items()))
|
||||||
|
|
||||||
|
def filter_by_ext(self, ext):
|
||||||
|
""" Returns a new view containing only items with file extension """
|
||||||
|
ext = ext.lower()
|
||||||
|
|
||||||
|
def has_extension(item):
|
||||||
|
return item[1].suffix[1:].lower() == ext
|
||||||
|
|
||||||
|
return Content(filter(has_extension, self.items()))
|
||||||
|
|
||||||
|
|
||||||
|
class Store:
|
||||||
|
def __init__(self, path):
|
||||||
|
if not isinstance(path, Path):
|
||||||
|
path = Path(path)
|
||||||
|
if not path.exists():
|
||||||
|
path.mkdir()
|
||||||
|
elif not path.is_dir():
|
||||||
|
raise ValueError(f"{path} is not a folder !")
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def read_content(self, exclude=None):
|
||||||
|
content = Content()
|
||||||
|
for path, _, filenames in os.walk(self.path):
|
||||||
|
# Ignore excluded folders
|
||||||
|
if exclude and Path(path).absolute().name == exclude:
|
||||||
|
continue
|
||||||
|
for name in filenames:
|
||||||
|
content[name] = path + "/" + name
|
||||||
|
return content
|
||||||
|
|
||||||
|
def copy_content(self, content):
|
||||||
|
print(f"Copy files to {self.path} : ", end="")
|
||||||
|
count = len(content)
|
||||||
|
start_time = time.process_time()
|
||||||
|
for name, path in content.items():
|
||||||
|
shutil.copy2(str(path), self.path)
|
||||||
|
elapsed = time.process_time() - start_time
|
||||||
|
print(f"{count} in {elapsed:.2f}s.")
|
||||||
|
|
||||||
|
def move_content(self, content):
|
||||||
|
print(f"Move files to {self.path} : ", end="")
|
||||||
|
count = len(content)
|
||||||
|
start_time = time.process_time()
|
||||||
|
for name, path in content.items():
|
||||||
|
shutil.move(str(path), self.path)
|
||||||
|
elapsed = time.process_time() - start_time
|
||||||
|
print(f"{count} in {elapsed:.2f}s.")
|
||||||
|
|
||||||
|
def remove_content(self, content):
|
||||||
|
print("Removing files from {self.path} : ", end="")
|
||||||
|
count = len(content)
|
||||||
|
start_time = time.process_time()
|
||||||
|
for name, path in content.items():
|
||||||
|
shutil.rm(path)
|
||||||
|
elapsed = time.process_time() - start_time
|
||||||
|
print(f"{count} in {elapsed:.2f}s.")
|
||||||
|
|
||||||
|
|
||||||
|
class Backup(Store):
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
super().__init__(path)
|
||||||
|
self.trash = Store(self.path / self.TRASH_NAME)
|
||||||
|
|
||||||
|
def trash_content(self, content):
|
||||||
|
self.trash.move_content(content)
|
||||||
|
|
||||||
|
def clean_trash(self):
|
||||||
|
raise NotImplementedError
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Synchronize files from gallery.
|
|
||||||
|
|
||||||
Mark deleted files from gallery, to be deleted at later time.
|
|
||||||
"""
|
|
||||||
import shutil
|
|
||||||
import time
|
|
||||||
from . import trash
|
|
||||||
from .utils import get_content, Content
|
|
||||||
from .config import CAMERA_PATH
|
|
||||||
|
|
||||||
|
|
||||||
def find_deleted():
|
|
||||||
return Content()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_trashed_from_card():
|
|
||||||
print("Removing trashed files from camera : ", end="")
|
|
||||||
trashed = trash.list()
|
|
||||||
on_card = get_content(CAMERA_PATH)
|
|
||||||
del_count = 0
|
|
||||||
start_time = time.process_time()
|
|
||||||
for name, path in on_card.items():
|
|
||||||
if name in trashed:
|
|
||||||
shutil.rm(path)
|
|
||||||
del_count += 1
|
|
||||||
elapsed = time.process_time() - start_time
|
|
||||||
print(f"{del_count} in {elapsed:.2f}s.")
|
|
||||||
|
|
||||||
|
|
||||||
def sync(update_card=False):
|
|
||||||
deleted = find_deleted()
|
|
||||||
trash.add(deleted)
|
|
||||||
if update_card:
|
|
||||||
remove_trashed_from_card()
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Trash container
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
from .utils import move_files, get_content
|
|
||||||
from .config import BACKUP_PATH, TRASH_NAME
|
|
||||||
|
|
||||||
TRASH_PATH = BACKUP_PATH + TRASH_NAME
|
|
||||||
|
|
||||||
|
|
||||||
def add(files):
|
|
||||||
# Ensure trash folder exists
|
|
||||||
if not os.exists(TRASH_PATH):
|
|
||||||
os.mkdir(TRASH_PATH)
|
|
||||||
move_files(files, TRASH_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
def list():
|
|
||||||
return get_content(TRASH_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
def manage_trash():
|
|
||||||
""" Remove files that have been sitting there for too long """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
import itertools
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
|
|
||||||
def copy_files(content, target_dir):
|
|
||||||
print(f"Copy files to {target_dir} : ", end="")
|
|
||||||
count = len(content)
|
|
||||||
start_time = time.process_time()
|
|
||||||
for name, path in content.items():
|
|
||||||
shutil.copy2(str(path), target_dir)
|
|
||||||
elapsed = time.process_time() - start_time
|
|
||||||
print(f"{count} in {elapsed:.2f}s.")
|
|
||||||
|
|
||||||
|
|
||||||
def move_files(content, target_dir):
|
|
||||||
print(f"Move files to {target_dir} : ", end="")
|
|
||||||
count = len(content)
|
|
||||||
start_time = time.process_time()
|
|
||||||
for name, path in content.items():
|
|
||||||
shutil.move(str(path), target_dir)
|
|
||||||
elapsed = time.process_time() - start_time
|
|
||||||
print(f"{count} in {elapsed:.2f}s.")
|
|
||||||
|
|
||||||
|
|
||||||
class Content(dict):
|
|
||||||
|
|
||||||
def difference(self, other):
|
|
||||||
""" Returns a new dict that only contains items from
|
|
||||||
this instance that are not present in the other
|
|
||||||
"""
|
|
||||||
my_keys = set(self.keys())
|
|
||||||
your_keys = set(other.keys())
|
|
||||||
new_keys = my_keys.difference(your_keys)
|
|
||||||
return dict(filter(lambda i: i[0] in new_keys, self.items()))
|
|
||||||
|
|
||||||
|
|
||||||
def get_content(folder, exclude=None):
|
|
||||||
result = Content()
|
|
||||||
for path, _, filenames in os.walk(folder):
|
|
||||||
# Ignore excluded folders
|
|
||||||
if exclude and Path(path).absolute().name == exclude:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for name in filenames:
|
|
||||||
result[name] = path + "/" + name
|
|
||||||
return result
|
|
||||||
Reference in New Issue
Block a user