Skip to content

Commit

Permalink
Refactor and add comments and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTyton committed Jul 4, 2024
1 parent c32f47f commit 36ebd1c
Show file tree
Hide file tree
Showing 15 changed files with 954 additions and 426 deletions.
124 changes: 79 additions & 45 deletions root/app/calibre_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,129 @@
import os
from subprocess import call

import ff_logging
import tomllib

import ff_logging # Custom logging module for failure logging
import tomllib # Module for parsing TOML files


class CalibreInfo:
"""
This class represents the Calibre library information.
It reads the configuration from a TOML file and provides access to the Calibre library details.
Manages Calibre library information, including paths and credentials, by
reading from a TOML configuration file. This class provides methods to load
configuration from a TOML file, check if Calibre is installed, and generate
command line arguments for Calibre based on the loaded configuration.
"""

def __init__(self, toml_path: str, manager: mp.Manager):
"""
Initialize the CalibreInfo object.
Initializes the CalibreInfo object by loading the Calibre configuration from a
TOML file.
Args:
toml_path (str): The path to the TOML configuration file.
manager (mp.Manager): A multiprocessing Manager instance.
toml_path (str): Path to the TOML configuration file.
manager (mp.Manager): A multiprocessing.Manager object to manage shared
resources like locks.
"""
# Open and load the TOML configuration file
# Load configuration from TOML file
with open(toml_path, "rb") as file:
config = tomllib.load(file)

# Get the 'calibre' section from the configuration
# Extract Calibre configuration section
calibre_config = config.get("calibre", {})

# If the 'path' key is not present in the 'calibre' section, log a failure and raise an exception
# Ensure the Calibre library path is specified
if not calibre_config.get("path"):
message = "Calibre library location not set in the config file. Cannot search the calibre library or update it."
ff_logging.log_failure(message)
raise ValueError(message)
message = "Calibre library location not set in the config file."
ff_logging.log_failure(message) # Log failure using custom logging module
raise ValueError(message) # Raise an exception if the path is not set

# Set the Calibre library details
# Store configuration details
self.location = calibre_config.get("path")
self.username = calibre_config.get("username")
self.password = calibre_config.get("password")
self.default_ini = self._get_ini_file(calibre_config, "default_ini", "defaults.ini")
self.personal_ini = self._get_ini_file(calibre_config, "personal_ini", "personal.ini")
# Create a lock for thread-safe operations
self.lock = manager.Lock()
self.default_ini = self._get_ini_file(
calibre_config, "default_ini", "defaults.ini"
)
self.personal_ini = self._get_ini_file(
calibre_config, "personal_ini", "personal.ini"
)
self.lock = manager.Lock() # Create a lock for thread/process safety

@staticmethod
def _append_filename(path: str, filename: str) -> str:
"""
Append the filename to the path if it's not already there.
Appends the filename to the path if it's not already there.
Args:
path (str): The original path.
filename (str): The filename to append.
path (str): The base path.
filename (str): The filename to append to the path.
Returns:
str: The path with the filename appended.
str: The combined path with the filename appended.
"""
# If the path is not None and does not already end with the filename, append the filename
if path and not path.endswith(filename):
return os.path.join(path, filename)
return os.path.join(
path, filename
) # Use os.path.join to ensure correct path formatting
return path


def _get_ini_file(self, calibre_config: dict, config_key:str, default_filename: str):
ini_file = self._append_filename(calibre_config.get(config_key), default_filename)

def _get_ini_file(
self, calibre_config: dict, config_key: str, default_filename: str
) -> str:
"""
Retrieves the ini file path from the configuration, verifying its existence.
Args:
calibre_config (dict): The Calibre configuration section from the TOML file.
config_key (str): The key in the configuration for the ini file path.
default_filename (str): The default filename to use if the path is not
specified.
Returns:
str: The path to the ini file or an empty string if the file does not exist.
"""
ini_file = self._append_filename(
calibre_config.get(config_key), default_filename
)
if ini_file and not os.path.isfile(ini_file):
ff_logging.log_failure(f"File {ini_file} does not exist.")
ini_file = ""
ff_logging.log_failure(
f"File {ini_file} does not exist."
) # Log failure if file does not exist
return ""
return ini_file

# Check if Calibre is installed
def check_installed(self) -> bool:
@staticmethod
def check_installed() -> bool:
"""
Checks if Calibre is installed by attempting to call calibredb.
Returns:
bool: True if Calibre is installed, False otherwise.
"""
try:
# Try to call calibredb
with open(os.devnull, "w") as nullout:
call(["calibredb"], stdout=nullout, stderr=nullout)
return True
except OSError:
# If calibredb is not found, log a failure and return False
ff_logging.log_failure(
"Calibredb is not installed on this system. Cannot search the calibre library or update it."
)
# Log failure if OSError is caught
ff_logging.log_failure("Calibredb is not installed on this system.")
return False
except Exception as e:
# If any other error occurs, log a failure
ff_logging.log_failure(f"Some other issue happened. {e}")
# Log any other exceptions
ff_logging.log_failure(f"Error checking Calibre installation: {e}")
return False

