Implemented retry for export db, #569
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.49.8"
|
__version__ = "0.49.9"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from sqlite3 import Error
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any, List, Optional, Tuple, Union
|
from typing import Any, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from tenacity import retry, stop_after_attempt
|
from tenacity import retry, retry_if_not_exception_type, stop_after_attempt
|
||||||
|
|
||||||
from ._constants import OSXPHOTOS_EXPORT_DB
|
from ._constants import OSXPHOTOS_EXPORT_DB
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
@@ -36,7 +36,7 @@ OSXPHOTOS_EXPORTDB_VERSION = "7.1"
|
|||||||
OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}"
|
OSXPHOTOS_ABOUT_STRING = f"Created by osxphotos version {__version__} (https://github.com/RhetTbull/osxphotos) on {datetime.datetime.now()}"
|
||||||
|
|
||||||
# max retry attempts for methods which use tenacity.retry
|
# max retry attempts for methods which use tenacity.retry
|
||||||
MAX_RETRY_ATTEMPTS = 5
|
MAX_RETRY_ATTEMPTS = 3
|
||||||
|
|
||||||
# maximum number of export results rows to save
|
# maximum number of export results rows to save
|
||||||
MAX_EXPORT_RESULTS_DATA_ROWS = 10
|
MAX_EXPORT_RESULTS_DATA_ROWS = 10
|
||||||
@@ -101,6 +101,7 @@ class ExportDB:
|
|||||||
"""returns path to export directory"""
|
"""returns path to export directory"""
|
||||||
return self._path
|
return self._path
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_file_record(self, filename: Union[pathlib.Path, str]) -> "ExportRecord":
|
def get_file_record(self, filename: Union[pathlib.Path, str]) -> "ExportRecord":
|
||||||
"""get info for filename and uuid
|
"""get info for filename and uuid
|
||||||
|
|
||||||
@@ -118,6 +119,10 @@ class ExportDB:
|
|||||||
return ExportRecord(conn, filename_normalized)
|
return ExportRecord(conn, filename_normalized)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||||
|
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||||
|
)
|
||||||
def create_file_record(
|
def create_file_record(
|
||||||
self, filename: Union[pathlib.Path, str], uuid: str
|
self, filename: Union[pathlib.Path, str], uuid: str
|
||||||
) -> "ExportRecord":
|
) -> "ExportRecord":
|
||||||
@@ -136,6 +141,10 @@ class ExportDB:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
return ExportRecord(conn, filename_normalized)
|
return ExportRecord(conn, filename_normalized)
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||||
|
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||||
|
)
|
||||||
def create_or_get_file_record(
|
def create_or_get_file_record(
|
||||||
self, filename: Union[pathlib.Path, str], uuid: str
|
self, filename: Union[pathlib.Path, str], uuid: str
|
||||||
) -> "ExportRecord":
|
) -> "ExportRecord":
|
||||||
@@ -154,25 +163,22 @@ class ExportDB:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
return ExportRecord(conn, filename_normalized)
|
return ExportRecord(conn, filename_normalized)
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_uuid_for_file(self, filename):
|
def get_uuid_for_file(self, filename):
|
||||||
"""query database for filename and return UUID
|
"""query database for filename and return UUID
|
||||||
returns None if filename not found in database
|
returns None if filename not found in database
|
||||||
"""
|
"""
|
||||||
filepath_normalized = self._normalize_filepath_relative(filename)
|
filepath_normalized = self._normalize_filepath_relative(filename)
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"SELECT uuid FROM export_data WHERE filepath_normalized = ?",
|
||||||
"SELECT uuid FROM export_data WHERE filepath_normalized = ?",
|
(filepath_normalized,),
|
||||||
(filepath_normalized,),
|
)
|
||||||
)
|
results = c.fetchone()
|
||||||
results = c.fetchone()
|
return results[0] if results else None
|
||||||
uuid = results[0] if results else None
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
uuid = None
|
|
||||||
return uuid
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_files_for_uuid(self, uuid: str) -> List:
|
def get_files_for_uuid(self, uuid: str) -> List:
|
||||||
"""query database for UUID and return list of files associated with UUID or empty list"""
|
"""query database for UUID and return list of files associated with UUID or empty list"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
@@ -184,33 +190,30 @@ class ExportDB:
|
|||||||
results = c.fetchall()
|
results = c.fetchall()
|
||||||
return [os.path.join(self.export_dir, r[0]) for r in results]
|
return [os.path.join(self.export_dir, r[0]) for r in results]
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_photoinfo_for_uuid(self, uuid):
|
def get_photoinfo_for_uuid(self, uuid):
|
||||||
"""returns the photoinfo JSON struct for a UUID"""
|
"""returns the photoinfo JSON struct for a UUID"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute("SELECT photoinfo FROM photoinfo WHERE uuid = ?", (uuid,))
|
||||||
c.execute("SELECT photoinfo FROM photoinfo WHERE uuid = ?", (uuid,))
|
results = c.fetchone()
|
||||||
results = c.fetchone()
|
return results[0] if results else None
|
||||||
info = results[0] if results else None
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
info = None
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||||
|
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||||
|
)
|
||||||
def set_photoinfo_for_uuid(self, uuid, info):
|
def set_photoinfo_for_uuid(self, uuid, info):
|
||||||
"""sets the photoinfo JSON struct for a UUID"""
|
"""sets the photoinfo JSON struct for a UUID"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);",
|
||||||
"INSERT OR REPLACE INTO photoinfo(uuid, photoinfo) VALUES (?, ?);",
|
(uuid, info),
|
||||||
(uuid, info),
|
)
|
||||||
)
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_target_for_file(
|
def get_target_for_file(
|
||||||
self, uuid: str, filename: Union[str, pathlib.Path]
|
self, uuid: str, filename: Union[str, pathlib.Path]
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
@@ -235,60 +238,62 @@ class ExportDB:
|
|||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
filepath_normalized = os.path.splitext(result[2])[0]
|
filepath_normalized = os.path.splitext(result[2])[0]
|
||||||
if re.match(re.escape(filepath_stem) + r"(\s\(\d+\))?$", filepath_normalized):
|
if re.match(
|
||||||
|
re.escape(filepath_stem) + r"(\s\(\d+\))?$", filepath_normalized
|
||||||
|
):
|
||||||
return os.path.join(self.export_dir, result[1])
|
return os.path.join(self.export_dir, result[1])
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_previous_uuids(self):
|
def get_previous_uuids(self):
|
||||||
"""returns list of UUIDs of previously exported photos found in export database"""
|
"""returns list of UUIDs of previously exported photos found in export database"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
previous_uuids = []
|
previous_uuids = []
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute("SELECT DISTINCT uuid FROM export_data")
|
||||||
c.execute("SELECT DISTINCT uuid FROM export_data")
|
results = c.fetchall()
|
||||||
results = c.fetchall()
|
return [row[0] for row in results]
|
||||||
previous_uuids = [row[0] for row in results]
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
return previous_uuids
|
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||||
|
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||||
|
)
|
||||||
def set_config(self, config_data):
|
def set_config(self, config_data):
|
||||||
"""set config in the database"""
|
"""set config in the database"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
dt = datetime.datetime.now().isoformat()
|
||||||
dt = datetime.datetime.now().isoformat()
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);",
|
||||||
"INSERT OR REPLACE INTO config(datetime, config) VALUES (?, ?);",
|
(dt, config_data),
|
||||||
(dt, config_data),
|
)
|
||||||
)
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
||||||
|
retry=retry_if_not_exception_type(sqlite3.IntegrityError),
|
||||||
|
)
|
||||||
def set_export_results(self, results):
|
def set_export_results(self, results):
|
||||||
"""Store export results in database; data is pickled and gzipped for storage"""
|
"""Store export results in database; data is pickled and gzipped for storage"""
|
||||||
|
|
||||||
results_data = pickle_and_zip(results)
|
results_data = pickle_and_zip(results)
|
||||||
|
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
dt = datetime.datetime.now().isoformat()
|
||||||
dt = datetime.datetime.now().isoformat()
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"""
|
||||||
"""
|
UPDATE export_results_data
|
||||||
UPDATE export_results_data
|
SET datetime = ?,
|
||||||
SET datetime = ?,
|
export_results = ?
|
||||||
export_results = ?
|
WHERE datetime = (SELECT MIN(datetime) FROM export_results_data);
|
||||||
WHERE datetime = (SELECT MIN(datetime) FROM export_results_data);
|
""",
|
||||||
""",
|
(dt, results_data),
|
||||||
(dt, results_data),
|
)
|
||||||
)
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_export_results(self, run: int = 0):
|
def get_export_results(self, run: int = 0):
|
||||||
"""Retrieve export results from database
|
"""Retrieve export results from database
|
||||||
|
|
||||||
@@ -304,47 +309,39 @@ class ExportDB:
|
|||||||
run = -run
|
run = -run
|
||||||
|
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute(
|
||||||
|
"""
|
||||||
|
SELECT export_results
|
||||||
|
FROM export_results_data
|
||||||
|
ORDER BY datetime DESC
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
rows = c.fetchall()
|
||||||
try:
|
try:
|
||||||
c = conn.cursor()
|
data = rows[run][0]
|
||||||
c.execute(
|
results = unzip_and_unpickle(data) if data else None
|
||||||
"""
|
except IndexError:
|
||||||
SELECT export_results
|
|
||||||
FROM export_results_data
|
|
||||||
ORDER BY datetime DESC
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
rows = c.fetchall()
|
|
||||||
try:
|
|
||||||
data = rows[run][0]
|
|
||||||
results = unzip_and_unpickle(data) if data else None
|
|
||||||
except IndexError:
|
|
||||||
results = None
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
results = None
|
results = None
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def get_exported_files(self):
|
def get_exported_files(self):
|
||||||
"""Returns tuple of (uuid, filepath) for all paths of all exported files tracked in the database"""
|
"""Returns tuple of (uuid, filepath) for all paths of all exported files tracked in the database"""
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute("SELECT uuid, filepath FROM export_data")
|
||||||
c.execute("SELECT uuid, filepath FROM export_data")
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
while row := c.fetchone():
|
while row := c.fetchone():
|
||||||
yield row[0], os.path.join(self.export_dir, row[1])
|
yield row[0], os.path.join(self.export_dir, row[1])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def close(self):
|
def close(self):
|
||||||
"""close the database connection"""
|
"""close the database connection"""
|
||||||
try:
|
self._conn.close()
|
||||||
self._conn.close()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def _open_export_db(self, dbfile):
|
def _open_export_db(self, dbfile):
|
||||||
"""open export database and return a db connection
|
"""open export database and return a db connection
|
||||||
if dbfile does not exist, will create and initialize the database
|
if dbfile does not exist, will create and initialize the database
|
||||||
@@ -354,8 +351,6 @@ class ExportDB:
|
|||||||
|
|
||||||
if not os.path.isfile(dbfile):
|
if not os.path.isfile(dbfile):
|
||||||
conn = self._get_db_connection(dbfile)
|
conn = self._get_db_connection(dbfile)
|
||||||
if not conn:
|
|
||||||
raise Exception(f"Error getting connection to database {dbfile}")
|
|
||||||
self._create_or_migrate_db_tables(conn)
|
self._create_or_migrate_db_tables(conn)
|
||||||
self.was_created = True
|
self.was_created = True
|
||||||
self.was_upgraded = ()
|
self.was_upgraded = ()
|
||||||
@@ -379,16 +374,12 @@ class ExportDB:
|
|||||||
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def _get_db_connection(self, dbfile):
|
def _get_db_connection(self, dbfile):
|
||||||
"""return db connection to dbname"""
|
"""return db connection to dbname"""
|
||||||
try:
|
return sqlite3.connect(dbfile)
|
||||||
conn = sqlite3.connect(dbfile)
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
conn = None
|
|
||||||
|
|
||||||
return conn
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def _get_database_version(self, conn):
|
def _get_database_version(self, conn):
|
||||||
"""return tuple of (osxphotos, exportdb) versions for database connection conn"""
|
"""return tuple of (osxphotos, exportdb) versions for database connection conn"""
|
||||||
version_info = conn.execute(
|
version_info = conn.execute(
|
||||||
@@ -484,18 +475,15 @@ class ExportDB:
|
|||||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_detected_text on detected_text (uuid);""",
|
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_detected_text on detected_text (uuid);""",
|
||||||
]
|
]
|
||||||
# create the tables if needed
|
# create the tables if needed
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
for cmd in sql_commands:
|
||||||
for cmd in sql_commands:
|
c.execute(cmd)
|
||||||
c.execute(cmd)
|
c.execute(
|
||||||
c.execute(
|
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
||||||
"INSERT INTO version(osxphotos, exportdb) VALUES (?, ?);",
|
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
||||||
(__version__, OSXPHOTOS_EXPORTDB_VERSION),
|
)
|
||||||
)
|
c.execute("INSERT INTO about(about) VALUES (?);", (OSXPHOTOS_ABOUT_STRING,))
|
||||||
c.execute("INSERT INTO about(about) VALUES (?);", (OSXPHOTOS_ABOUT_STRING,))
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
# perform needed migrations
|
# perform needed migrations
|
||||||
if version[1] < "4.3":
|
if version[1] < "4.3":
|
||||||
@@ -524,6 +512,7 @@ class ExportDB:
|
|||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
self._conn.close()
|
self._conn.close()
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def _insert_run_info(self):
|
def _insert_run_info(self):
|
||||||
dt = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
dt = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||||
python_path = sys.executable
|
python_path = sys.executable
|
||||||
@@ -531,16 +520,13 @@ class ExportDB:
|
|||||||
args = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else ""
|
args = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else ""
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
conn = self._conn
|
conn = self._conn
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)",
|
||||||
"INSERT INTO runs (datetime, python_path, script_name, args, cwd) VALUES (?, ?, ?, ?, ?)",
|
(dt, python_path, cmd, args, cwd),
|
||||||
(dt, python_path, cmd, args, cwd),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
def _relative_filepath(self, filepath: Union[str, pathlib.Path]) -> str:
|
def _relative_filepath(self, filepath: Union[str, pathlib.Path]) -> str:
|
||||||
"""return filepath relative to self._path"""
|
"""return filepath relative to self._path"""
|
||||||
@@ -596,184 +582,169 @@ class ExportDB:
|
|||||||
|
|
||||||
def _migrate_4_3_to_5_0(self, conn):
|
def _migrate_4_3_to_5_0(self, conn):
|
||||||
"""Migrate database from version 4.3 to 5.0"""
|
"""Migrate database from version 4.3 to 5.0"""
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
# add metadata column to files to support --force-update
|
||||||
# add metadata column to files to support --force-update
|
c.execute("ALTER TABLE files ADD COLUMN metadata TEXT;")
|
||||||
c.execute("ALTER TABLE files ADD COLUMN metadata TEXT;")
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
def _migrate_5_0_to_6_0(self, conn):
|
def _migrate_5_0_to_6_0(self, conn):
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
|
||||||
|
|
||||||
# add export_data table
|
# add export_data table
|
||||||
c.execute(
|
c.execute(
|
||||||
""" CREATE TABLE IF NOT EXISTS export_data(
|
""" CREATE TABLE IF NOT EXISTS export_data(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
filepath_normalized TEXT NOT NULL,
|
filepath_normalized TEXT NOT NULL,
|
||||||
filepath TEXT NOT NULL,
|
filepath TEXT NOT NULL,
|
||||||
uuid TEXT NOT NULL,
|
uuid TEXT NOT NULL,
|
||||||
src_mode INTEGER,
|
src_mode INTEGER,
|
||||||
src_size INTEGER,
|
src_size INTEGER,
|
||||||
src_mtime REAL,
|
src_mtime REAL,
|
||||||
dest_mode INTEGER,
|
dest_mode INTEGER,
|
||||||
dest_size INTEGER,
|
dest_size INTEGER,
|
||||||
dest_mtime REAL,
|
dest_mtime REAL,
|
||||||
digest TEXT,
|
digest TEXT,
|
||||||
exifdata JSON,
|
exifdata JSON,
|
||||||
export_options INTEGER,
|
export_options INTEGER,
|
||||||
UNIQUE(filepath_normalized)
|
UNIQUE(filepath_normalized)
|
||||||
); """,
|
); """,
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """,
|
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_export_data_filepath_normalized on export_data (filepath_normalized); """,
|
||||||
)
|
)
|
||||||
|
|
||||||
# migrate data
|
# migrate data
|
||||||
c.execute(
|
c.execute(
|
||||||
""" INSERT INTO export_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;""",
|
""" INSERT INTO export_data (filepath_normalized, filepath, uuid) SELECT filepath_normalized, filepath, uuid FROM files;""",
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" UPDATE export_data
|
""" UPDATE export_data
|
||||||
SET (src_mode, src_size, src_mtime) =
|
SET (src_mode, src_size, src_mtime) =
|
||||||
(SELECT mode, size, mtime
|
(SELECT mode, size, mtime
|
||||||
FROM edited
|
FROM edited
|
||||||
WHERE export_data.filepath_normalized = edited.filepath_normalized);
|
WHERE export_data.filepath_normalized = edited.filepath_normalized);
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" UPDATE export_data
|
""" UPDATE export_data
|
||||||
SET (dest_mode, dest_size, dest_mtime) =
|
SET (dest_mode, dest_size, dest_mtime) =
|
||||||
(SELECT orig_mode, orig_size, orig_mtime
|
(SELECT orig_mode, orig_size, orig_mtime
|
||||||
FROM files
|
FROM files
|
||||||
WHERE export_data.filepath_normalized = files.filepath_normalized);
|
WHERE export_data.filepath_normalized = files.filepath_normalized);
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" UPDATE export_data SET digest =
|
""" UPDATE export_data SET digest =
|
||||||
(SELECT metadata FROM files
|
(SELECT metadata FROM files
|
||||||
WHERE files.filepath_normalized = export_data.filepath_normalized
|
WHERE files.filepath_normalized = export_data.filepath_normalized
|
||||||
); """
|
); """
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" UPDATE export_data SET exifdata =
|
""" UPDATE export_data SET exifdata =
|
||||||
(SELECT json_exifdata FROM exifdata
|
(SELECT json_exifdata FROM exifdata
|
||||||
WHERE exifdata.filepath_normalized = export_data.filepath_normalized
|
WHERE exifdata.filepath_normalized = export_data.filepath_normalized
|
||||||
); """
|
); """
|
||||||
)
|
)
|
||||||
|
|
||||||
# create config table
|
# create config table
|
||||||
c.execute(
|
c.execute(
|
||||||
""" CREATE TABLE IF NOT EXISTS config (
|
""" CREATE TABLE IF NOT EXISTS config (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
datetime TEXT,
|
datetime TEXT,
|
||||||
config TEXT
|
config TEXT
|
||||||
); """
|
); """
|
||||||
)
|
)
|
||||||
|
|
||||||
# create photoinfo table
|
# create photoinfo table
|
||||||
c.execute(
|
c.execute(
|
||||||
""" CREATE TABLE IF NOT EXISTS photoinfo (
|
""" CREATE TABLE IF NOT EXISTS photoinfo (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
uuid TEXT NOT NULL,
|
uuid TEXT NOT NULL,
|
||||||
photoinfo JSON,
|
photoinfo JSON,
|
||||||
UNIQUE(uuid)
|
UNIQUE(uuid)
|
||||||
); """
|
); """
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
"""CREATE UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""
|
"""CREATE UNIQUE INDEX IF NOT EXISTS idx_photoinfo_uuid on photoinfo (uuid);"""
|
||||||
)
|
)
|
||||||
c.execute(
|
c.execute(
|
||||||
""" INSERT INTO photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""
|
""" INSERT INTO photoinfo (uuid, photoinfo) SELECT uuid, json_info FROM info;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# drop indexes no longer needed
|
# drop indexes no longer needed
|
||||||
c.execute("DROP INDEX IF EXISTS idx_files_filepath_normalized;")
|
c.execute("DROP INDEX IF EXISTS idx_files_filepath_normalized;")
|
||||||
c.execute("DROP INDEX IF EXISTS idx_exifdata_filename;")
|
c.execute("DROP INDEX IF EXISTS idx_exifdata_filename;")
|
||||||
c.execute("DROP INDEX IF EXISTS idx_edited_filename;")
|
c.execute("DROP INDEX IF EXISTS idx_edited_filename;")
|
||||||
c.execute("DROP INDEX IF EXISTS idx_converted_filename;")
|
c.execute("DROP INDEX IF EXISTS idx_converted_filename;")
|
||||||
c.execute("DROP INDEX IF EXISTS idx_sidecar_filename;")
|
c.execute("DROP INDEX IF EXISTS idx_sidecar_filename;")
|
||||||
c.execute("DROP INDEX IF EXISTS idx_detected_text;")
|
c.execute("DROP INDEX IF EXISTS idx_detected_text;")
|
||||||
|
|
||||||
# drop tables no longer needed
|
# drop tables no longer needed
|
||||||
c.execute("DROP TABLE IF EXISTS files;")
|
c.execute("DROP TABLE IF EXISTS files;")
|
||||||
c.execute("DROP TABLE IF EXISTS info;")
|
c.execute("DROP TABLE IF EXISTS info;")
|
||||||
c.execute("DROP TABLE IF EXISTS exifdata;")
|
c.execute("DROP TABLE IF EXISTS exifdata;")
|
||||||
c.execute("DROP TABLE IF EXISTS edited;")
|
c.execute("DROP TABLE IF EXISTS edited;")
|
||||||
c.execute("DROP TABLE IF EXISTS converted;")
|
c.execute("DROP TABLE IF EXISTS converted;")
|
||||||
c.execute("DROP TABLE IF EXISTS sidecar;")
|
c.execute("DROP TABLE IF EXISTS sidecar;")
|
||||||
c.execute("DROP TABLE IF EXISTS detected_text;")
|
c.execute("DROP TABLE IF EXISTS detected_text;")
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
def _migrate_6_0_to_7_0(self, conn):
|
def _migrate_6_0_to_7_0(self, conn):
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
|
"""CREATE TABLE IF NOT EXISTS export_results_data (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
datetime TEXT,
|
||||||
|
export_results BLOB
|
||||||
|
);"""
|
||||||
|
)
|
||||||
|
# pre-populate report_data table with blank fields
|
||||||
|
# ExportDB will use these as circular buffer always writing to the oldest record
|
||||||
|
for _ in range(MAX_EXPORT_RESULTS_DATA_ROWS):
|
||||||
c.execute(
|
c.execute(
|
||||||
"""CREATE TABLE IF NOT EXISTS export_results_data (
|
"""INSERT INTO export_results_data (datetime, export_results) VALUES (?, ?);""",
|
||||||
id INTEGER PRIMARY KEY,
|
(datetime.datetime.now().isoformat(), b""),
|
||||||
datetime TEXT,
|
|
||||||
export_results BLOB
|
|
||||||
);"""
|
|
||||||
)
|
)
|
||||||
# pre-populate report_data table with blank fields
|
# sleep a tiny bit just to ensure time stamps increment
|
||||||
# ExportDB will use these as circular buffer always writing to the oldest record
|
time.sleep(0.001)
|
||||||
for _ in range(MAX_EXPORT_RESULTS_DATA_ROWS):
|
conn.commit()
|
||||||
c.execute(
|
|
||||||
"""INSERT INTO export_results_data (datetime, export_results) VALUES (?, ?);""",
|
|
||||||
(datetime.datetime.now().isoformat(), b""),
|
|
||||||
)
|
|
||||||
# sleep a tiny bit just to ensure time stamps increment
|
|
||||||
time.sleep(0.001)
|
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
def _migrate_7_0_to_7_1(self, conn):
|
def _migrate_7_0_to_7_1(self, conn):
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute("""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;""")
|
||||||
c.execute("""ALTER TABLE export_data ADD COLUMN timestamp DATETIME;""")
|
c.execute(
|
||||||
c.execute(
|
"""
|
||||||
"""
|
CREATE TRIGGER insert_timestamp_trigger
|
||||||
CREATE TRIGGER insert_timestamp_trigger
|
AFTER INSERT ON export_data
|
||||||
AFTER INSERT ON export_data
|
BEGIN
|
||||||
BEGIN
|
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
||||||
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
END;
|
||||||
END;
|
"""
|
||||||
"""
|
)
|
||||||
)
|
c.execute(
|
||||||
c.execute(
|
"""
|
||||||
"""
|
CREATE TRIGGER update_timestamp_trigger
|
||||||
CREATE TRIGGER update_timestamp_trigger
|
AFTER UPDATE On export_data
|
||||||
AFTER UPDATE On export_data
|
BEGIN
|
||||||
BEGIN
|
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
||||||
UPDATE export_data SET timestamp = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') WHERE id = NEW.id;
|
END;
|
||||||
END;
|
"""
|
||||||
"""
|
)
|
||||||
)
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
def _perform_db_maintenace(self, conn):
|
def _perform_db_maintenace(self, conn):
|
||||||
"""Perform database maintenance"""
|
"""Perform database maintenance"""
|
||||||
try:
|
c = conn.cursor()
|
||||||
c = conn.cursor()
|
c.execute(
|
||||||
c.execute(
|
"""DELETE FROM config
|
||||||
"""DELETE FROM config
|
WHERE id < (
|
||||||
WHERE id < (
|
SELECT MIN(id)
|
||||||
SELECT MIN(id)
|
FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)
|
||||||
FROM (SELECT id FROM config ORDER BY id DESC LIMIT 9)
|
);
|
||||||
);
|
"""
|
||||||
"""
|
)
|
||||||
)
|
conn.commit()
|
||||||
conn.commit()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
|
||||||
class ExportDBInMemory(ExportDB):
|
class ExportDBInMemory(ExportDB):
|
||||||
@@ -825,14 +796,13 @@ class ExportDBInMemory(ExportDB):
|
|||||||
conn_on_disk.commit()
|
conn_on_disk.commit()
|
||||||
conn_on_disk.close()
|
conn_on_disk.close()
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def close(self):
|
def close(self):
|
||||||
"""close the database connection"""
|
"""close the database connection"""
|
||||||
try:
|
if self._conn:
|
||||||
if self._conn:
|
self._conn.close()
|
||||||
self._conn.close()
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
|
@retry(stop=stop_after_attempt(MAX_RETRY_ATTEMPTS))
|
||||||
def _open_export_db(self, dbfile): # sourcery skip: raise-specific-error
|
def _open_export_db(self, dbfile): # sourcery skip: raise-specific-error
|
||||||
"""open export database and return a db connection
|
"""open export database and return a db connection
|
||||||
returns: connection to the database
|
returns: connection to the database
|
||||||
@@ -866,13 +836,7 @@ class ExportDBInMemory(ExportDB):
|
|||||||
|
|
||||||
def _get_db_connection(self):
|
def _get_db_connection(self):
|
||||||
"""return db connection to in memory database"""
|
"""return db connection to in memory database"""
|
||||||
try:
|
return sqlite3.connect(":memory:")
|
||||||
conn = sqlite3.connect(":memory:")
|
|
||||||
except Error as e:
|
|
||||||
logging.warning(e)
|
|
||||||
conn = None
|
|
||||||
|
|
||||||
return conn
|
|
||||||
|
|
||||||
def _dump_db(self, conn: sqlite3.Connection) -> StringIO:
|
def _dump_db(self, conn: sqlite3.Connection) -> StringIO:
|
||||||
"""dump sqlite db to a string buffer"""
|
"""dump sqlite db to a string buffer"""
|
||||||
|
|||||||
Reference in New Issue
Block a user