Added folder support for Photos <= 4, closes #93
This commit is contained in:
@@ -17,8 +17,8 @@ _TESTED_DB_VERSIONS = ["6000", "4025", "4016", "3301", "2622"]
|
||||
_PHOTOS_3_VERSION = "3301"
|
||||
|
||||
# versions 5.0 and later have a different database structure
|
||||
_PHOTOS_4_VERSION = "4025" # latest Mojove version on 10.14.6
|
||||
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.14.4
|
||||
_PHOTOS_4_VERSION = "4025" # latest Mojove version on 10.14.6
|
||||
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.15.4
|
||||
|
||||
# which major version operating systems have been tested
|
||||
_TESTED_OS_VERSIONS = ["12", "13", "14", "15"]
|
||||
@@ -47,3 +47,7 @@ _PHOTOS_5_ALBUM_KIND = 2 # normal user album
|
||||
_PHOTOS_5_SHARED_ALBUM_KIND = 1505 # shared album
|
||||
_PHOTOS_5_FOLDER_KIND = 4000 # user folder
|
||||
_PHOTOS_5_ROOT_FOLDER_KIND = 3999 # root folder
|
||||
|
||||
_PHOTOS_4_ALBUM_KIND = 3 # RKAlbum.albumSubclass
|
||||
_PHOTOS_4_TOP_LEVEL_ALBUM = "TopLevelAlbums"
|
||||
_PHOTOS_4_ROOT_FOLDER = "LibraryFolder"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.28.1"
|
||||
__version__ = "0.28.2"
|
||||
|
||||
@@ -12,7 +12,13 @@ PhotosDB.folders() returns a list of FolderInfo objects
|
||||
|
||||
import logging
|
||||
|
||||
from ._constants import _PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND, _PHOTOS_5_VERSION
|
||||
from ._constants import (
|
||||
_PHOTOS_4_ALBUM_KIND,
|
||||
_PHOTOS_4_TOP_LEVEL_ALBUM,
|
||||
_PHOTOS_4_VERSION,
|
||||
_PHOTOS_5_ALBUM_KIND,
|
||||
_PHOTOS_5_FOLDER_KIND,
|
||||
)
|
||||
|
||||
|
||||
class AlbumInfo:
|
||||
@@ -53,14 +59,13 @@ class AlbumInfo:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders """
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
try:
|
||||
return self._folder_names
|
||||
except AttributeError:
|
||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||
else:
|
||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||
return self._folder_names
|
||||
|
||||
@property
|
||||
@@ -70,10 +75,6 @@ class AlbumInfo:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders """
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
try:
|
||||
return self._folders
|
||||
except AttributeError:
|
||||
@@ -83,19 +84,23 @@ class AlbumInfo:
|
||||
@property
|
||||
def parent(self):
|
||||
""" returns FolderInfo object for parent folder or None if no parent (e.g. top-level album) """
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return None
|
||||
|
||||
try:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||
if parent_pk != self._db._folder_root_pk
|
||||
else None
|
||||
)
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
parent_uuid = self._db._dbalbum_details[self._uuid]["folderUuid"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||
if parent_uuid != _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
else None
|
||||
)
|
||||
else:
|
||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||
if parent_pk != self._db._folder_root_pk
|
||||
else None
|
||||
)
|
||||
return self._parent
|
||||
|
||||
def __len__(self):
|
||||
@@ -112,8 +117,12 @@ class FolderInfo:
|
||||
def __init__(self, db=None, uuid=None):
|
||||
self._uuid = uuid
|
||||
self._db = db
|
||||
self._pk = self._db._dbalbum_details[uuid]["pk"]
|
||||
self._title = self._db._dbalbum_details[uuid]["title"]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
self._pk = None
|
||||
self._title = self._db._dbfolder_details[uuid]["name"]
|
||||
else:
|
||||
self._pk = self._db._dbalbum_details[uuid]["pk"]
|
||||
self._title = self._db._dbalbum_details[uuid]["title"]
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
@@ -131,13 +140,22 @@ class FolderInfo:
|
||||
try:
|
||||
return self._albums
|
||||
except AttributeError:
|
||||
albums = [
|
||||
AlbumInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_ALBUM_KIND
|
||||
and detail["parentfolder"] == self._pk
|
||||
]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
albums = [
|
||||
AlbumInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["albumSubclass"] == _PHOTOS_4_ALBUM_KIND
|
||||
and detail["folderUuid"] == self._uuid
|
||||
]
|
||||
else:
|
||||
albums = [
|
||||
AlbumInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_ALBUM_KIND
|
||||
and detail["parentfolder"] == self._pk
|
||||
]
|
||||
self._albums = albums
|
||||
return self._albums
|
||||
|
||||
@@ -147,12 +165,20 @@ class FolderInfo:
|
||||
try:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||
if parent_pk != self._db._folder_root_pk
|
||||
else None
|
||||
)
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
parent_uuid = self._db._dbfolder_details[self._uuid]["parentFolderUuid"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||
if parent_uuid != _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
else None
|
||||
)
|
||||
else:
|
||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||
if parent_pk != self._db._folder_root_pk
|
||||
else None
|
||||
)
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
@@ -161,13 +187,22 @@ class FolderInfo:
|
||||
try:
|
||||
return self._folders
|
||||
except AttributeError:
|
||||
folders = [
|
||||
FolderInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._pk
|
||||
]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
folders = [
|
||||
FolderInfo(db=self._db, uuid=folder)
|
||||
for folder, detail in self._db._dbfolder_details.items()
|
||||
if not detail["intrash"]
|
||||
and not detail["isMagic"]
|
||||
and detail["parentFolderUuid"] == self._uuid
|
||||
]
|
||||
else:
|
||||
folders = [
|
||||
FolderInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._pk
|
||||
]
|
||||
self._folders = folders
|
||||
return self._folders
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ from mako.template import Template
|
||||
from ._constants import (
|
||||
_MOVIE_TYPE,
|
||||
_PHOTO_TYPE,
|
||||
_PHOTOS_4_VERSION,
|
||||
_PHOTOS_5_SHARED_PHOTO_PATH,
|
||||
_PHOTOS_5_VERSION,
|
||||
_TEMPLATE_DIR,
|
||||
_XMP_TEMPLATE_NAME,
|
||||
)
|
||||
@@ -98,7 +98,7 @@ class PhotoInfo:
|
||||
if self._info["isMissing"] == 1:
|
||||
return photopath # path would be meaningless until downloaded
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
vol = self._info["volume"]
|
||||
if vol is not None:
|
||||
photopath = os.path.join("/Volumes", vol, self._info["imagePath"])
|
||||
@@ -137,7 +137,7 @@ class PhotoInfo:
|
||||
|
||||
photopath = None
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
if self._info["hasAdjustments"]:
|
||||
edit_id = self._info["edit_resource_id"]
|
||||
if edit_id is not None:
|
||||
@@ -269,7 +269,7 @@ class PhotoInfo:
|
||||
# )
|
||||
# return photopath
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
vol = self._info["raw_info"]["volume"]
|
||||
if vol is not None:
|
||||
photopath = os.path.join(
|
||||
@@ -400,7 +400,7 @@ class PhotoInfo:
|
||||
def shared(self):
|
||||
""" returns True if photos is in a shared iCloud album otherwise false
|
||||
Only valid on Photos 5; returns None on older versions """
|
||||
if self._db._db_version >= _PHOTOS_5_VERSION:
|
||||
if self._db._db_version > _PHOTOS_4_VERSION:
|
||||
return self._info["shared"]
|
||||
else:
|
||||
return None
|
||||
@@ -445,7 +445,7 @@ class PhotoInfo:
|
||||
""" Returns True if photo is a cloud asset (in an iCloud library),
|
||||
otherwise False
|
||||
"""
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return (
|
||||
True
|
||||
if self._info["cloudLibraryState"] is not None
|
||||
@@ -488,7 +488,7 @@ class PhotoInfo:
|
||||
If photo is missing, returns None """
|
||||
|
||||
photopath = None
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
if self.live_photo and not self.ismissing:
|
||||
live_model_id = self._info["live_model_id"]
|
||||
if live_model_id == None:
|
||||
@@ -579,7 +579,7 @@ class PhotoInfo:
|
||||
# implementation note: doesn't create the PlaceInfo object until requested
|
||||
# then memoizes the object in self._place to avoid recreating the object
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
try:
|
||||
return self._place # pylint: disable=access-member-before-definition
|
||||
except AttributeError:
|
||||
|
||||
@@ -24,6 +24,8 @@ from ._constants import (
|
||||
_TESTED_DB_VERSIONS,
|
||||
_TESTED_OS_VERSIONS,
|
||||
_UNKNOWN_PERSON,
|
||||
_PHOTOS_4_ALBUM_KIND,
|
||||
_PHOTOS_4_TOP_LEVEL_ALBUM,
|
||||
_PHOTOS_5_ROOT_FOLDER_KIND,
|
||||
_PHOTOS_5_FOLDER_KIND,
|
||||
_PHOTOS_5_ALBUM_KIND,
|
||||
@@ -173,6 +175,9 @@ class PhotosDB:
|
||||
# e.g. {'AA4145F5-098C-496E-9197-B7584958FF9B': {'99D24D3E-59E7-465F-B386-A48A94B00BC1': {'F2246D82-1A12-4994-9654-3DC6FE38A7A8': None}}, }
|
||||
self._dbalbum_folders = {}
|
||||
|
||||
# Dict with information about folders
|
||||
self._dbfolder_details = {}
|
||||
|
||||
# Will hold the primary key of root folder
|
||||
self._folder_root_pk = None
|
||||
|
||||
@@ -312,7 +317,7 @@ class PhotosDB:
|
||||
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """
|
||||
|
||||
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.warning(
|
||||
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||
)
|
||||
@@ -348,33 +353,43 @@ class PhotosDB:
|
||||
@property
|
||||
def folder_info(self):
|
||||
""" return list FolderInfo objects representing top-level folders in the photos database """
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
folders = [
|
||||
FolderInfo(db=self, uuid=album)
|
||||
for album, detail in self._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._folder_root_pk
|
||||
]
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
folders = [
|
||||
FolderInfo(db=self, uuid=folder)
|
||||
for folder, detail in self._dbfolder_details.items()
|
||||
if not detail["intrash"]
|
||||
and not detail["isMagic"]
|
||||
and detail["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
]
|
||||
else:
|
||||
folders = [
|
||||
FolderInfo(db=self, uuid=album)
|
||||
for album, detail in self._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._folder_root_pk
|
||||
]
|
||||
return folders
|
||||
|
||||
@property
|
||||
def folders(self):
|
||||
""" return list of top-level folder names in the photos database """
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
folder_names = [
|
||||
detail["title"]
|
||||
for detail in self._dbalbum_details.values()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._folder_root_pk
|
||||
]
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
folder_names = [
|
||||
folder["name"]
|
||||
for folder in self._dbfolder_details.values()
|
||||
if not folder["intrash"]
|
||||
and not folder["isMagic"]
|
||||
and folder["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
]
|
||||
else:
|
||||
folder_names = [
|
||||
detail["title"]
|
||||
for detail in self._dbalbum_details.values()
|
||||
if not detail["intrash"]
|
||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||
and detail["parentfolder"] == self._folder_root_pk
|
||||
]
|
||||
return folder_names
|
||||
|
||||
@property
|
||||
@@ -395,7 +410,7 @@ class PhotosDB:
|
||||
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """
|
||||
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
|
||||
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.warning(
|
||||
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||
)
|
||||
@@ -434,7 +449,7 @@ class PhotosDB:
|
||||
|
||||
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
|
||||
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.warning(
|
||||
f"album_names_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||
)
|
||||
@@ -538,7 +553,10 @@ class PhotosDB:
|
||||
|
||||
# Get info on albums
|
||||
c.execute(
|
||||
""" select RKAlbum.uuid, RKVersion.uuid from RKAlbum, RKVersion, RKAlbumVersion
|
||||
""" select
|
||||
RKAlbum.uuid,
|
||||
RKVersion.uuid
|
||||
from RKAlbum, RKVersion, RKAlbumVersion
|
||||
where RKAlbum.modelID = RKAlbumVersion.albumId and
|
||||
RKAlbumVersion.versionID = RKVersion.modelId
|
||||
and RKVersion.isInTrash = 0 """
|
||||
@@ -554,17 +572,31 @@ class PhotosDB:
|
||||
|
||||
# now get additional details about albums
|
||||
c.execute(
|
||||
"SELECT "
|
||||
"uuid, " # 0
|
||||
"name, " # 1
|
||||
"cloudLibraryState, " # 2
|
||||
"cloudIdentifier, " # 3
|
||||
"isInTrash " # 4
|
||||
"FROM RKAlbum "
|
||||
""" SELECT
|
||||
uuid,
|
||||
name,
|
||||
cloudLibraryState,
|
||||
cloudIdentifier,
|
||||
isInTrash,
|
||||
folderUuid,
|
||||
albumType,
|
||||
albumSubclass
|
||||
FROM RKAlbum """
|
||||
)
|
||||
|
||||
# Order of results
|
||||
# 0: uuid
|
||||
# 1: name
|
||||
# 2: cloudLibraryState
|
||||
# 3: cloudIdentifier
|
||||
# 4: isInTrash
|
||||
# 5: folderUuid
|
||||
# 6: albumType
|
||||
# 7: albumSubclass -- if 3, normal user album
|
||||
|
||||
for album in c:
|
||||
self._dbalbum_details[album[0]] = {
|
||||
"_uuid": album[0],
|
||||
"title": album[1],
|
||||
"cloudlibrarystate": album[2],
|
||||
"cloudidentifier": album[3],
|
||||
@@ -573,13 +605,71 @@ class PhotosDB:
|
||||
"cloudownerfirstname": None, # Photos 5
|
||||
"cloudownderlastname": None, # Photos 5
|
||||
"cloudownerhashedpersonid": None, # Photos 5
|
||||
"folderUuid": album[5],
|
||||
"albumType": album[6],
|
||||
"albumSubclass": album[7],
|
||||
}
|
||||
|
||||
# get details about folders
|
||||
c.execute(
|
||||
""" SELECT
|
||||
uuid,
|
||||
modelId,
|
||||
name,
|
||||
isMagic,
|
||||
isInTrash,
|
||||
folderType,
|
||||
parentFolderUuid,
|
||||
folderPath
|
||||
FROM RKFolder """
|
||||
)
|
||||
|
||||
# Order of results
|
||||
# 0 uuid,
|
||||
# 1 modelId,
|
||||
# 2 name,
|
||||
# 3 isMagic,
|
||||
# 4 isInTrash,
|
||||
# 5 folderType,
|
||||
# 6 parentFolderUuid,
|
||||
# 7 folderPath
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
self._dbfolder_details[uuid] = {
|
||||
"_uuid": row[0],
|
||||
"modelId": row[1],
|
||||
"name": row[2],
|
||||
"isMagic": row[3],
|
||||
"intrash": row[4],
|
||||
"folderType": row[5],
|
||||
"parentFolderUuid": row[6],
|
||||
"folderPath": row[7],
|
||||
}
|
||||
|
||||
# build _dbalbum_folders in form uuid: [parent uuid] to be consistent with _process_database5
|
||||
for album, details in self._dbalbum_details.items():
|
||||
# album can be in a single folder
|
||||
parent = details["folderUuid"]
|
||||
self._dbalbum_parent_folders[album] = [parent]
|
||||
|
||||
# build folder hierarchy
|
||||
for album, details in self._dbalbum_details.items():
|
||||
parent_folder = details["folderUuid"]
|
||||
if parent_folder != _PHOTOS_4_TOP_LEVEL_ALBUM:
|
||||
# logging.warning(f"album = {details['title']}, parent = {parent_folder}")
|
||||
folder_hierarchy = self._build_album_folder_hierarchy_4(parent_folder)
|
||||
self._dbalbum_folders[album] = folder_hierarchy
|
||||
else:
|
||||
self._dbalbum_folders[album] = {}
|
||||
|
||||
if _debug():
|
||||
logging.debug(f"Finished walking through albums")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
logging.debug(pformat(self._dbfolder_details))
|
||||
|
||||
# Get info on keywords
|
||||
c.execute(
|
||||
@@ -1122,6 +1212,30 @@ class PhotosDB:
|
||||
logging.debug("Burst Photos (dbphotos_burst:")
|
||||
logging.debug(pformat(self._dbphotos_burst))
|
||||
|
||||
def _build_album_folder_hierarchy_4(self, uuid, folders=None):
|
||||
""" recursively build folder/album hierarchy
|
||||
uuid: uuid of the album/folder being processed
|
||||
folders: dict holding the folder hierarchy """
|
||||
|
||||
parent_uuid = self._dbfolder_details[uuid]["parentFolderUuid"]
|
||||
|
||||
# logging.warning(f"uuid = {uuid}, parent = {parent_uuid}, folders = {folders}")
|
||||
|
||||
if parent_uuid is None:
|
||||
return folders
|
||||
|
||||
if parent_uuid == _PHOTOS_4_TOP_LEVEL_ALBUM:
|
||||
# at top of hierarchy, we're done
|
||||
return folders
|
||||
|
||||
# recurse to keep building
|
||||
if not folders:
|
||||
# first time building
|
||||
folders = {uuid: None}
|
||||
folders = {parent_uuid: folders}
|
||||
folders = self._build_album_folder_hierarchy_4(parent_uuid, folders=folders)
|
||||
return folders
|
||||
|
||||
def _process_database5(self):
|
||||
""" process the Photos database to extract info """
|
||||
""" works on Photos version >= 5.0 """
|
||||
@@ -1228,6 +1342,8 @@ class PhotosDB:
|
||||
self._folder_root_pk = self._dbalbum_details[root_uuid[0]]["pk"]
|
||||
|
||||
# build _dbalbum_folders which is in form uuid: [list of parent uuids]
|
||||
# TODO: look at this code...it works but I think I album can only be in a single folder
|
||||
# which means there's a code path that will never get executed
|
||||
for album, details in self._dbalbum_details.items():
|
||||
pk_parent = details["parentfolder"]
|
||||
if pk_parent is None:
|
||||
@@ -1246,10 +1362,10 @@ class PhotosDB:
|
||||
for album, details in self._dbalbum_details.items():
|
||||
# if details["kind"] in [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND]:
|
||||
if details["kind"] == _PHOTOS_5_ALBUM_KIND:
|
||||
folder_hierarchy = self._build_album_folder_hierarchy(album)
|
||||
folder_hierarchy = self._build_album_folder_hierarchy_5(album)
|
||||
self._dbalbum_folders[album] = folder_hierarchy
|
||||
elif details["kind"] == _PHOTOS_5_SHARED_ALBUM_KIND:
|
||||
# shared albums can be in folders
|
||||
# shared albums can't be in folders
|
||||
self._dbalbum_folders[album] = []
|
||||
|
||||
if _debug():
|
||||
@@ -1257,6 +1373,7 @@ class PhotosDB:
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
|
||||
# get details on keywords
|
||||
c.execute(
|
||||
@@ -1747,14 +1864,11 @@ class PhotosDB:
|
||||
logging.debug("Burst Photos (dbphotos_burst:")
|
||||
logging.debug(pformat(self._dbphotos_burst))
|
||||
|
||||
def _build_album_folder_hierarchy(self, uuid, folders=None):
|
||||
def _build_album_folder_hierarchy_5(self, uuid, folders=None):
|
||||
""" recursively build folder/album hierarchy
|
||||
uuid: uuid of the album/folder being processed
|
||||
folders: dict holding the folder hierarchy """
|
||||
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
raise AttributeError("Not yet implemented for this DB version")
|
||||
|
||||
# get parent uuid
|
||||
parent = self._dbalbum_details[uuid]["parentfolder"]
|
||||
|
||||
@@ -1770,10 +1884,50 @@ class PhotosDB:
|
||||
|
||||
# recurse to keep building
|
||||
folders = {parent_uuid: folders}
|
||||
folders = self._build_album_folder_hierarchy(parent_uuid, folders=folders)
|
||||
folders = self._build_album_folder_hierarchy_5(parent_uuid, folders=folders)
|
||||
return folders
|
||||
|
||||
def _album_folder_hierarchy_list(self, album_uuid):
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
return self._album_folder_hierarchy_list_4(album_uuid)
|
||||
else:
|
||||
return self._album_folder_hierarchy_list_5(album_uuid)
|
||||
|
||||
def _album_folder_hierarchy_list_4(self, album_uuid):
|
||||
""" return hierarchical list of folder names album_uuid is contained in
|
||||
the folder list is in form:
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
returns empty list of album is not in any folders """
|
||||
# title = photosdb._dbalbum_details[album_uuid]["title"]
|
||||
folders = self._dbalbum_folders[album_uuid]
|
||||
|
||||
def _recurse_folder_hierarchy(folders, hierarchy=[]):
|
||||
""" recursively walk the folders dict to build list of folder hierarchy """
|
||||
if not folders:
|
||||
# empty folder dict (album has no folder hierarchy)
|
||||
return []
|
||||
|
||||
if len(folders) != 1:
|
||||
raise ValueError("Expected only a single key in folders dict")
|
||||
|
||||
folder_uuid = list(folders)[0] # first and only key of dict
|
||||
|
||||
parent_title = self._dbfolder_details[folder_uuid]["name"]
|
||||
hierarchy.append(parent_title)
|
||||
|
||||
folders = folders[folder_uuid]
|
||||
if folders:
|
||||
# still have elements left to recurse
|
||||
hierarchy = _recurse_folder_hierarchy(folders, hierarchy=hierarchy)
|
||||
return hierarchy
|
||||
|
||||
# no elements left to recurse
|
||||
return hierarchy
|
||||
|
||||
hierarchy = _recurse_folder_hierarchy(folders)
|
||||
return hierarchy
|
||||
|
||||
def _album_folder_hierarchy_list_5(self, album_uuid):
|
||||
""" return hierarchical list of folder names album_uuid is contained in
|
||||
the folder list is in form:
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
@@ -1808,6 +1962,46 @@ class PhotosDB:
|
||||
return hierarchy
|
||||
|
||||
def _album_folder_hierarchy_folderinfo(self, album_uuid):
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
return self._album_folder_hierarchy_folderinfo_4(album_uuid)
|
||||
else:
|
||||
return self._album_folder_hierarchy_folderinfo_5(album_uuid)
|
||||
|
||||
def _album_folder_hierarchy_folderinfo_4(self, album_uuid):
|
||||
""" return hierarchical list of FolderInfo objects album_uuid is contained in
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
returns empty list of album is not in any folders """
|
||||
# title = photosdb._dbalbum_details[album_uuid]["title"]
|
||||
folders = self._dbalbum_folders[album_uuid]
|
||||
# logging.warning(f"uuid = {album_uuid}, folder = {folders}")
|
||||
|
||||
def _recurse_folder_hierarchy(folders, hierarchy=[]):
|
||||
""" recursively walk the folders dict to build list of folder hierarchy """
|
||||
# logging.warning(f"folders={folders},hierarchy = {hierarchy}")
|
||||
if not folders:
|
||||
# empty folder dict (album has no folder hierarchy)
|
||||
return []
|
||||
|
||||
if len(folders) != 1:
|
||||
raise ValueError("Expected only a single key in folders dict")
|
||||
|
||||
folder_uuid = list(folders)[0] # first and only key of dict
|
||||
hierarchy.append(FolderInfo(db=self, uuid=folder_uuid))
|
||||
|
||||
folders = folders[folder_uuid]
|
||||
if folders:
|
||||
# still have elements left to recurse
|
||||
hierarchy = _recurse_folder_hierarchy(folders, hierarchy=hierarchy)
|
||||
return hierarchy
|
||||
|
||||
# no elements left to recurse
|
||||
return hierarchy
|
||||
|
||||
hierarchy = _recurse_folder_hierarchy(folders)
|
||||
# logging.warning(f"hierarchy = {hierarchy}")
|
||||
return hierarchy
|
||||
|
||||
def _album_folder_hierarchy_folderinfo_5(self, album_uuid):
|
||||
""" return hierarchical list of FolderInfo objects album_uuid is contained in
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
returns empty list of album is not in any folders """
|
||||
|
||||
Reference in New Issue
Block a user