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