oldnews 0.8.0__tar.gz → 0.9.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.
Files changed (48) hide show
  1. {oldnews-0.8.0 → oldnews-0.9.0}/PKG-INFO +2 -2
  2. {oldnews-0.8.0 → oldnews-0.9.0}/pyproject.toml +26 -3
  3. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/__main__.py +1 -2
  4. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/__init__.py +6 -5
  5. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/config.py +6 -3
  6. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/last_grab.py +11 -19
  7. oldnews-0.9.0/src/oldnews/data/local_articles.py +291 -0
  8. oldnews-0.9.0/src/oldnews/data/local_data.py +45 -0
  9. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/local_folders.py +12 -21
  10. oldnews-0.9.0/src/oldnews/data/local_subscriptions.py +79 -0
  11. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/local_unread.py +5 -3
  12. oldnews-0.9.0/src/oldnews/data/models/__init__.py +24 -0
  13. oldnews-0.9.0/src/oldnews/data/models/local_article.py +81 -0
  14. oldnews-0.9.0/src/oldnews/data/models/local_folder.py +19 -0
  15. oldnews-0.9.0/src/oldnews/data/models/local_state.py +25 -0
  16. oldnews-0.9.0/src/oldnews/data/models/local_subscription.py +41 -0
  17. oldnews-0.9.0/src/oldnews/data/navigation_state.py +36 -0
  18. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/reset.py +7 -1
  19. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/oldnews.py +8 -1
  20. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/information_display.py +1 -1
  21. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/main.py +55 -47
  22. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/sync.py +40 -35
  23. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/widgets/_next_matching_option.py +1 -1
  24. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/widgets/article_list.py +10 -9
  25. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/widgets/navigation.py +24 -14
  26. oldnews-0.8.0/src/oldnews/data/db.py +0 -97
  27. oldnews-0.8.0/src/oldnews/data/local_articles.py +0 -430
  28. oldnews-0.8.0/src/oldnews/data/local_subscriptions.py +0 -120
  29. oldnews-0.8.0/src/oldnews/data/navigation_state.py +0 -45
  30. oldnews-0.8.0/src/oldnews/data/tools.py +0 -45
  31. {oldnews-0.8.0 → oldnews-0.9.0}/README.md +0 -0
  32. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/__init__.py +0 -0
  33. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/commands/__init__.py +0 -0
  34. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/commands/main.py +0 -0
  35. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/auth.py +0 -0
  36. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/dump.py +0 -0
  37. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/locations.py +0 -0
  38. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/data/log.py +0 -0
  39. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/providers/__init__.py +0 -0
  40. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/providers/main.py +0 -0
  41. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/py.typed +0 -0
  42. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/__init__.py +0 -0
  43. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/folder_input.py +0 -0
  44. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/login.py +0 -0
  45. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/new_subscription.py +0 -0
  46. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/screens/process_subscription.py +0 -0
  47. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/widgets/__init__.py +0 -0
  48. {oldnews-0.8.0 → oldnews-0.9.0}/src/oldnews/widgets/article_content.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oldnews
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: A terminal-based client for TheOldReader
5
5
  Keywords: atom,client,Google Reader,RSS,TheOldReader,terminal,news-reader,news
6
6
  Author: Dave Pearson
@@ -20,7 +20,7 @@ Requires-Dist: oldas>=0.10.0
20
20
  Requires-Dist: textual>=6.3.0
21
21
  Requires-Dist: textual-autocomplete>=4.0.6
22
22
  Requires-Dist: textual-enhanced>=1.2.0
23
- Requires-Dist: typedal>=4.2.2
23
+ Requires-Dist: tortoise-orm>=0.25.4
24
24
  Requires-Dist: xdg-base-dirs>=6.0.2
25
25
  Requires-Python: >=3.12
26
26
  Project-URL: Homepage, https://github.com/davep/oldnews
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oldnews"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "A terminal-based client for TheOldReader"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -25,7 +25,7 @@ dependencies = [
25
25
  "textual>=6.3.0",
26
26
  "textual-autocomplete>=4.0.6",
27
27
  "textual-enhanced>=1.2.0",
28
- "typedal>=4.2.2",
28
+ "tortoise-orm>=0.25.4",
29
29
  "xdg-base-dirs>=6.0.2",
30
30
  ]