# String representation of the object
def __str__(self):
repr = f' --with-library "{self.location}"'
def __str__(self) -> str:
"""
Provides a string representation of the CalibreInfo object for command line
arguments.
Returns:
str: A string for command line arguments specifying Calibre library details.
"""
repr = f' --with-library "{self.location}"' # Include library location
if self.username:
repr += f' --username "{self.username}"'
repr += f' --username "{self.username}"' # Include username if specified
if self.password:
repr += f' --password "{self.password}"'
return repr
repr += f' --password "{self.password}"' # Include password if specified
return repr
140 changes: 140 additions & 0 deletions root/app/calibredb_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import fanfic_info
import calibre_info
import ff_logging
import regex_parsing
import system_utils
from subprocess import call, PIPE, DEVNULL


def call_calibre_db(
command: str,
calibre_info: calibre_info.CalibreInfo,
fanfic_info: fanfic_info.FanficInfo = None,
):
"""
Calls the calibre database with a specific command.
Args:
command (str): The command to be executed on the calibre database.
calibre_info (calibre_info.CalibreInfo): The calibre information object.
fanfic_info (fanfic_info.FanficInfo): The fanfic information object.
Returns:
None
"""
try:
# Lock the calibre database to prevent concurrent modifications
with calibre_info.lock:
# Call the calibre command line tool with the specified command\
call(
f"calibredb {command} {fanfic_info.calibre_id if fanfic_info else ''} {calibre_info}",
shell=True,
stdin=PIPE,
stdout=DEVNULL,
stderr=DEVNULL,
)
except Exception as e:
# Log any failures
ff_logging.log_failure(
f'\t"{command} {fanfic_info.calibre_id if fanfic_info else ""} {calibre_info}" failed: {e}'
)


def export_story(
*,
fanfic_info: fanfic_info.FanficInfo,
location: str,
calibre_info: calibre_info.CalibreInfo,
) -> None:
"""
Exports a story from the Calibre library to a specified location.
This function constructs and executes a command to export a story from the
Calibre library, placing the exported file(s) into the specified directory. It
ensures that the cover and OPF files are not saved during the export, and all
files are placed in a single directory.
Args:
fanfic_info (fanfic_info.FanficInfo): An object containing information
about the fanfic to be exported.
location (str): The target directory path where the story should be
exported.
calibre_info (calibre_info.CalibreInfo): An object containing information
about the Calibre library.
Returns:
None: The function does not return any value.
"""
# Construct the command for exporting the story, specifying not to save cover or OPF, and to use a single directory
command = (
f'export --dont-save-cover --dont-write-opf --single-dir --to-dir "{location}"'
)

# Execute the command to export the story from Calibre to the specified location
call_calibre_db(command, calibre_info, fanfic_info)


def remove_story(
fanfic_info: fanfic_info.FanficInfo, calibre_info: calibre_info.CalibreInfo
) -> None:
"""
Removes a story from the Calibre library based on the information provided in
fanfic_info.
This function interfaces with the Calibre database to remove a specific story.
It utilizes the unique identifier or metadata associated with the fanfic_info
object to locate and remove the story from the Calibre library.
Args:
fanfic_info (fanfic_info.FanficInfo): An object containing information
about the fanfic to be removed.
calibre_info (calibre_info.CalibreInfo): An object containing information
about the Calibre library from which the story will be removed.
Returns:
None: This function does not return a value but removes the specified story
from the Calibre library.
"""
# Utilize a helper function to call the Calibre database's "remove" command with the necessary information
call_calibre_db("remove", calibre_info, fanfic_info)


def add_story(
*,
location: str,
fanfic_info: fanfic_info.FanficInfo,
calibre_info: calibre_info.CalibreInfo,
) -> None:
"""
Adds a story to the Calibre library from a specified location.
This function searches for the first EPUB file within the given location and
attempts to add it to the Calibre library. It logs the process and handles
potential errors. The title of the fanfic is updated based on the filename of
the EPUB.
Args:
location (str): The directory where the story file is located.
fanfic_info (fanfic_info.FanficInfo): The fanfic information object.
calibre_info (calibre_info.CalibreInfo): The calibre information object.
Returns:
None
"""
# Find the first EPUB file in the specified location
epub_files = system_utils.get_files(
location, file_extension="epub", return_full_path=True
)
if not epub_files:
ff_logging.log_failure("No EPUB files found in the specified location.")
return

file_to_add = epub_files[0]

# Extract and update the fanfic title from the filename
fanfic_info.title = regex_parsing.extract_filename(file_to_add)

# Log the addition attempt
ff_logging.log(f"\t({fanfic_info.site}) Adding {file_to_add} to Calibre", "OKGREEN")
command = f'add -d {calibre_info} "{file_to_add}"'
call_calibre_db(command, calibre_info, fanfic_info=None)
Loading

0 comments on commit 36ebd1c

Please sign in to comment.