Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #45 from gilesknap/feature
Browse files Browse the repository at this point in the history
Feature
  • Loading branch information
gilesknap authored Jan 27, 2019
2 parents 6efd1b7 + e8e50ac commit 3b4c427
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 218 deletions.
10 changes: 4 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
|build_status|
|coverage|
|codacy|


==================

.. image:: https://api.codacy.com/project/badge/Grade/5a5b8c359800462e90ee2ba21a969f87
:alt: Codacy Badge
:target: https://app.codacy.com/app/giles.knap/gphotos-sync?utm_source=github.com&utm_medium=referral&utm_content=gilesknap/gphotos-sync&utm_campaign=Badge_Grade_Dashboard
Google Photos Sync
==================

Expand Down Expand Up @@ -129,3 +124,6 @@ For a description of additional command line parameters type::
:target: https://codecov.io/gh/gilesknap/gphotos-sync
:alt: Test coverage

.. |codacy| image:: https://api.codacy.com/project/badge/Grade/5a5b8c359800462e90ee2ba21a969f87
:alt: Codacy Badge
:target: https://app.codacy.com/app/giles.knap/gphotos-sync?utm_source=github.com&utm_medium=referral&utm_content=gilesknap/gphotos-sync&utm_campaign=Badge_Grade_Dashboard
5 changes: 3 additions & 2 deletions gphotos/BadIds.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
log = logging.getLogger(__name__)

''' keeps a list of media items with ID in a YAML file. The YAML file
allows a user to easily investigate their list of media items that have
allows a user to easily investigate their list of media items that have
failed to download '''


Expand All @@ -35,14 +35,15 @@ def store_ids(self):
with open(self.bad_ids_filename, 'w') as stream:
dump(self.items, stream, Dumper=Dumper, default_flow_style=False)

def add_id(self, path, gid, product_url):
def add_id(self, path, gid, product_url, e):
item = {
'path': path,
'gid': gid,
'product_url': product_url
}
self.ids.append(gid)
self.items.append(item)
log.debug('BAD ID %s for %s', gid, path, exc_info=e)

def check_id_ok(self, gid):
if gid in self.ids:
Expand Down
65 changes: 19 additions & 46 deletions gphotos/BaseMedia.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import re
from time import gmtime, strftime
from datetime import datetime

from .LocalData import LocalData

from gphotos.LocalData import LocalData
from enum import IntEnum


Expand All @@ -20,7 +18,6 @@ class MediaType(IntEnum):


# base class for media model classes
# noinspection PyCompatibility
class BaseMedia(object):
MEDIA_TYPE = MediaType.NONE
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
Expand All @@ -36,7 +33,7 @@ def __init__(self, **k_args):
fix_windows = re.compile(r'[<>:"/\\|?*]|[\x00-\x1f]|\x7f|\x00')
fix_windows_ending = re.compile('([ .]+$)')

def validate_encoding(self, s):
def validate_encoding(self, s: str) -> str:
"""
makes sure a string is valid for creating file names and converts to
unicode assuming utf8 encoding if necessary
Expand All @@ -51,7 +48,7 @@ def validate_encoding(self, s):
s = self.fix_linux.sub('_', s)
return s

def save_to_db(self, db, update=False):
def save_to_db(self, db: LocalData, update: bool = False) -> int:
now_time = strftime(BaseMedia.TIME_FORMAT, gmtime())
new_row = LocalData.SyncRow.make(RemoteId=self.id, Url=self.url,
Path=self.relative_folder,
Expand All @@ -69,23 +66,23 @@ def save_to_db(self, db, update=False):
Downloaded=0)
return db.put_file(new_row, update)

def set_path_by_date(self, root):
def set_path_by_date(self, root: str):
y = "{:04d}".format(self.create_date.year)
m = "{:02d}".format(self.create_date.month)
self._relative_folder = os.path.join(root, y, m)

def is_video(self):
def is_video(self) -> bool:
return self.mime_type.startswith('video')

@property
def duplicate_number(self):
def duplicate_number(self) -> int:
return self._duplicate_number

@duplicate_number.setter
def duplicate_number(self, value):
def duplicate_number(self, value: int):
self._duplicate_number = value

def is_indexed(self, db):
def is_indexed(self, db: LocalData) -> LocalData.SyncRow:
# checking for index has the side effect of setting duplicate number as
# it is when we discover if other entries share path and filename
(num, row) = db.file_duplicate_no(self.filename, self.relative_folder,
Expand All @@ -96,16 +93,16 @@ def is_indexed(self, db):
# Relative path to the media file from the root of the sync folder
# e.g. 'Google Photos/2017/09'.
@property
def relative_path(self):
def relative_path(self) -> str:
return os.path.join(self._relative_folder, self.filename)

# as above but without the filename appended
@property
def relative_folder(self):
def relative_folder(self) -> str:
return self._relative_folder

@property
def filename(self):
def filename(self) -> str:
if self.duplicate_number > 0:
base, ext = os.path.splitext(os.path.basename(self.orig_name))
filename = "%(base)s (%(duplicate)d)%(ext)s" % {
Expand All @@ -119,57 +116,33 @@ def filename(self):

# ----- Properties for override below -----
@property
def size(self):
"""
:rtype: int
"""
def size(self) -> int:
raise NotImplementedError

@property
def id(self):
"""
:rtype: int
"""
def id(self) -> str:
raise NotImplementedError

@property
def description(self):
"""
:rtype: str
"""
def description(self) -> str:
raise NotImplementedError

@property
def orig_name(self):
"""
:rtype: str
"""
def orig_name(self) -> str:
raise NotImplementedError

@property
def create_date(self):
"""
:rtype: datetime
"""
def create_date(self) -> datetime:
raise NotImplementedError

@property
def modify_date(self):
"""
:rtype: datetime
"""
def modify_date(self) -> datetime:
raise NotImplementedError

@property
def mime_type(self):
"""
:rtype: str
"""
def mime_type(self) -> str:
raise NotImplementedError

@property
def url(self):
"""
:rtype: str
"""
def url(self) -> str:
raise NotImplementedError
111 changes: 52 additions & 59 deletions gphotos/DatabaseMedia.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
# coding: utf8
from datetime import datetime

from .BaseMedia import BaseMedia, MediaType
from .LocalData import LocalData
from gphotos.BaseMedia import BaseMedia, MediaType
from gphotos.LocalData import LocalData
from typing import TypeVar

D = TypeVar('D', bound='DatabaseMedia')


class DatabaseMedia(BaseMedia):
Expand All @@ -13,30 +16,26 @@ class DatabaseMedia(BaseMedia):
from the database using one of the two factory class methods.
Attributes:
_id (str): remote identifier from picasa or drive
_filename (str):
_orig_name (str):
_duplicate_number (int):
_date (datetime):
_description (str):
_size (int):
_create_date (datetime):
_media_type (int):
_sym_link (int):
_id: remote identifier from Google Photos
_filename:
_orig_name:
_duplicate_number:
_date:
_description:
_size:
_create_date:
_media_type:
_sym_link:
"""
MEDIA_TYPE = MediaType.DATABASE

