sebsync 1.0a2__tar.gz → 1.0a4__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.
sebsync-1.0a4/PKG-INFO ADDED
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.1
2
+ Name: sebsync
3
+ Version: 1.0a4
4
+ Summary: Synchronize Standard Ebooks catalog with local ebooks.
5
+ Home-page: https://github.com/pbryan/sebsync/
6
+ License: MIT
7
+ Keywords: ebook,downloader,standard,book
8
+ Author: sebsync authors
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Other/Nonlisted Topic
18
+ Requires-Dist: click (>=8.1,<9.0)
19
+ Requires-Dist: requests (>=2.32,<3.0)
20
+ Project-URL: Documentation, https://github.com/pbryan/sebsync/tree/main/docs
21
+ Description-Content-Type: text/markdown
22
+
23
+ # sebsync
24
+
25
+ sebsync is a simple script to synchronize the [Standard Ebooks](https://standardebooks.org/)
26
+ catalog with your local EPUB collection.
27
+
28
+ ## How it works
29
+
30
+ The script reads the Standard Ebooks [OPDS feed](https://standardebooks.org/feeds) and
31
+ reconciles the ebooks in their catalog against the EPUBs in your local filesystem,
32
+ downloading new ones or updating existing ones as necessary.
33
+
34
+ If a new ebook is detected in the catalog, then it will be downloaded into your downloads
35
+ directory. If an updated version of a book is detected in the catalog that is already in your
36
+ local filesystem, then the updated version will be downloaded and stored in the existing local
37
+ file.
38
+
39
+ The script can also detect extraneous local ebooks (local ebooks not found in the Standard
40
+ Ebooks catalog). This can can occur when Standard Ebooks changes the identifier of a previously
41
+ published ebook. It's a rare occurance, and it's generally safe to delete such files.
42
+
43
+ ## Requirements
44
+
45
+ This script uses Standard Ebooks' OPDS feed to access metadata on all books their catalog. In
46
+ order to do so, you need to be a member of the Standard Ebooks
47
+ [Patrons Circle](https://standardebooks.org/donate#patrons-circle) (or have previously produced
48
+ an ebook for Standard Ebooks).
49
+
50
+ If you're not already a Standard Ebooks patron, please consider becoming one. It's a
51
+ tremendously valuable project, deserving of your support.
52
+
53
+ ## Installation
54
+
55
+ ```
56
+ pipx install sebsync
57
+ ```
58
+
59
+ ## Example usage
60
+
61
+ ```
62
+ sebsync --email addr@example.com --books /home/user/MyBooks --downloads /home/user/MyBooks/Downloads
63
+ ```
64
+
65
+ ## Questions and answers
66
+
67
+ Q1. *Why use a separate downloads directory for new ebooks?*
68
+
69
+ A1. This is a feature for those who want to easily recognize new ebooks, and to manually
70
+ rename and/or categorize them within their library. If this feature is not useful for you,
71
+ simply set `--downloads` to be the same directory as `--books`.
72
+
73
+ Q2. *What is pipx?*
74
+
75
+ A2. [pipx](https://pipx.pypa.io/) is a utility that allows Python packages like sebsync to
76
+ be installed in an isolated environment. This makes it really easy to install and run such
77
+ scripts without interfering with other Python package installations.
78
+
79
+ Q3. *Does this script support Kindle books?*
80
+
81
+ A3. Not presently. A cursory analysis of the Standard Ebooks AZW3 structure suggests that there
82
+ currently isn't a reliable method to reconcile Kindle ebooks with the OPDS feed catalog. If
83
+ this feature is important to you, please let us know by voting for
84
+ [this issue](https://github.com/pbryan/sebsync/issues/2) in GitHub.
85
+
@@ -0,0 +1,62 @@
1
+ # sebsync
2
+
3
+ sebsync is a simple script to synchronize the [Standard Ebooks](https://standardebooks.org/)
4
+ catalog with your local EPUB collection.
5
+
6
+ ## How it works
7
+
8
+ The script reads the Standard Ebooks [OPDS feed](https://standardebooks.org/feeds) and
9
+ reconciles the ebooks in their catalog against the EPUBs in your local filesystem,
10
+ downloading new ones or updating existing ones as necessary.
11
+
12
+ If a new ebook is detected in the catalog, then it will be downloaded into your downloads
13
+ directory. If an updated version of a book is detected in the catalog that is already in your
14
+ local filesystem, then the updated version will be downloaded and stored in the existing local
15
+ file.
16
+
17
+ The script can also detect extraneous local ebooks (local ebooks not found in the Standard
18
+ Ebooks catalog). This can can occur when Standard Ebooks changes the identifier of a previously
19
+ published ebook. It's a rare occurance, and it's generally safe to delete such files.
20
+
21
+ ## Requirements
22
+
23
+ This script uses Standard Ebooks' OPDS feed to access metadata on all books their catalog. In
24
+ order to do so, you need to be a member of the Standard Ebooks
25
+ [Patrons Circle](https://standardebooks.org/donate#patrons-circle) (or have previously produced
26
+ an ebook for Standard Ebooks).
27
+
28
+ If you're not already a Standard Ebooks patron, please consider becoming one. It's a
29
+ tremendously valuable project, deserving of your support.
30
+
31
+ ## Installation
32
+
33
+ ```
34
+ pipx install sebsync
35
+ ```
36
+
37
+ ## Example usage
38
+
39
+ ```
40
+ sebsync --email addr@example.com --books /home/user/MyBooks --downloads /home/user/MyBooks/Downloads
41
+ ```
42
+
43
+ ## Questions and answers
44
+
45
+ Q1. *Why use a separate downloads directory for new ebooks?*
46
+
47
+ A1. This is a feature for those who want to easily recognize new ebooks, and to manually
48
+ rename and/or categorize them within their library. If this feature is not useful for you,
49
+ simply set `--downloads` to be the same directory as `--books`.
50
+
51
+ Q2. *What is pipx?*
52
+
53
+ A2. [pipx](https://pipx.pypa.io/) is a utility that allows Python packages like sebsync to
54
+ be installed in an isolated environment. This makes it really easy to install and run such
55
+ scripts without interfering with other Python package installations.
56
+
57
+ Q3. *Does this script support Kindle books?*
58
+
59
+ A3. Not presently. A cursory analysis of the Standard Ebooks AZW3 structure suggests that there
60
+ currently isn't a reliable method to reconcile Kindle ebooks with the OPDS feed catalog. If
61
+ this feature is important to you, please let us know by voting for
62
+ [this issue](https://github.com/pbryan/sebsync/issues/2) in GitHub.
@@ -8,7 +8,7 @@ target-version = ['py312']
8
8
 
9
9
  [tool.poetry]
10
10
  name = "sebsync"
11
- version = "1.0alpha2"
11
+ version = "1.0a4"
12
12
  description = "Synchronize Standard Ebooks catalog with local ebooks."
13
13
  readme = "README.md"
14
14
  authors = ["sebsync authors"]
@@ -1,4 +1,4 @@
1
- """Synchronize Standard Ebooks catalog with local ebooks."""
1
+ """Synchronize Standard Ebooks catalog with a local EPUB collection."""
2
2
 
3
3
  import click
4
4
  import requests
@@ -6,7 +6,7 @@ import xml.etree.ElementTree as ElementTree
6
6
  import zipfile
7
7
 
8
8
  from dataclasses import dataclass
9
- from datetime import datetime
9
+ from datetime import datetime, timezone
10
10
  from pathlib import Path
11
11
  from requests.auth import HTTPBasicAuth
12
12
 
@@ -16,8 +16,22 @@ _quiet: bool = False
16
16
  _verbose: bool = False
17
17
 
18
18
 
19
- _epilog = """
20
- All options can be provided through environment variables in all-caps with SEBSYNC_ prefix.
19
+ class Status:
20
+ NEW = click.style("N", fg="green")
21
+ UPDATE = click.style("U", fg="blue")
22
+ EXTRA = click.style("X", fg="yellow")
23
+
24
+
25
+ _epilog = f"""
26
+ The following statuses are reported for ebooks:
27
+
28
+ \b
29
+ {Status.NEW}: new (ebook found in Standard Ebooks catalog but not found locally)
30
+ {Status.UPDATE}: update (new version of ebook found in Standard Ebooks catalog)
31
+ {Status.EXTRA}: extraneous (local ebook was not found in Standard Ebooks catalog)
32
+
33
+ An extraneous ebook can occur when Standard Ebooks changes the identifier of a previously
34
+ published ebook. It's a rare occurance, and it's generally safe to delete such files.
21
35
  """
22
36
 
23
37
 
@@ -42,15 +56,37 @@ class LocalEbook:
42
56
  modified: datetime
43
57
 
44
58
 
59
+ def _echo_status(path: Path, status: str) -> None:
60
+ if not _quiet:
61
+ click.echo(f"{status} {path}")
62
+
63
+
45
64
  def _if_exists(path: Path) -> Path | None:
46
65
  return path if path.exists() else None
47
66
 
48
67
 
49
- def get_standard_ebooks(opds: str, email: str) -> dict[str, StandardEbook]:
68
+ def _fromisoformat(text: str) -> datetime:
69
+ """Convert RFC 3339 string into datetime; compatible with Python 3.10."""
70
+ if not text.endswith("Z"):
71
+ raise ValueError("expecting RFC 3339 formatted string")
72
+ d = datetime.fromisoformat(text.rstrip("Z"))
73
+ return datetime(
74
+ d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, timezone.utc
75
+ )
76
+
77
+
78
+ type_selector = {
79
+ "compatible": "Recommended compatible epub",
80
+ "kobo": "Kobo Kepub epub",
81
+ "advanced": "Advanced epub",
82
+ }
83
+
84
+
85
+ def get_remote_ebooks(opds_url: str, email: str, type: str) -> dict[str, StandardEbook]:
50
86
  """Return Standard Ebooks metadata for EPUBs from the OPDS catalog."""
51
87
  ns = {"atom": "http://www.w3.org/2005/Atom", "dc": "http://purl.org/dc/terms/"}
52
88
  ebooks = {}
53
- response = requests.get(opds, stream=True, auth=HTTPBasicAuth(email, ""))
89
+ response = requests.get(opds_url, stream=True, auth=HTTPBasicAuth(email, ""))
54
90
  response.raw.decode_content = True
55
91
  root = ElementTree.parse(response.raw).getroot()
56
92
  for entry in root.iterfind(".//atom:entry", ns):
@@ -58,10 +94,8 @@ def get_standard_ebooks(opds: str, email: str) -> dict[str, StandardEbook]:
58
94
  id=entry.find("dc:identifier", ns).text,
59
95
  title=entry.find("atom:title", ns).text,
60
96
  author=entry.find("atom:author", ns).find("atom:name", ns).text,
61
- href=entry.find(".//atom:link[@title='Recommended compatible epub']", ns).attrib[
62
- "href"
63
- ],
64
- updated=datetime.fromisoformat(entry.find("atom:updated", ns).text),
97
+ href=entry.find(f".//atom:link[@title='{type_selector[type]}']", ns).attrib["href"],
98
+ updated=_fromisoformat(entry.find("atom:updated", ns).text),
65
99
  )
66
100
  ebooks[ebook.id] = ebook
67
101
  return ebooks
@@ -91,7 +125,7 @@ def get_local_ebooks(dir: Path) -> dict[str, LocalEbook]:
91
125
  id=id.text,
92
126
  title=metadata.find(".//dc:title", ns).text,
93
127
  path=path,
94
- modified=datetime.fromisoformat(modified.text),
128
+ modified=_fromisoformat(modified.text),
95
129
  )
96
130
  ebooks[ebook.id] = ebook
97
131
  return ebooks
@@ -99,8 +133,6 @@ def get_local_ebooks(dir: Path) -> dict[str, LocalEbook]:
99
133
 
100
134
  def download_ebook(url: str, path: Path) -> None:
101
135
  """Download the ebook at the specified URL into the specified path."""
102
- if not _quiet:
103
- click.echo(f"Download: {path}")
104
136
  if not _dry_run:
105
137
  response = requests.get(url, stream=True)
106
138
  with path.open("wb") as file:
@@ -126,12 +158,12 @@ def ebook_filename(ebook: StandardEbook) -> str:
126
158
  @click.option(
127
159
  "--books",
128
160
  help="directory where local books are stored",
129
- type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
161
+ type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True, path_type=Path),
130
162
  default=_if_exists(Path.home() / "Books"),
131
163
  )
132
164
  @click.option(
133
165
  "--downloads",
134
- help="directory where ebooks are downloaded",
166
+ help="directory where new ebooks are downloaded",
135
167
  type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True, path_type=Path),
136
168
  default=_if_exists(Path.home() / "Downloads"),
137
169
  )
@@ -142,7 +174,7 @@ def ebook_filename(ebook: StandardEbook) -> str:
142
174
  )
143
175
  @click.option(
144
176
  "--email",
145
- help="email to authenticate with Standard Ebooks",
177
+ help="email address to authenticate with Standard Ebooks",
146
178
  required=True,
147
179
  )
148
180
  @click.option(
@@ -155,13 +187,26 @@ def ebook_filename(ebook: StandardEbook) -> str:
155
187
  help="suppress non-error messages",
156
188
  is_flag=True,
157
189
  )
190
+ @click.option(
191
+ "--type",
192
+ type=click.Choice(["compatible", "kobo", "advanced"]),
193
+ help="EPUB type to download",
194
+ default="compatible",
195
+ )
158
196
  @click.option(
159
197
  "--verbose",
160
198
  help="increase verbosity",
161
199
  is_flag=True,
162
200
  )
163
201
  def sebsync(
164
- books: str, downloads: str, dry_run: bool, email: str, opds: str, quiet: bool, verbose: bool
202
+ books: str,
203
+ downloads: str,
204
+ dry_run: bool,
205
+ email: str,
206
+ opds: str,
207
+ quiet: bool,
208
+ type: str,
209
+ verbose: bool,
165
210
  ):
166
211
  global _dry_run
167
212
  global _verbose
@@ -171,28 +216,29 @@ def sebsync(
171
216
  _verbose = verbose
172
217
  _quiet = quiet
173
218
 
174
- remote_ebooks = get_standard_ebooks(opds, email)
219
+ remote_ebooks = get_remote_ebooks(opds, email, type)
175
220
  if not remote_ebooks:
176
- raise click.ClickException("Email address rejected by Standard Ebooks.")
221
+ raise click.ClickException("OPDS download failed. Is email address correct?")
177
222
  if _verbose:
178
- click.echo(f"Found {len(remote_ebooks)} remote Standard Ebooks titles.")
223
+ click.echo(f"Found {len(remote_ebooks)} remote ebooks.")
179
224
 
180
225
  local_ebooks = get_local_ebooks(downloads) | get_local_ebooks(books)
181
226
  if _verbose:
182
- click.echo(f"Found {len(local_ebooks)} local Standard Ebooks titles.")
227
+ click.echo(f"Found {len(local_ebooks)} local ebooks.")
183
228
 
184
229
  for remote_ebook in remote_ebooks.values():
185
230
  if local_ebook := local_ebooks.get(remote_ebook.id):
186
231
  if remote_ebook.updated != local_ebook.modified:
232
+ _echo_status(local_ebook.path, Status.UPDATE)
187
233
  download_ebook(remote_ebook.href, local_ebook.path)
188
234
  else:
189
235
  path = downloads / ebook_filename(remote_ebook)
236
+ _echo_status(path, Status.NEW)
190
237
  download_ebook(remote_ebook.href, path)
191
238
 
192
- if not _quiet:
193
- for local_ebook in local_ebooks.values():
194
- if local_ebook.id not in remote_ebooks:
195
- click.secho(f"Extra: {local_ebook.path}", fg="yellow")
239
+ for local_ebook in local_ebooks.values():
240
+ if local_ebook.id not in remote_ebooks:
241
+ _echo_status(local_ebook.path, Status.EXTRA)
196
242
 
197
243
 
198
244
  def main():
sebsync-1.0a2/PKG-INFO DELETED
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: sebsync
3
- Version: 1.0a2
4
- Summary: Synchronize Standard Ebooks catalog with local ebooks.
5
- Home-page: https://github.com/pbryan/sebsync/
6
- License: MIT
7
- Keywords: ebook,downloader,standard,book
8
- Author: sebsync authors
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: End Users/Desktop
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Topic :: Other/Nonlisted Topic
18
- Requires-Dist: click (>=8.1,<9.0)
19
- Requires-Dist: requests (>=2.32,<3.0)
20
- Project-URL: Documentation, https://github.com/pbryan/sebsync/tree/main/docs
21
- Description-Content-Type: text/markdown
22
-
23
- # sebsync
24
-
25
- sebsync is a simple script to synchronize all EPUBs from Standard Ebooks to your local
26
- filesystem. It detects any new and updated titles and downloads them automatically.
27
-
28
- ## How it works
29
-
30
- The `sebsync` script reads the Standard Ebooks OPDS catalog and reconciles their titles against
31
- EPUBs in your local filesystem, downloading new ones or updating existing ones as necessary.
32
-
33
- If you're member of the Standard Ebooks
34
- [Patrons Circle](https://standardebooks.org/donate#patrons-circle), or have produced an ebook
35
- for them in the past, you may access their [OPDS feed](https://standardebooks.org/feeds),
36
- a catalog of all published titles. This allows new and updated titles to be easily recognized.
37
-
38
- If a title is detected in the catalog that is not present in your local books or downloads
39
- directory, then it will be downloaded into your downloads directory. If a new version of a book
40
- is detected in the catalog that is already in your local books or download directory, then the
41
- new version will be downloaded and stored in the same place it was found.
42
-
43
- ## Installation
44
-
45
- ```
46
- pipx install sebsync
47
- ```
48
-
49
- ### Example usage
50
-
51
- ```
52
- sebsync --email address@example.com --books /home/user/MyBooks --downloads /home/user/MyBooks/Downloads
53
- ```
54
-
sebsync-1.0a2/README.md DELETED
@@ -1,31 +0,0 @@
1
- # sebsync
2
-
3
- sebsync is a simple script to synchronize all EPUBs from Standard Ebooks to your local
4
- filesystem. It detects any new and updated titles and downloads them automatically.
5
-
6
- ## How it works
7
-
8
- The `sebsync` script reads the Standard Ebooks OPDS catalog and reconciles their titles against
9
- EPUBs in your local filesystem, downloading new ones or updating existing ones as necessary.
10
-
11
- If you're member of the Standard Ebooks
12
- [Patrons Circle](https://standardebooks.org/donate#patrons-circle), or have produced an ebook
13
- for them in the past, you may access their [OPDS feed](https://standardebooks.org/feeds),
14
- a catalog of all published titles. This allows new and updated titles to be easily recognized.
15
-
16
- If a title is detected in the catalog that is not present in your local books or downloads
17
- directory, then it will be downloaded into your downloads directory. If a new version of a book
18
- is detected in the catalog that is already in your local books or download directory, then the
19
- new version will be downloaded and stored in the same place it was found.
20
-
21
- ## Installation
22
-
23
- ```
24
- pipx install sebsync
25
- ```
26
-
27
- ### Example usage
28
-
29
- ```
30
- sebsync --email address@example.com --books /home/user/MyBooks --downloads /home/user/MyBooks/Downloads
31
- ```
File without changes
File without changes