bookcli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bookcli-0.1.0/PKG-INFO +198 -0
- bookcli-0.1.0/README.md +184 -0
- bookcli-0.1.0/bookcli/__init__.py +3 -0
- bookcli-0.1.0/bookcli/cache.py +103 -0
- bookcli-0.1.0/bookcli/cli.py +752 -0
- bookcli-0.1.0/bookcli/config.py +72 -0
- bookcli-0.1.0/bookcli/database/__init__.py +32 -0
- bookcli-0.1.0/bookcli/database/migrations.py +94 -0
- bookcli-0.1.0/bookcli/downloader.py +130 -0
- bookcli-0.1.0/bookcli/exceptions.py +34 -0
- bookcli-0.1.0/bookcli/models.py +23 -0
- bookcli-0.1.0/bookcli/opener.py +32 -0
- bookcli-0.1.0/bookcli/providers/__init__.py +1 -0
- bookcli-0.1.0/bookcli/providers/base.py +61 -0
- bookcli-0.1.0/bookcli/providers/google_books.py +202 -0
- bookcli-0.1.0/bookcli/providers/gutenberg.py +211 -0
- bookcli-0.1.0/bookcli/providers/internet_archive.py +266 -0
- bookcli-0.1.0/bookcli/providers/openlibrary.py +256 -0
- bookcli-0.1.0/bookcli/services/__init__.py +1 -0
- bookcli-0.1.0/bookcli/services/history.py +69 -0
- bookcli-0.1.0/bookcli/services/ranking.py +169 -0
- bookcli-0.1.0/bookcli/services/search.py +96 -0
- bookcli-0.1.0/bookcli/settings.py +28 -0
- bookcli-0.1.0/bookcli/utils.py +30 -0
- bookcli-0.1.0/bookcli.egg-info/PKG-INFO +198 -0
- bookcli-0.1.0/bookcli.egg-info/SOURCES.txt +30 -0
- bookcli-0.1.0/bookcli.egg-info/dependency_links.txt +1 -0
- bookcli-0.1.0/bookcli.egg-info/entry_points.txt +2 -0
- bookcli-0.1.0/bookcli.egg-info/requires.txt +6 -0
- bookcli-0.1.0/bookcli.egg-info/top_level.txt +1 -0
- bookcli-0.1.0/pyproject.toml +26 -0
- bookcli-0.1.0/setup.cfg +4 -0
bookcli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bookcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A production-grade Python CLI book search and download application.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: typer>=0.9.0
|
|
9
|
+
Requires-Dist: rich>=13.0.0
|
|
10
|
+
Requires-Dist: httpx>=0.25.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
|
12
|
+
Requires-Dist: rapidfuzz>=3.0.0
|
|
13
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
14
|
+
|
|
15
|
+
# BookCLI
|
|
16
|
+
|
|
17
|
+
BookCLI is a production-ready, clean-architecture command-line interface application built in Python 3.12+. It allows users to search multiple legal book sources concurrently, merge and rank results dynamically using fuzzy string matching, and safely download books when legally provided by the API source.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Concurrent Multi-Provider Search**: Queries Google Books, Open Library, Project Gutenberg, and the Internet Archive concurrently.
|
|
24
|
+
- **Advanced Deduplication & Ranking**: Automatically filters duplicate search entries using fuzzy title and author string comparison via `RapidFuzz` and sorts results based on metadata completeness and direct download availability.
|
|
25
|
+
- **Safe & Legal Downloads**: Only attempts downloads when the source explicitly provides a free/public-domain download link.
|
|
26
|
+
- **Custom Download Paths**: Allows downloading to custom directories or specific files via CLI arguments, search explorer prompts, or global configurations.
|
|
27
|
+
- **Download Resumption**: Supports HTTP range requests to resume interrupted downloads.
|
|
28
|
+
- **Rich Terminal UI**: Displays tabular results, dynamic progress bars, speed, ETA, and styled metadata panel screens utilizing `Rich`.
|
|
29
|
+
- **Offline Mode & Metadata Caching**: Implements local caching in SQLite to preserve previously fetched results, allowing details to be checked offline.
|
|
30
|
+
- **Short Session Indexing**: Supports referring to book search results by their simple short IDs (1, 2, 3...) in follow-up commands like `info`, `download`, `open`, and `favorite`.
|
|
31
|
+
- **Search History & Favorites**: Saves queries and favorites locally.
|
|
32
|
+
- **Configurable Options**: Change default download directory, cache TTL, client timeout, and disable/enable specific providers.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Directory Architecture
|
|
37
|
+
|
|
38
|
+
The project adheres to Clean Architecture principles:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
bookcli/
|
|
42
|
+
│
|
|
43
|
+
├── database/ # Database initialization and migrations
|
|
44
|
+
│ └── migrations.py
|
|
45
|
+
│
|
|
46
|
+
├── providers/ # API clients for external book metadata providers
|
|
47
|
+
│ ├── base.py
|
|
48
|
+
│ ├── google_books.py
|
|
49
|
+
│ ├── gutenberg.py
|
|
50
|
+
│ ├── internet_archive.py
|
|
51
|
+
│ └── openlibrary.py
|
|
52
|
+
│
|
|
53
|
+
├── services/ # Core business services
|
|
54
|
+
│ ├── history.py
|
|
55
|
+
│ ├── ranking.py
|
|
56
|
+
│ └── search.py
|
|
57
|
+
│
|
|
58
|
+
├── cache.py # SQLite metadata cache implementation
|
|
59
|
+
├── cli.py # Typer CLI application entry point and commands
|
|
60
|
+
├── config.py # Pydantic configuration loader and validator
|
|
61
|
+
├── downloader.py # Async downloader with range-resume and Rich progress
|
|
62
|
+
├── exceptions.py # Custom exceptions for uniform error handling
|
|
63
|
+
├── opener.py # OS-specific default application file opener
|
|
64
|
+
├── settings.py # Configuration defaults and directory paths
|
|
65
|
+
└── utils.py # Formatting and filename utilities
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
Ensure you have Python 3.12+ installed. Install BookCLI locally in editable mode:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/your-username/bookcli.git
|
|
76
|
+
cd bookcli
|
|
77
|
+
pip install -e .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Once installed, the CLI tool is available globally as `book`. If the script directory is not on your PATH, you can use the provided script wrappers in the project root:
|
|
81
|
+
- On Windows (CMD/PowerShell): `.\book.bat <command>`
|
|
82
|
+
- On Unix/macOS/Git Bash: `./book <command>`
|
|
83
|
+
- Or directly via Python module: `python -m bookcli.cli <command>`
|
|
84
|
+
|
|
85
|
+
> [!NOTE]
|
|
86
|
+
> On Windows, running `.\book.bat` without any command starts the **Interactive Search Explorer** loop, which also includes a menu option to update your default download directory.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
### 1. Search for Books
|
|
93
|
+
Query multiple providers concurrently. Results will display in a styled table containing short session IDs.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# General search
|
|
97
|
+
book search "Atomic Habits"
|
|
98
|
+
|
|
99
|
+
# Filtered search
|
|
100
|
+
book search "Clean Code" --author "Robert C. Martin"
|
|
101
|
+
book search "Relativity" --subject "Physics"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Interactive Explorer Mode
|
|
105
|
+
When running search inside a terminal (TTY mode), you enter an interactive prompt:
|
|
106
|
+
- Enter `i <ID>` to see details.
|
|
107
|
+
- Enter `f <ID>` to favorite a book.
|
|
108
|
+
- Enter `o <ID>` to open a downloaded book.
|
|
109
|
+
- Enter `<ID>` to download a book to your default download directory.
|
|
110
|
+
- Enter `<ID> -o <path>` or `<ID> --output <path>` to download a book to a **custom path** (e.g. `1 -o C:\downloads` or `1 -o mybook.epub`).
|
|
111
|
+
- Enter `q` to quit.
|
|
112
|
+
|
|
113
|
+
### 2. View Detailed Metadata
|
|
114
|
+
Examine pages, publishers, description, ISBN, and download status of a book by using its short index ID (from your last search) or exact provider ID.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
book info 1
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3. Legal Download with Progress Bar
|
|
121
|
+
Download the book with a progress bar. You can choose to download to your default configured directory or specify a custom path (either a directory or exact file path).
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Download to the default configured download directory
|
|
125
|
+
book download 1
|
|
126
|
+
|
|
127
|
+
# Download to a custom directory (automatically creates it if missing)
|
|
128
|
+
book download 1 --output "/path/to/my_downloads/"
|
|
129
|
+
book download 1 -o "C:\my_downloads\"
|
|
130
|
+
|
|
131
|
+
# Download to a specific custom filename
|
|
132
|
+
book download 1 --output "/path/to/my_books/clean_code.epub"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. Open File in OS Default Viewer
|
|
136
|
+
Open the downloaded book (EPUB, PDF, TXT) immediately in your operating system's default book reader.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
book open 1
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 5. Managing Favorites
|
|
143
|
+
Bookmark books to read later.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Add to favorites
|
|
147
|
+
book favorite add 1
|
|
148
|
+
|
|
149
|
+
# List all favorites
|
|
150
|
+
book favorite list
|
|
151
|
+
|
|
152
|
+
# Remove from favorites
|
|
153
|
+
book favorite remove gutenberg:1342
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 6. Configuration Settings
|
|
157
|
+
List all configuration options or update parameters:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# View configuration
|
|
161
|
+
book config
|
|
162
|
+
|
|
163
|
+
# Set default download directory (can also use 'download-path' alias)
|
|
164
|
+
book config set download-dir "/path/to/downloads"
|
|
165
|
+
book config set download-path "/path/to/downloads"
|
|
166
|
+
|
|
167
|
+
# Set client requests timeout
|
|
168
|
+
book config set timeout 10
|
|
169
|
+
|
|
170
|
+
# Disable a provider (e.g. internet_archive)
|
|
171
|
+
book config set provider false internet-archive
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 7. Cache Management
|
|
175
|
+
View statistics or clear cached metadata:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
book cache stats
|
|
179
|
+
book cache clear
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 8. Query History
|
|
183
|
+
View your search history logs:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
book history
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Testing & Code Quality
|
|
192
|
+
|
|
193
|
+
BookCLI includes a comprehensive test suite of unit and integration tests with **80%+ code coverage**.
|
|
194
|
+
|
|
195
|
+
### Run Tests
|
|
196
|
+
```bash
|
|
197
|
+
python -m pytest --cov=bookcli
|
|
198
|
+
```
|
bookcli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# BookCLI
|
|
2
|
+
|
|
3
|
+
BookCLI is a production-ready, clean-architecture command-line interface application built in Python 3.12+. It allows users to search multiple legal book sources concurrently, merge and rank results dynamically using fuzzy string matching, and safely download books when legally provided by the API source.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Concurrent Multi-Provider Search**: Queries Google Books, Open Library, Project Gutenberg, and the Internet Archive concurrently.
|
|
10
|
+
- **Advanced Deduplication & Ranking**: Automatically filters duplicate search entries using fuzzy title and author string comparison via `RapidFuzz` and sorts results based on metadata completeness and direct download availability.
|
|
11
|
+
- **Safe & Legal Downloads**: Only attempts downloads when the source explicitly provides a free/public-domain download link.
|
|
12
|
+
- **Custom Download Paths**: Allows downloading to custom directories or specific files via CLI arguments, search explorer prompts, or global configurations.
|
|
13
|
+
- **Download Resumption**: Supports HTTP range requests to resume interrupted downloads.
|
|
14
|
+
- **Rich Terminal UI**: Displays tabular results, dynamic progress bars, speed, ETA, and styled metadata panel screens utilizing `Rich`.
|
|
15
|
+
- **Offline Mode & Metadata Caching**: Implements local caching in SQLite to preserve previously fetched results, allowing details to be checked offline.
|
|
16
|
+
- **Short Session Indexing**: Supports referring to book search results by their simple short IDs (1, 2, 3...) in follow-up commands like `info`, `download`, `open`, and `favorite`.
|
|
17
|
+
- **Search History & Favorites**: Saves queries and favorites locally.
|
|
18
|
+
- **Configurable Options**: Change default download directory, cache TTL, client timeout, and disable/enable specific providers.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Directory Architecture
|
|
23
|
+
|
|
24
|
+
The project adheres to Clean Architecture principles:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
bookcli/
|
|
28
|
+
│
|
|
29
|
+
├── database/ # Database initialization and migrations
|
|
30
|
+
│ └── migrations.py
|
|
31
|
+
│
|
|
32
|
+
├── providers/ # API clients for external book metadata providers
|
|
33
|
+
│ ├── base.py
|
|
34
|
+
│ ├── google_books.py
|
|
35
|
+
│ ├── gutenberg.py
|
|
36
|
+
│ ├── internet_archive.py
|
|
37
|
+
│ └── openlibrary.py
|
|
38
|
+
│
|
|
39
|
+
├── services/ # Core business services
|
|
40
|
+
│ ├── history.py
|
|
41
|
+
│ ├── ranking.py
|
|
42
|
+
│ └── search.py
|
|
43
|
+
│
|
|
44
|
+
├── cache.py # SQLite metadata cache implementation
|
|
45
|
+
├── cli.py # Typer CLI application entry point and commands
|
|
46
|
+
├── config.py # Pydantic configuration loader and validator
|
|
47
|
+
├── downloader.py # Async downloader with range-resume and Rich progress
|
|
48
|
+
├── exceptions.py # Custom exceptions for uniform error handling
|
|
49
|
+
├── opener.py # OS-specific default application file opener
|
|
50
|
+
├── settings.py # Configuration defaults and directory paths
|
|
51
|
+
└── utils.py # Formatting and filename utilities
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
Ensure you have Python 3.12+ installed. Install BookCLI locally in editable mode:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/your-username/bookcli.git
|
|
62
|
+
cd bookcli
|
|
63
|
+
pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Once installed, the CLI tool is available globally as `book`. If the script directory is not on your PATH, you can use the provided script wrappers in the project root:
|
|
67
|
+
- On Windows (CMD/PowerShell): `.\book.bat <command>`
|
|
68
|
+
- On Unix/macOS/Git Bash: `./book <command>`
|
|
69
|
+
- Or directly via Python module: `python -m bookcli.cli <command>`
|
|
70
|
+
|
|
71
|
+
> [!NOTE]
|
|
72
|
+
> On Windows, running `.\book.bat` without any command starts the **Interactive Search Explorer** loop, which also includes a menu option to update your default download directory.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### 1. Search for Books
|
|
79
|
+
Query multiple providers concurrently. Results will display in a styled table containing short session IDs.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# General search
|
|
83
|
+
book search "Atomic Habits"
|
|
84
|
+
|
|
85
|
+
# Filtered search
|
|
86
|
+
book search "Clean Code" --author "Robert C. Martin"
|
|
87
|
+
book search "Relativity" --subject "Physics"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Interactive Explorer Mode
|
|
91
|
+
When running search inside a terminal (TTY mode), you enter an interactive prompt:
|
|
92
|
+
- Enter `i <ID>` to see details.
|
|
93
|
+
- Enter `f <ID>` to favorite a book.
|
|
94
|
+
- Enter `o <ID>` to open a downloaded book.
|
|
95
|
+
- Enter `<ID>` to download a book to your default download directory.
|
|
96
|
+
- Enter `<ID> -o <path>` or `<ID> --output <path>` to download a book to a **custom path** (e.g. `1 -o C:\downloads` or `1 -o mybook.epub`).
|
|
97
|
+
- Enter `q` to quit.
|
|
98
|
+
|
|
99
|
+
### 2. View Detailed Metadata
|
|
100
|
+
Examine pages, publishers, description, ISBN, and download status of a book by using its short index ID (from your last search) or exact provider ID.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
book info 1
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Legal Download with Progress Bar
|
|
107
|
+
Download the book with a progress bar. You can choose to download to your default configured directory or specify a custom path (either a directory or exact file path).
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Download to the default configured download directory
|
|
111
|
+
book download 1
|
|
112
|
+
|
|
113
|
+
# Download to a custom directory (automatically creates it if missing)
|
|
114
|
+
book download 1 --output "/path/to/my_downloads/"
|
|
115
|
+
book download 1 -o "C:\my_downloads\"
|
|
116
|
+
|
|
117
|
+
# Download to a specific custom filename
|
|
118
|
+
book download 1 --output "/path/to/my_books/clean_code.epub"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4. Open File in OS Default Viewer
|
|
122
|
+
Open the downloaded book (EPUB, PDF, TXT) immediately in your operating system's default book reader.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
book open 1
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 5. Managing Favorites
|
|
129
|
+
Bookmark books to read later.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Add to favorites
|
|
133
|
+
book favorite add 1
|
|
134
|
+
|
|
135
|
+
# List all favorites
|
|
136
|
+
book favorite list
|
|
137
|
+
|
|
138
|
+
# Remove from favorites
|
|
139
|
+
book favorite remove gutenberg:1342
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. Configuration Settings
|
|
143
|
+
List all configuration options or update parameters:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# View configuration
|
|
147
|
+
book config
|
|
148
|
+
|
|
149
|
+
# Set default download directory (can also use 'download-path' alias)
|
|
150
|
+
book config set download-dir "/path/to/downloads"
|
|
151
|
+
book config set download-path "/path/to/downloads"
|
|
152
|
+
|
|
153
|
+
# Set client requests timeout
|
|
154
|
+
book config set timeout 10
|
|
155
|
+
|
|
156
|
+
# Disable a provider (e.g. internet_archive)
|
|
157
|
+
book config set provider false internet-archive
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 7. Cache Management
|
|
161
|
+
View statistics or clear cached metadata:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
book cache stats
|
|
165
|
+
book cache clear
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 8. Query History
|
|
169
|
+
View your search history logs:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
book history
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Testing & Code Quality
|
|
178
|
+
|
|
179
|
+
BookCLI includes a comprehensive test suite of unit and integration tests with **80%+ code coverage**.
|
|
180
|
+
|
|
181
|
+
### Run Tests
|
|
182
|
+
```bash
|
|
183
|
+
python -m pytest --cov=bookcli
|
|
184
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Metadata cache using SQLite for BookCLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from bookcli.database import DatabaseManager
|
|
8
|
+
from bookcli.models import BookMetadata
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_book_from_cache(
|
|
14
|
+
db_manager: DatabaseManager, book_id: str, ttl_seconds: int
|
|
15
|
+
) -> Optional[BookMetadata]:
|
|
16
|
+
"""Retrieves a book from the cache if it exists and has not expired."""
|
|
17
|
+
query = "SELECT json_data, cached_at FROM cached_books WHERE id = ?"
|
|
18
|
+
async with db_manager.connection() as conn:
|
|
19
|
+
async with conn.execute(query, (book_id,)) as cursor:
|
|
20
|
+
row = await cursor.fetchone()
|
|
21
|
+
if not row:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
json_data, cached_at_str = row[0], row[1]
|
|
25
|
+
try:
|
|
26
|
+
# SQLite datetime strings are stored in ISO format
|
|
27
|
+
# Parse timestamp: 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SS...'
|
|
28
|
+
# We can replace ' ' with 'T' if needed, or use fromisoformat.
|
|
29
|
+
# In standard SQLite datetime, it might not contain 'T', let's format it.
|
|
30
|
+
if "T" not in cached_at_str:
|
|
31
|
+
# e.g., '2026-07-11 04:53:00'
|
|
32
|
+
cached_at = datetime.strptime(cached_at_str.split(".")[0], "%Y-%m-%d %H:%M:%S")
|
|
33
|
+
else:
|
|
34
|
+
# e.g., ISO format
|
|
35
|
+
cached_at = datetime.fromisoformat(cached_at_str)
|
|
36
|
+
|
|
37
|
+
# Convert to naive or timezone-aware matching
|
|
38
|
+
# Since datetime.now(timezone.utc) is timezone aware, let's make cached_at UTC aware.
|
|
39
|
+
if cached_at.tzinfo is None:
|
|
40
|
+
cached_at = cached_at.replace(tzinfo=timezone.utc)
|
|
41
|
+
|
|
42
|
+
age = (datetime.now(timezone.utc) - cached_at).total_seconds()
|
|
43
|
+
if age > ttl_seconds:
|
|
44
|
+
logger.debug("Cache expired for book ID %s (age: %s seconds)", book_id, age)
|
|
45
|
+
# We could delete it, but simple overwrite on set is fine, or we can delete it now
|
|
46
|
+
await conn.execute("DELETE FROM cached_books WHERE id = ?", (book_id,))
|
|
47
|
+
await conn.commit()
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
data = json.loads(json_data)
|
|
51
|
+
return BookMetadata(**data)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error("Failed to parse cached book %s: %s", book_id, e)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def save_book_to_cache(db_manager: DatabaseManager, book: BookMetadata) -> None:
|
|
58
|
+
"""Saves a book to the SQLite cache, overwriting if it already exists."""
|
|
59
|
+
query = """
|
|
60
|
+
INSERT OR REPLACE INTO cached_books (id, provider, json_data, cached_at)
|
|
61
|
+
VALUES (?, ?, ?, ?)
|
|
62
|
+
"""
|
|
63
|
+
async with db_manager.connection() as conn:
|
|
64
|
+
try:
|
|
65
|
+
# Save date in standard ISO format with timezone (UTC)
|
|
66
|
+
now_str = datetime.now(timezone.utc).isoformat()
|
|
67
|
+
await conn.execute(
|
|
68
|
+
query,
|
|
69
|
+
(book.id, book.source, book.model_dump_json(), now_str)
|
|
70
|
+
)
|
|
71
|
+
await conn.commit()
|
|
72
|
+
logger.debug("Saved book %s to cache", book.id)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error("Failed to write book %s to cache: %s", book.id, e)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def clear_cache_db(db_manager: DatabaseManager) -> int:
|
|
78
|
+
"""Clears all cached books. Returns the number of rows deleted."""
|
|
79
|
+
async with db_manager.connection() as conn:
|
|
80
|
+
async with conn.execute("SELECT COUNT(*) FROM cached_books") as cursor:
|
|
81
|
+
row = await cursor.fetchone()
|
|
82
|
+
count = row[0] if row else 0
|
|
83
|
+
|
|
84
|
+
await conn.execute("DELETE FROM cached_books")
|
|
85
|
+
await conn.commit()
|
|
86
|
+
logger.info("Cleared cached metadata database (%d items).", count)
|
|
87
|
+
return count
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def get_cache_db_stats(db_manager: DatabaseManager) -> dict:
|
|
91
|
+
"""Gets metadata cache statistics."""
|
|
92
|
+
async with db_manager.connection() as conn:
|
|
93
|
+
async with conn.execute("SELECT COUNT(*), MIN(cached_at), MAX(cached_at) FROM cached_books") as cursor:
|
|
94
|
+
row = await cursor.fetchone()
|
|
95
|
+
count = row[0] if row else 0
|
|
96
|
+
oldest = row[1] if row and row[1] else "N/A"
|
|
97
|
+
newest = row[2] if row and row[2] else "N/A"
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
"total_items": count,
|
|
101
|
+
"oldest_item": oldest,
|
|
102
|
+
"newest_item": newest
|
|
103
|
+
}
|