# noinspection PyUnresolvedReferences
def __init__(self, row):
def __init__(self, row: LocalData.SyncRow):
"""
This constructor is kept in sync with changes to the SyncFiles table
:param (str) root_folder: the root of the sync folder in which
the database file is created and below which the synced files are
stored
:param (LocalData.SyncRow) row: a tuple containing a value for each
column in the SyncFiles table.
Args:
row: a tuple containing a value for each column in the SyncFiles
table.
"""
super(DatabaseMedia, self).__init__()

Expand All @@ -62,34 +61,42 @@ def __init__(self, row):
self._id = None

@classmethod
def get_media_by_filename(cls, folder, name, db):
def get_media_by_filename(cls, folder: str, name: str, db: LocalData) -> D:
"""
A factory method for finding a single row in the SyncFile table by
full path to filename and instantiate a DataBaseMedia object to
represent it
:param (str) folder : the root relative path to the file to find
:param (str) name: the name of the file to find
:param (LocalData) db: the database wrapper object
:return:
Args:
folder : the root relative path to the file to find
name: the name of the file to find
db: the database wrapper object
"""
data_tuple = db.get_file_by_path(folder, name)
return DatabaseMedia(data_tuple)

@classmethod
def get_media_by_search(cls, db, remote_id='%', media_type='%',
start_date=None, end_date=None, skip_linked=False,
skip_downloaded=False):
def get_media_by_search(cls, db: LocalData, remote_id: str = '%',
media_type: MediaType = '%',
start_date: datetime = None,
end_date: datetime = None,
skip_linked: bool = False,
skip_downloaded: bool = False):
"""
A factory method to find any number of rows in SyncFile and yield an
iterator of DataBaseMedia objects representing the results
:param (LocalData) db: the database wrapper object
:param (str) remote_id: optional id of row to find
:param (int) media_type: optional type of rows to find
:param (datetime) start_date: optional date filter
:param (datetime) end_date: optional date filter
:param (bool) skip_linked: skip files with non-null SymLink
:param (bool) skip_downloaded: skip files with downloaded=1
:returns (GoogleMedia): yields GoogleMedia object filled from database
Args:
db: the database wrapper object
remote_id: optional id of row to find
media_type: optional type of rows to find
start_date: optional date filter
end_date: optional date filter
skip_linked: skip files with non-null SymLink
skip_downloaded: skip files with downloaded=1
Returns:
yields GoogleMedia object filled from database
"""
for record in db.get_files_by_search(
remote_id, media_type, start_date, end_date, skip_linked,
Expand All @@ -99,69 +106,55 @@ def get_media_by_search(cls, db, remote_id='%', media_type='%',

# ----- GoogleMedia base class override Properties below -----
@property
def size(self):
"""
The size of the file
:return int:
"""
def size(self) -> int:
return self._size

@property
def mime_type(self):
def mime_type(self) -> str:
return self._mimeType

@property
def id(self):
"""
The remote id of the server copy of the file
:return (str):
"""
def id(self) -> str:
return self._id

@property
def description(self):
def description(self) -> str:
"""
The description of the file
:return (str):
"""
return self.validate_encoding(self._description)

@property
def orig_name(self):
def orig_name(self) -> str:
"""
Original filename before duplicate name handling
:return (str):
"""
return self.validate_encoding(self._orig_name)

@property
def filename(self):
def filename(self)->str:
"""
filename including a suffix to make it unique if duplicates exist
:return (str):
"""
return self.validate_encoding(self._filename)

@property
def create_date(self):
def create_date(self) -> datetime:
"""
Creation date
:return (datetime):
"""
return self._create_date

@property
def modify_date(self):
def modify_date(self) -> datetime:
"""
Modify Date
:return (datetime):
"""
return self._date

@property
def url(self):
def url(self) -> str:
"""
Remote url to retrieve this file from the server
:return (string):
"""
return self._url
Loading

0 comments on commit 3b4c427

Please sign in to comment.