31
31
  classifiers = [
@@ -51,7 +51,7 @@ Discussions = "https://github.com/davep/oldnews/discussions"
51
51
  oldnews = "oldnews.__main__:main"
52
52
 
53
53
  [build-system]
54
- requires = ["uv_build>=0.9.4,<0.10.0"]
54
+ requires = ["uv_build>=0.9.4,<0.11.0"]
55
55
  build-backend = "uv_build"
56
56
 
57
57
  [[tool.uv.index]]
@@ -73,3 +73,26 @@ dev = [
73
73
  "ruff>=0.14.0",
74
74
  "textual-dev>=1.8.0",
75
75
  ]
76
+
77
+ [tool.ruff.lint]
78
+ select = [
79
+ # pycodestyle
80
+ "E",
81
+ # Pyflakes
82
+ "F",
83
+ # pyupgrade
84
+ "UP",
85
+ # flake8-bugbear
86
+ "B",
87
+ # flake8-simplify
88
+ "SIM",
89
+ # isort
90
+ "I",
91
+ ]
92
+ ignore = [
93
+ # I think try...expect...pass reads far better.
94
+ "SIM105",
95
+ ]
96
+
97
+ [tool.ruff.lint.pycodestyle]
98
+ max-line-length = 120
@@ -9,7 +9,7 @@ from operator import attrgetter
9
9
  ##############################################################################
10
10
  # Local imports.
11
11
  from . import __doc__, __version__
12
- from .data import initialise_database, reset_data
12
+ from .data import reset_data
13
13
  from .data.locations import config_dir, data_dir
14
14
  from .oldnews import OldNews
15
15
 
@@ -155,7 +155,6 @@ def main() -> None:
155
155
  case "themes":
156
156
  show_themes()
157
157
  case _:
158
- initialise_database()
159
158
  OldNews(args).run()
160
159
 
161
160
 
@@ -9,7 +9,6 @@ from .config import (
9
9
  save_configuration,
10
10
  update_configuration,
11
11
  )
12
- from .db import initialise_database
13
12
  from .dump import data_dump
14
13
  from .last_grab import last_grabbed_data_at, remember_we_last_grabbed_at
15
14
  from .local_articles import (
@@ -24,6 +23,7 @@ from .local_articles import (
24
23
  rename_folder_for_articles,
25
24
  save_local_articles,
26
25
  )
26
+ from .local_data import initialise_local_data, shutdown_local_data
27
27
  from .local_folders import get_local_folders, save_local_folders
28
28
  from .local_subscriptions import get_local_subscriptions, save_local_subscriptions
29
29
  from .local_unread import LocalUnread, get_local_unread, total_unread
@@ -34,9 +34,8 @@ from .reset import reset_data
34
34
  ##############################################################################
35
35
  # Exports.
36
36
  __all__ = [
37
- "Configuration",
38
- "Log",
39
37
  "clean_old_read_articles",
38
+ "Configuration",
40
39
  "data_dump",
41
40
  "get_auth_token",
42
41
  "get_local_articles",
@@ -45,12 +44,13 @@ __all__ = [
45
44
  "get_local_unread",
46
45
  "get_navigation_state",
47
46
  "get_unread_article_ids",
48
- "initialise_database",
47
+ "initialise_local_data",
49
48
  "last_grabbed_data_at",
50
49
  "load_configuration",
51
- "locally_mark_read",
52
50
  "locally_mark_article_ids_read",
51
+ "locally_mark_read",
53
52
  "LocalUnread",
53
+ "Log",
54
54
  "move_subscription_articles",
55
55
  "remember_we_last_grabbed_at",
56
56
  "remove_folder_from_articles",
@@ -63,6 +63,7 @@ __all__ = [
63
63
  "save_local_subscriptions",
64
64
  "save_navigation_state",
65
65
  "set_auth_token",
66
+ "shutdown_local_data",
66
67
  "total_unread",
67
68
  "update_configuration",
68
69
  ]
@@ -2,12 +2,12 @@
2
2
 
3
3
  ##############################################################################
4
4
  # Python imports.
5
+ from collections.abc import Iterator
5
6
  from contextlib import contextmanager
6
7
  from dataclasses import asdict, dataclass, field
7
- from functools import lru_cache
8
+ from functools import cache
8
9
  from json import dumps, loads
9
10
  from pathlib import Path
10
- from typing import Iterator
11
11
 
12
12
  ##############################################################################
13
13
  # Local imports.
@@ -37,6 +37,9 @@ class Configuration:
37
37
  startup_refresh_holdoff_period: float = 600
38
38
  """The number of seconds to wait before hitting TheOldReader again on startup."""
39
39
 
40
+ article_download_batch_size: int = 50
41
+ """The batch size to use when downloading articles."""
42
+
40
43
 
41
44
  ##############################################################################
42
45
  def configuration_file() -> Path:
@@ -66,7 +69,7 @@ def save_configuration(configuration: Configuration) -> Configuration:
66
69
 
67
70
 
68
71
  ##############################################################################
69
- @lru_cache(maxsize=None)
72
+ @cache
70
73
  def load_configuration() -> Configuration:
71
74
  """Load the configuration.
72
75
 
@@ -2,39 +2,31 @@
2
2
 
3
3
  ##############################################################################
4
4
  # Python imports.
5
- from datetime import datetime, timezone
5
+ from datetime import UTC, datetime
6
6
 
7
7
  ##############################################################################
8
- # TypeDAL imports.
9
- from typedal import TypedTable
8
+ # Tortoise imports.
9
+ from tortoise.transactions import in_transaction
10
10
 
11
11
  ##############################################################################
12
12
  # Local imports.
13
- from .tools import commit
13
+ from .models import LastGrabbed
14
14
 
15
15
 
16
16
  ##############################################################################
17
- class LastGrabbed(TypedTable):
18
- """Table that holds details of when data was last grabbed."""
19
-
20
- at_time: datetime
21
- """The time at which data was last grabbed."""
22
-
23
-
24
- ##############################################################################
25
- def last_grabbed_data_at() -> datetime | None:
17
+ async def last_grabbed_data_at() -> datetime | None:
26
18
  """The time at which data was last grabbed.
27
19
 
28
20
  Returns:
29
21
  The time at which we last grabbed data, or `None` if we never have.
30
22
  """
31
- if (row := LastGrabbed.select(LastGrabbed.ALL).first()) is not None:
32
- return row.at_time
23
+ if last_grabbed := await LastGrabbed.first():
24
+ return last_grabbed.at_time
33
25
  return None
34
26
 
35
27
 
36
28
  ##############################################################################
37
- def remember_we_last_grabbed_at(grab_time: datetime | None = None) -> None:
29
+ async def remember_we_last_grabbed_at(grab_time: datetime | None = None) -> None:
38
30
  """Remember the time we last grabbed data.
39
31
 
40
32
  Args:
@@ -43,9 +35,9 @@ def remember_we_last_grabbed_at(grab_time: datetime | None = None) -> None:
43
35
  Note:
44
36
  If `grab_time` isn't supplied then it is recorded as now.
45
37
  """
46
- LastGrabbed.truncate()
47
- LastGrabbed.insert(at_time=grab_time or datetime.now(timezone.utc))
48
- commit(LastGrabbed)
38
+ async with in_transaction():
39
+ await LastGrabbed.all().delete()
40
+ await LastGrabbed.create(at_time=grab_time or datetime.now(UTC))
49
41
 
50
42
 
51
43
  ### last_grab.py ends here
@@ -0,0 +1,291 @@
1
+ """Code relating to persisting articles."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from collections.abc import Iterable
6
+ from datetime import UTC, datetime, timedelta
7
+ from html import unescape
8
+ from typing import cast
9
+
10
+ ##############################################################################
11
+ # OldAS imports.
12
+ from oldas import Article, Articles, Folder, Folders, State, Subscription
13
+ from oldas.articles import Alternate, Alternates, Direction, Origin, Summary
14
+
15
+ ##############################################################################
16
+ # Tortoise imports.
17
+ from tortoise.transactions import in_transaction
18
+
19
+ ##############################################################################
20
+ # Local imports.
21
+ from .log import Log
22
+ from .models import LocalArticle, LocalArticleAlternate, LocalArticleCategory
23
+
24
+
25
+ ##############################################################################
26
+ async def save_local_articles(articles: Articles) -> Articles:
27
+ """Locally save the given articles.
28
+
29
+ Args:
30
+ articles: The articles to save.
31
+
32
+ Returns:
33
+ The articles.
34
+ """
35
+ for article in articles:
36
+ async with in_transaction():
37
+ local_article, _ = await LocalArticle.update_or_create(
38
+ article_id=article.id,
39
+ defaults={
40
+ "title": article.title,
41
+ "published": article.published,
42
+ "updated": article.updated,
43
+ "author": article.author,
44
+ "summary_direction": article.summary.direction,
45
+ "summary_content": article.summary.content,
46
+ "origin_stream_id": article.origin.stream_id,
47
+ "origin_title": article.origin.title,
48
+ "origin_html_url": article.origin.html_url,
49
+ },
50
+ )
51
+ await LocalArticleCategory.filter(article=local_article).delete()
52
+ await LocalArticleCategory.bulk_create(
53
+ LocalArticleCategory(article=local_article, category=str(category))
54
+ for category in article.categories
55
+ )
56
+ await LocalArticleAlternate.filter(article=local_article).delete()
57
+ await LocalArticleAlternate.bulk_create(
58
+ LocalArticleAlternate(
59
+ article=local_article,
60
+ href=alternate.href,
61
+ mime_type=alternate.mime_type,
62
+ )
63
+ for alternate in article.alternate
64
+ )
65
+ return articles
66
+
67
+
68
+ ##############################################################################
69
+ async def get_local_read_article_ids() -> set[str]:
70
+ """Get the set of local articles that have been read.
71
+
72
+ Returns:
73
+ A `set` of IDs of articles that have been read.
74
+ """
75
+ return {
76
+ category.article.article_id
77
+ for category in await LocalArticleCategory.filter(
78
+ category=str(State.READ)
79
+ ).prefetch_related("article")
80
+ }
81
+
82
+
83
+ ##############################################################################
84
+ async def get_local_articles(
85
+ related_to: Folder | Subscription, unread_only: bool
86
+ ) -> Articles:
87
+ """Get all available unread articles.
88
+
89
+ Args:
90
+ related_to: The folder or feed the articles should relate to.
91
+ unread_only: Only load up the unread articles?
92
+
93
+ Returns: The unread articles.
94
+ """
95
+ local_articles = (
96
+ LocalArticle.filter(categories__category=related_to.id)
97
+ if isinstance(related_to, Folder)
98
+ else LocalArticle.filter(origin_stream_id=related_to.id)
99
+ )
100
+ if unread_only and (read := (await get_local_read_article_ids())):
101
+ local_articles = local_articles.filter(article_id__not_in=read)
102
+
103
+ articles: list[Article] = []
104
+ for article in await local_articles.prefetch_related(
105
+ "categories", "alternates"
106
+ ).order_by("-published"):
107
+ articles.append(
108
+ Article(
109
+ id=article.article_id,
110
+ title=unescape(article.title),
111
+ published=article.published,
112
+ updated=article.updated,
113
+ author=article.author,
114
+ categories=Article.clean_categories(
115
+ category.category
116
+ for category in article.categories # type: ignore
117
+ ),
118
+ alternate=Alternates(
119
+ Alternate(href=alternate.href, mime_type=alternate.mime_type)
120
+ for alternate in article.alternates # type: ignore
121
+ ),
122
+ origin=Origin(
123
+ stream_id=article.origin_stream_id,
124
+ title=unescape(article.origin_title),
125
+ html_url=article.origin_html_url,
126
+ ),
127
+ summary=Summary(
128
+ direction=cast(Direction, article.summary_direction),
129
+ content=article.summary_content,
130
+ ),
131
+ )
132
+ )
133
+ return Articles(articles)
134
+
135
+
136
+ ##############################################################################
137
+ async def locally_mark_read(article: Article) -> None:
138
+ """Mark the given article as read.
139
+
140
+ Args:
141
+ article: The article to locally mark as read.
142
+ """
143
+ if local_article := await LocalArticle.filter(article_id=article.id).get_or_none():
144
+ await local_article.add_category(str(State.READ))
145
+
146
+
147
+ ##############################################################################
148
+ async def locally_mark_article_ids_read(articles: Iterable[str]) -> None:
149
+ """Locally mark a collection of article IDs as being read.
150
+
151
+ Args:
152
+ articles: The article IDs to mark as read.
153
+ """
154
+ if article_ids := set(articles):
155
+ Log().debug(f"Number of articles to mark as read: {len(article_ids)}")
156
+ await LocalArticleCategory.bulk_create(
157
+ [
158
+ LocalArticleCategory(article=article, category=str(State.READ))
159
+ for article in await LocalArticle.filter(article_id__in=article_ids)
160
+ ],
161
+ ignore_conflicts=True,
162
+ )
163
+
164
+
165
+ ##############################################################################
166
+ async def unread_count_in(
167
+ category: Folder | Subscription, read: set[str] | None = None
168
+ ) -> int:
169
+ """Get the count of unread articles in a given category.
170
+
171
+ Args:
172
+ category: The category (Folder or Subscription) to get the unread count for.
173
+ read: The set of IDs of read articles.
174
+
175
+ Returns:
176
+ The count of unread articles in that category.
177
+ """
178
+ query = (
179
+ LocalArticle.filter(categories__category=category.id)
180
+ if isinstance(category, Folder)
181
+ else LocalArticle.filter(origin_stream_id=category.id)
182
+ )
183
+ if read := read if read is not None else await get_local_read_article_ids():
184
+ query = query.filter(article_id__not_in=read)
185
+ return await query.count()
186
+
187
+
188
+ ##############################################################################
189
+ async def get_unread_article_ids() -> list[str]:
190
+ """Get a list of all the unread article IDs.
191
+
192
+ Returns:
193
+ The list of IDs of unread articles.
194
+ """
195
+ read = await get_local_read_article_ids()
196
+ return [
197
+ article.article_id
198
+ for article in await LocalArticle.filter(article_id__not_in=read)
199
+ ]
200
+
201
+
202
+ ##############################################################################
203
+ async def clean_old_read_articles(cutoff: timedelta) -> int:
204
+ """Clean up articles that are older than the given cutoff time.
205
+
206
+ Args:
207
+ cutoff: The cutoff period after which articles will be removed.
208
+
209
+ Returns:
210
+ The number of removed articles.
211
+ """
212
+ read = await get_local_read_article_ids()
213
+ retire_time = datetime.now(UTC) - cutoff
214
+ Log().debug(f"Cleaning up read articles published before {retire_time}")
215
+ cleaned = await LocalArticle.filter(
216
+ published__lt=retire_time, article_id__in=read
217
+ ).delete()
218
+ Log().debug(f"Cleaned: {cleaned}")
219
+ return cleaned
220
+
221
+
222
+ ##############################################################################
223
+ async def rename_folder_for_articles(rename_from: str | Folder, rename_to: str) -> None:
224
+ """Rename a folder for all articles that are in that folder.
225
+
226
+ Args:
227
+ rename_from: The folder name to rename from.
228
+ rename_to: The folder name to rename to.
229
+ """
230
+ rename_from = Folders.full_id(rename_from)
231
+ rename_to = Folders.full_id(rename_to)
232
+ Log().debug(f"Renaming folder for local articles from {rename_from} to {rename_to}")
233
+ await LocalArticleCategory.filter(category=rename_from).update(category=rename_to)
234
+
235
+
236
+ ##############################################################################
237
+ async def remove_folder_from_articles(folder: str | Folder) -> None:
238
+ """Remove a folder from being associated with all articles.
239
+
240
+ Args:
241
+ folder: The folder to remove from all articles.
242
+ """
243
+ folder = Folders.full_id(folder)
244
+ Log().debug(f"Removing folder {folder} from all local articles")
245
+ await LocalArticleCategory.filter(category=folder).delete()
246
+
247
+
248
+ ##############################################################################
249
+ async def move_subscription_articles(
250
+ subscription: Subscription,
251
+ from_folder: str | Folder | None,
252
+ to_folder: str | Folder | None,
253
+ ) -> None:
254
+ """Move the articles of a subscription from one folder to another.
255
+
256
+ Args:
257
+ subscription: The subscription whose articles we should move.
258
+ from_folder: The folder to move from.
259
+ to_folder: The folder to move to.
260
+ """
261
+ from_folder = (
262
+ Folders.full_id(from_folder) if from_folder is not None else from_folder
263
+ )
264
+ to_folder = Folders.full_id(to_folder) if to_folder is not None else to_folder
265
+ Log().debug(
266
+ f"Moving all articles of {subscription.title} ({subscription.id}) from folder {from_folder} to {to_folder}"
267
+ )
268
+ for article in await LocalArticle.filter(
269
+ origin_stream_id=subscription.id
270
+ ).prefetch_related("categories"):
271
+ if from_folder:
272
+ await article.remove_category(from_folder)
273
+ if to_folder:
274
+ await article.add_category(to_folder)
275
+
276
+
277
+ ##############################################################################
278
+ async def remove_subscription_articles(subscription: str | Subscription) -> None:
279
+ """Remove all the articles associated with the given subscription.
280
+
281
+ Args:
282
+ subscription: The subscription to remove the articles for.
283
+ """
284
+ if isinstance(subscription, Subscription):
285
+ subscription = subscription.id
286
+ Log().debug(f"Removing all local articles for subscription {subscription}")
287
+ deleted = await LocalArticle.filter(origin_stream_id=subscription).delete()
288
+ Log().debug(f"Articles removed that belonged to {subscription}: {deleted}")
289
+
290
+
291
+ ### local_articles.py ends here
@@ -0,0 +1,45 @@
1
+ """Provides code for setting up the local database."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from pathlib import Path
6
+
7
+ ##############################################################################
8
+ # Tortoise imports.
9
+ from tortoise import Tortoise
10
+
11
+ ##############################################################################
12
+ # Local imports.
13
+ from .locations import data_dir
14
+ from .log import Log
15
+
16
+
17
+ ##############################################################################
18
+ def local_db_file() -> Path:
19
+ """Get the file that contains the database.
20
+
21
+ Returns:
22
+ The file that contains the database.
23
+ """
24
+ return data_dir() / "oldnews.db"
25
+
26
+
27
+ ##############################################################################
28
+ async def initialise_local_data() -> None:
29
+ """Initialise the local storage."""
30
+ Log().debug("Database startup")
31
+ await Tortoise.init(
32
+ db_url=f"sqlite://{local_db_file()}",
33
+ modules={"models": ["oldnews.data.models"]},
34
+ )
35
+ await Tortoise.generate_schemas()
36
+
37
+
38
+ ##############################################################################
39
+ async def shutdown_local_data() -> None:
40
+ """Close down the local connection."""
41
+ await Tortoise.close_connections()
42
+ Log().debug("Database shutdown")
43
+
44
+
45
+ ### local_data.py ends here
@@ -5,26 +5,16 @@
5
5
  from oldas import Folder, Folders
6
6
 
7
7
  ##############################################################################
8
- # TypeDAL imports.
9
- from typedal import TypedTable
8
+ # Tortoise imports.
9
+ from tortoise.transactions import in_transaction
10
10
 
11
11
  ##############################################################################
12
12
  # Local imports.
13
- from .tools import commit
13
+ from .models import LocalFolder
14
14
 
15
15
 
16
16
  ##############################################################################
17
- class LocalFolder(TypedTable):
18
- """A local copy of a folder."""
19
-
20
- folder_id: str
21
- """The ID of the folder."""
22
- sort_id: str
23
- """The sort ID of the folder."""
24
-
25
-
26
- ##############################################################################
27
- def get_local_folders() -> Folders:
17
+ async def get_local_folders() -> Folders:
28
18
  """Gets the local cache of known folders.
29
19
 
30
20
  Returns:
@@ -32,12 +22,12 @@ def get_local_folders() -> Folders:
32
22
  """
33
23
  return Folders(
34
24
  Folder(id=folder.folder_id, sort_id=folder.sort_id)
35
- for folder in LocalFolder.select(LocalFolder.ALL)
25
+ for folder in await LocalFolder.all()
36
26
  )
37
27
 
38
28
 
39
29
  ##############################################################################
40
- def save_local_folders(folders: Folders) -> Folders:
30
+ async def save_local_folders(folders: Folders) -> Folders:
41
31
  """Save the local copy of the known folders.
42
32
 
43
33
  Args:
@@ -46,11 +36,12 @@ def save_local_folders(folders: Folders) -> Folders:
46
36
  Returns:
47
37
  The folders.
48
38
  """
49
- LocalFolder.truncate()
50
- LocalFolder.bulk_insert(
51
- [{"folder_id": folder.id, "sort_id": folder.sort_id} for folder in folders]
52
- )
53
- commit(LocalFolder)
39
+ async with in_transaction():
40
+ await LocalFolder.all().delete()
41
+ await LocalFolder.bulk_create(
42
+ LocalFolder(folder_id=folder.id, sort_id=folder.sort_id)
43
+ for folder in folders
44
+ )
54
45
  return folders
55
46
 
56
47