oldnews 0.3.0__tar.gz → 0.5.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.
- {oldnews-0.3.0 → oldnews-0.5.0}/PKG-INFO +3 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/pyproject.toml +3 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/commands/__init__.py +20 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/commands/main.py +65 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/__init__.py +8 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/db.py +9 -36
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/last_grab.py +5 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_articles.py +78 -9
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_folders.py +5 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_subscriptions.py +31 -23
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_unread.py +2 -2
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/navigation_state.py +5 -2
- oldnews-0.5.0/src/oldnews/data/tools.py +45 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/providers/main.py +20 -2
- oldnews-0.5.0/src/oldnews/screens/folder_input.py +62 -0
- oldnews-0.5.0/src/oldnews/screens/information_display.py +86 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/main.py +342 -36
- oldnews-0.5.0/src/oldnews/screens/new_subscription.py +122 -0
- {oldnews-0.3.0/src/oldnews/sync → oldnews-0.5.0/src/oldnews}/sync.py +74 -16
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/navigation.py +19 -0
- oldnews-0.3.0/src/oldnews/sync/__init__.py +0 -11
- {oldnews-0.3.0 → oldnews-0.5.0}/README.md +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/__init__.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/__main__.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/auth.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/config.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/locations.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/reset.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/oldnews.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/providers/__init__.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/py.typed +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/__init__.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/login.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/__init__.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/_after_highlight.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/article_content.py +0 -0
- {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/article_list.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oldnews
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -15,8 +15,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
17
|
Requires-Dist: html-to-markdown>=2.15.0
|
|
18
|
-
Requires-Dist: oldas>=0.
|
|
18
|
+
Requires-Dist: oldas>=0.6.0
|
|
19
19
|
Requires-Dist: textual>=6.3.0
|
|
20
|
+
Requires-Dist: textual-autocomplete>=4.0.6
|
|
20
21
|
Requires-Dist: textual-enhanced>=1.2.0
|
|
21
22
|
Requires-Dist: typedal>=4.2.2
|
|
22
23
|
Requires-Dist: xdg-base-dirs>=6.0.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oldnews"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A terminal-based client for TheOldReader"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -20,8 +20,9 @@ keywords = [
|
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
22
|
"html-to-markdown>=2.15.0",
|
|
23
|
-
"oldas>=0.
|
|
23
|
+
"oldas>=0.6.0",
|
|
24
24
|
"textual>=6.3.0",
|
|
25
|
+
"textual-autocomplete>=4.0.6",
|
|
25
26
|
"textual-enhanced>=1.2.0",
|
|
26
27
|
"typedal>=4.2.2",
|
|
27
28
|
"xdg-base-dirs>=6.0.2",
|
|
@@ -3,30 +3,48 @@
|
|
|
3
3
|
##############################################################################
|
|
4
4
|
# Local imports.
|
|
5
5
|
from .main import (
|
|
6
|
+
AddSubscription,
|
|
7
|
+
Copy,
|
|
8
|
+
CopyArticleToClipboard,
|
|
9
|
+
CopyFeedToClipboard,
|
|
10
|
+
CopyHomePageToClipboard,
|
|
6
11
|
Escape,
|
|
12
|
+
Information,
|
|
7
13
|
MarkAllRead,
|
|
14
|
+
MoveSubscription,
|
|
8
15
|
Next,
|
|
9
16
|
NextUnread,
|
|
10
17
|
OpenArticle,
|
|
11
|
-
|
|
18
|
+
OpenHomePage,
|
|
12
19
|
Previous,
|
|
13
20
|
PreviousUnread,
|
|
14
21
|
RefreshFromTheOldReader,
|
|
22
|
+
Remove,
|
|
23
|
+
Rename,
|
|
15
24
|
ToggleShowAll,
|
|
16
25
|
)
|
|
17
26
|
|
|
18
27
|
##############################################################################
|
|
19
28
|
# Exports.
|
|
20
29
|
__all__ = [
|
|
30
|
+
"AddSubscription",
|
|
31
|
+
"Copy",
|
|
32
|
+
"CopyArticleToClipboard",
|
|
33
|
+
"CopyFeedToClipboard",
|
|
34
|
+
"CopyHomePageToClipboard",
|
|
21
35
|
"Escape",
|
|
36
|
+
"Information",
|
|
22
37
|
"MarkAllRead",
|
|
23
38
|
"Next",
|
|
24
39
|
"NextUnread",
|
|
40
|
+
"MoveSubscription",
|
|
25
41
|
"OpenArticle",
|
|
26
|
-
"
|
|
42
|
+
"OpenHomePage",
|
|
27
43
|
"Previous",
|
|
28
44
|
"PreviousUnread",
|
|
29
45
|
"RefreshFromTheOldReader",
|
|
46
|
+
"Rename",
|
|
47
|
+
"Remove",
|
|
30
48
|
"ToggleShowAll",
|
|
31
49
|
]
|
|
32
50
|
|
|
@@ -64,8 +64,8 @@ class OpenArticle(Command):
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
##############################################################################
|
|
67
|
-
class
|
|
68
|
-
"""Open the
|
|
67
|
+
class OpenHomePage(Command):
|
|
68
|
+
"""Open the home page for the current subscription in the web browser"""
|
|
69
69
|
|
|
70
70
|
BINDING_KEY = "O"
|
|
71
71
|
|
|
@@ -77,4 +77,67 @@ class MarkAllRead(Command):
|
|
|
77
77
|
BINDING_KEY = "R"
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
##############################################################################
|
|
81
|
+
class CopyHomePageToClipboard(Command):
|
|
82
|
+
"""Copy the URL of the current subscription's home page to the clipboard"""
|
|
83
|
+
|
|
84
|
+
BINDING_KEY = "f3"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
##############################################################################
|
|
88
|
+
class CopyFeedToClipboard(Command):
|
|
89
|
+
"""Copy the URL of the current subscription's feed to the clipboard"""
|
|
90
|
+
|
|
91
|
+
BINDING_KEY = "f4"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
##############################################################################
|
|
95
|
+
class CopyArticleToClipboard(Command):
|
|
96
|
+
"""Copy the URL for the current article to the clipboard"""
|
|
97
|
+
|
|
98
|
+
BINDING_KEY = "f5"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
##############################################################################
|
|
102
|
+
class Copy(Command):
|
|
103
|
+
"""Copy a URL to the clipboard depending on the context"""
|
|
104
|
+
|
|
105
|
+
BINDING_KEY = "ctrl+c"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
##############################################################################
|
|
109
|
+
class AddSubscription(Command):
|
|
110
|
+
"""Add a subscription feed"""
|
|
111
|
+
|
|
112
|
+
BINDING_KEY = "plus"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
##############################################################################
|
|
116
|
+
class Rename(Command):
|
|
117
|
+
"""Rename the current folder or subscription"""
|
|
118
|
+
|
|
119
|
+
BINDING_KEY = "f6"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
##############################################################################
|
|
123
|
+
class Remove(Command):
|
|
124
|
+
"""Remove the current folder or subscription"""
|
|
125
|
+
|
|
126
|
+
BINDING_KEY = "delete"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
##############################################################################
|
|
130
|
+
class MoveSubscription(Command):
|
|
131
|
+
"""Move the current subscription to folder"""
|
|
132
|
+
|
|
133
|
+
BINDING_KEY = "f7"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
##############################################################################
|
|
137
|
+
class Information(Command):
|
|
138
|
+
"""Show low-level information about the selected item"""
|
|
139
|
+
|
|
140
|
+
BINDING_KEY = "f8"
|
|
141
|
+
|
|
142
|
+
|
|
80
143
|
### main.py ends here
|
|
@@ -17,6 +17,10 @@ from .local_articles import (
|
|
|
17
17
|
get_unread_article_ids,
|
|
18
18
|
locally_mark_article_ids_read,
|
|
19
19
|
locally_mark_read,
|
|
20
|
+
move_subscription_articles,
|
|
21
|
+
remove_folder_from_articles,
|
|
22
|
+
remove_subscription_articles,
|
|
23
|
+
rename_folder_for_articles,
|
|
20
24
|
save_local_articles,
|
|
21
25
|
)
|
|
22
26
|
from .local_folders import get_local_folders, save_local_folders
|
|
@@ -43,7 +47,11 @@ __all__ = [
|
|
|
43
47
|
"locally_mark_read",
|
|
44
48
|
"locally_mark_article_ids_read",
|
|
45
49
|
"LocalUnread",
|
|
50
|
+
"move_subscription_articles",
|
|
46
51
|
"remember_we_last_grabbed_at",
|
|
52
|
+
"remove_folder_from_articles",
|
|
53
|
+
"remove_subscription_articles",
|
|
54
|
+
"rename_folder_for_articles",
|
|
47
55
|
"reset_data",
|
|
48
56
|
"save_configuration",
|
|
49
57
|
"save_local_articles",
|
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
##############################################################################
|
|
4
4
|
# Python imports.
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
6
|
|
|
8
7
|
##############################################################################
|
|
9
8
|
# TypeDAL imports.
|
|
10
|
-
from typedal import TypeDAL
|
|
9
|
+
from typedal import TypeDAL
|
|
11
10
|
from typedal.config import TypeDALConfig
|
|
12
|
-
from typedal.helpers import get_field
|
|
13
|
-
from typedal.types import Field
|
|
14
11
|
|
|
15
12
|
##############################################################################
|
|
16
13
|
# Local imports.
|
|
@@ -20,6 +17,7 @@ from .local_folders import LocalFolder
|
|
|
20
17
|
from .local_subscriptions import LocalSubscription, LocalSubscriptionCategory
|
|
21
18
|
from .locations import data_dir
|
|
22
19
|
from .navigation_state import NavigationState
|
|
20
|
+
from .tools import safely_index
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
##############################################################################
|
|
@@ -32,31 +30,6 @@ def db_file() -> Path:
|
|
|
32
30
|
return data_dir() / "oldnews.db"
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
##############################################################################
|
|
36
|
-
def _safely_index(
|
|
37
|
-
table: type[TypedTable], name: str, field: str | Field | TypedField[Any]
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Create an index on a type, but handle errors.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
table: The table to create the index against.
|
|
43
|
-
name: The name of the index.
|
|
44
|
-
field: The field to index.
|
|
45
|
-
|
|
46
|
-
Notes:
|
|
47
|
-
From what I can gather TypeDAL *should* only create the index if it
|
|
48
|
-
doesn't exist. Instead it throws an error if it exists. So here I
|
|
49
|
-
swallow the `RuntimeError`. Hopefully there is a better way and I've
|
|
50
|
-
just missed it.
|
|
51
|
-
"""
|
|
52
|
-
try:
|
|
53
|
-
table.create_index(
|
|
54
|
-
name, get_field(field) if isinstance(field, TypedField) else field
|
|
55
|
-
)
|
|
56
|
-
except RuntimeError:
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
|
|
60
33
|
##############################################################################
|
|
61
34
|
def initialise_database() -> TypeDAL:
|
|
62
35
|
"""Create the database.
|
|
@@ -73,20 +46,20 @@ def initialise_database() -> TypeDAL:
|
|
|
73
46
|
dal = TypeDAL(f"sqlite://{db_file()}", folder=data_dir(), config=TypeDALConfig())
|
|
74
47
|
|
|
75
48
|
dal.define(LocalArticle)
|
|
76
|
-
|
|
77
|
-
|
|
49
|
+
safely_index(LocalArticle, "idx_local_article_article_id", LocalArticle.article_id)
|
|
50
|
+
safely_index(
|
|
78
51
|
LocalArticle,
|
|
79
52
|
"idx_local_article_origin_stream_id",
|
|
80
53
|
LocalArticle.origin_stream_id,
|
|
81
54
|
)
|
|
82
55
|
|
|
83
56
|
dal.define(LocalArticleCategory)
|
|
84
|
-
|
|
57
|
+
safely_index(
|
|
85
58
|
LocalArticleCategory,
|
|
86
59
|
"idx_local_article_category_article",
|
|
87
60
|
LocalArticleCategory.article,
|
|
88
61
|
)
|
|
89
|
-
|
|
62
|
+
safely_index(
|
|
90
63
|
LocalArticleCategory,
|
|
91
64
|
"idx_local_article_category_category",
|
|
92
65
|
LocalArticleCategory.category,
|
|
@@ -97,19 +70,19 @@ def initialise_database() -> TypeDAL:
|
|
|
97
70
|
dal.define(LocalFolder)
|
|
98
71
|
|
|
99
72
|
dal.define(LocalSubscription)
|
|
100
|
-
|
|
73
|
+
safely_index(
|
|
101
74
|
LocalSubscription,
|
|
102
75
|
"idx_local_subscription_subscription_id",
|
|
103
76
|
LocalSubscription.subscription_id,
|
|
104
77
|
)
|
|
105
78
|
|
|
106
79
|
dal.define(LocalSubscriptionCategory)
|
|
107
|
-
|
|
80
|
+
safely_index(
|
|
108
81
|
LocalSubscriptionCategory,
|
|
109
82
|
"idx_local_subscription_category_subscription",
|
|
110
83
|
LocalSubscriptionCategory.subscription,
|
|
111
84
|
)
|
|
112
|
-
|
|
85
|
+
safely_index(
|
|
113
86
|
LocalSubscriptionCategory,
|
|
114
87
|
"idx_local_subscription_category_category_id",
|
|
115
88
|
LocalSubscriptionCategory.category_id,
|
|
@@ -8,6 +8,10 @@ from datetime import datetime, timezone
|
|
|
8
8
|
# TypeDAL imports.
|
|
9
9
|
from typedal import TypedTable
|
|
10
10
|
|
|
11
|
+
##############################################################################
|
|
12
|
+
# Local imports.
|
|
13
|
+
from .tools import commit
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
##############################################################################
|
|
13
17
|
class LastGrabbed(TypedTable):
|
|
@@ -39,10 +43,9 @@ def remember_we_last_grabbed_at(grab_time: datetime | None = None) -> None:
|
|
|
39
43
|
Note:
|
|
40
44
|
If `grab_time` isn't supplied then it is recorded as now.
|
|
41
45
|
"""
|
|
42
|
-
assert LastGrabbed._db is not None
|
|
43
46
|
LastGrabbed.truncate()
|
|
44
47
|
LastGrabbed.insert(at_time=grab_time or datetime.now(timezone.utc))
|
|
45
|
-
|
|
48
|
+
commit(LastGrabbed)
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
### last_grab.py ends here
|
|
@@ -8,13 +8,17 @@ from typing import Iterable, Iterator, cast
|
|
|
8
8
|
|
|
9
9
|
##############################################################################
|
|
10
10
|
# OldAS imports.
|
|
11
|
-
from oldas import Article, Articles, Folder, State, Subscription
|
|
11
|
+
from oldas import Article, Articles, Folder, Folders, State, Subscription
|
|
12
12
|
from oldas.articles import Alternate, Alternates, Direction, Origin, Summary
|
|
13
13
|
|
|
14
14
|
##############################################################################
|
|
15
15
|
# TypeDAL imports.
|
|
16
16
|
from typedal import TypedField, TypedTable, relationship
|
|
17
17
|
|
|
18
|
+
##############################################################################
|
|
19
|
+
# Local imports.
|
|
20
|
+
from .tools import commit
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
##############################################################################
|
|
20
24
|
class LocalArticle(TypedTable):
|
|
@@ -62,9 +66,8 @@ class LocalArticle(TypedTable):
|
|
|
62
66
|
category: The category to add.
|
|
63
67
|
"""
|
|
64
68
|
if not str(category) in self.categories:
|
|
65
|
-
assert LocalArticleCategory._db is not None
|
|
66
69
|
LocalArticleCategory.insert(article=self.id, category=str(category))
|
|
67
|
-
|
|
70
|
+
commit(LocalArticleCategory)
|
|
68
71
|
|
|
69
72
|
def remove_category(self, category: str | State) -> None:
|
|
70
73
|
"""Remove a given category from the local article.
|
|
@@ -73,12 +76,11 @@ class LocalArticle(TypedTable):
|
|
|
73
76
|
category: The category to add.
|
|
74
77
|
"""
|
|
75
78
|
if str(category) in self.categories:
|
|
76
|
-
assert LocalArticleCategory._db is not None
|
|
77
79
|
LocalArticleCategory.where(
|
|
78
80
|
(LocalArticleCategory.article == self.id)
|
|
79
81
|
& (LocalArticleCategory.category == str(category))
|
|
80
82
|
).delete()
|
|
81
|
-
|
|
83
|
+
commit(LocalArticleCategory)
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
##############################################################################
|
|
@@ -113,7 +115,6 @@ def save_local_articles(articles: Articles) -> Articles:
|
|
|
113
115
|
Returns:
|
|
114
116
|
The articles.
|
|
115
117
|
"""
|
|
116
|
-
assert LocalArticle._db is not None
|
|
117
118
|
for article in articles:
|
|
118
119
|
local_article = LocalArticle.update_or_insert(
|
|
119
120
|
LocalArticle.article_id == article.id,
|
|
@@ -146,7 +147,7 @@ def save_local_articles(articles: Articles) -> Articles:
|
|
|
146
147
|
for alternate in article.alternate
|
|
147
148
|
]
|
|
148
149
|
)
|
|
149
|
-
|
|
150
|
+
commit(LocalArticle)
|
|
150
151
|
return articles
|
|
151
152
|
|
|
152
153
|
|
|
@@ -336,7 +337,6 @@ def get_unread_article_ids() -> list[str]:
|
|
|
336
337
|
##############################################################################
|
|
337
338
|
def clean_old_read_articles(cutoff: timedelta) -> int:
|
|
338
339
|
"""Clean up articles that are older than the given cutoff time."""
|
|
339
|
-
assert LocalArticle._db is not None
|
|
340
340
|
read = get_local_read_article_ids()
|
|
341
341
|
retire_time = datetime.now() - cutoff
|
|
342
342
|
cleaned = len(
|
|
@@ -344,8 +344,77 @@ def clean_old_read_articles(cutoff: timedelta) -> int:
|
|
|
344
344
|
(LocalArticle.published < retire_time) & LocalArticle.id.belongs(read)
|
|
345
345
|
).delete()
|
|
346
346
|
)
|
|
347
|
-
|
|
347
|
+
commit(LocalArticle)
|
|
348
348
|
return cleaned
|
|
349
349
|
|
|
350
350
|
|
|
351
|
+
##############################################################################
|
|
352
|
+
def rename_folder_for_articles(rename_from: str | Folder, rename_to: str) -> None:
|
|
353
|
+
"""Rename a folder for all articles that are in that folder.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
rename_from: The folder name to rename from.
|
|
357
|
+
rename_to: The folder name to rename to.
|
|
358
|
+
"""
|
|
359
|
+
rename_from = Folders.full_id(rename_from)
|
|
360
|
+
rename_to = Folders.full_id(rename_to)
|
|
361
|
+
LocalArticleCategory.where(LocalArticleCategory.category == rename_from).update(
|
|
362
|
+
category=rename_to
|
|
363
|
+
)
|
|
364
|
+
commit(LocalArticleCategory)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
##############################################################################
|
|
368
|
+
def remove_folder_from_articles(folder: str | Folder) -> None:
|
|
369
|
+
"""Remove a folder from being associated with all articles.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
folder: The folder to remove from all articles.
|
|
373
|
+
"""
|
|
374
|
+
LocalArticleCategory.where(
|
|
375
|
+
LocalArticleCategory.category == Folders.full_id(folder)
|
|
376
|
+
).delete()
|
|
377
|
+
commit(LocalArticleCategory)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
##############################################################################
|
|
381
|
+
def move_subscription_articles(
|
|
382
|
+
subscription: Subscription,
|
|
383
|
+
from_folder: str | Folder | None,
|
|
384
|
+
to_folder: str | Folder | None,
|
|
385
|
+
) -> None:
|
|
386
|
+
"""Move the articles of a subscription from one folder to another.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
subscription: The subscription whose articles we should move.
|
|
390
|
+
from_folder: The folder to move from.
|
|
391
|
+
to_folder: The folder to move to.
|
|
392
|
+
"""
|
|
393
|
+
from_folder = (
|
|
394
|
+
Folders.full_id(from_folder) if from_folder is not None else from_folder
|
|
395
|
+
)
|
|
396
|
+
to_folder = Folders.full_id(to_folder) if to_folder is not None else to_folder
|
|
397
|
+
for article in LocalArticle.where(origin_stream_id=subscription.id).join().select():
|
|
398
|
+
if from_folder:
|
|
399
|
+
LocalArticleCategory.where(
|
|
400
|
+
(LocalArticleCategory.article == article.id)
|
|
401
|
+
& (LocalArticleCategory.category == from_folder)
|
|
402
|
+
).delete()
|
|
403
|
+
commit(LocalArticleCategory)
|
|
404
|
+
if to_folder:
|
|
405
|
+
LocalArticleCategory.insert(article=article.id, category=to_folder)
|
|
406
|
+
commit(LocalArticleCategory)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
##############################################################################
|
|
410
|
+
def remove_subscription_articles(subscription: Subscription) -> None:
|
|
411
|
+
"""Remove all the articles associated with the given subscription.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
subscription: The subscription to remove the articles for.
|
|
415
|
+
"""
|
|
416
|
+
LocalArticle.where(origin_stream_id=subscription.id).delete()
|
|
417
|
+
commit(LocalArticle)
|
|
418
|
+
|
|
419
|
+
|
|
351
420
|
### local_articles.py ends here
|
|
@@ -8,6 +8,10 @@ from oldas import Folder, Folders
|
|
|
8
8
|
# TypeDAL imports.
|
|
9
9
|
from typedal import TypedTable
|
|
10
10
|
|
|
11
|
+
##############################################################################
|
|
12
|
+
# Local imports.
|
|
13
|
+
from .tools import commit
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
##############################################################################
|
|
13
17
|
class LocalFolder(TypedTable):
|
|
@@ -42,12 +46,11 @@ def save_local_folders(folders: Folders) -> Folders:
|
|
|
42
46
|
Returns:
|
|
43
47
|
The folders.
|
|
44
48
|
"""
|
|
45
|
-
assert LocalFolder._db is not None
|
|
46
49
|
LocalFolder.truncate()
|
|
47
50
|
LocalFolder.bulk_insert(
|
|
48
51
|
[{"folder_id": folder.id, "sort_id": folder.sort_id} for folder in folders]
|
|
49
52
|
)
|
|
50
|
-
|
|
53
|
+
commit(LocalFolder)
|
|
51
54
|
return folders
|
|
52
55
|
|
|
53
56
|
|
|
@@ -13,6 +13,10 @@ from oldas.subscriptions import Categories, Category
|
|
|
13
13
|
# TypeDAL imports.
|
|
14
14
|
from typedal import TypedTable
|
|
15
15
|
|
|
16
|
+
##############################################################################
|
|
17
|
+
# Local imports.
|
|
18
|
+
from .tools import commit
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
##############################################################################
|
|
18
22
|
class LocalSubscription(TypedTable):
|
|
@@ -83,29 +87,33 @@ def save_local_subscriptions(subscriptions: Subscriptions) -> Subscriptions:
|
|
|
83
87
|
Returns:
|
|
84
88
|
The subscriptions.
|
|
85
89
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
90
|
+
LocalSubscription.truncate()
|
|
91
|
+
LocalSubscriptionCategory.truncate()
|
|
92
|
+
LocalSubscription.bulk_insert(
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"subscription_id": subscription.id,
|
|
96
|
+
"title": subscription.title,
|
|
97
|
+
"sort_id": subscription.sort_id,
|
|
98
|
+
"first_item_time": subscription.first_item_time,
|
|
99
|
+
"url": subscription.url,
|
|
100
|
+
"html_url": subscription.html_url,
|
|
101
|
+
}
|
|
102
|
+
for subscription in subscriptions
|
|
103
|
+
]
|
|
104
|
+
)
|
|
105
|
+
LocalSubscriptionCategory.bulk_insert(
|
|
106
|
+
[
|
|
107
|
+
{
|
|
108
|
+
"subscription": subscription.id,
|
|
109
|
+
"category_id": category.id,
|
|
110
|
+
"label": category.label,
|
|
111
|
+
}
|
|
112
|
+
for subscription in subscriptions
|
|
113
|
+
for category in subscription.categories
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
commit(LocalSubscription)
|
|
109
117
|
return subscriptions
|
|
110
118
|
|
|
111
119
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
##############################################################################
|
|
4
4
|
# OldAS imports.
|
|
5
|
-
from oldas import Folders, Subscriptions,
|
|
5
|
+
from oldas import Folders, Subscriptions, id_is_a_feed
|
|
6
6
|
|
|
7
7
|
##############################################################################
|
|
8
8
|
# Local imports.
|
|
@@ -41,7 +41,7 @@ def total_unread(unread: LocalUnread) -> int:
|
|
|
41
41
|
Returns:
|
|
42
42
|
The total unread.
|
|
43
43
|
"""
|
|
44
|
-
return sum(count for category, count in unread.items() if
|
|
44
|
+
return sum(count for category, count in unread.items() if id_is_a_feed(category))
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
### local_unread.py ends here
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
# TypeDAL imports.
|
|
5
5
|
from typedal import TypedTable
|
|
6
6
|
|
|
7
|
+
##############################################################################
|
|
8
|
+
# Local imports.
|
|
9
|
+
from .tools import commit
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
##############################################################################
|
|
9
13
|
class NavigationState(TypedTable):
|
|
@@ -33,10 +37,9 @@ def save_navigation_state(state: set[str]) -> None:
|
|
|
33
37
|
Args:
|
|
34
38
|
state: The state to save.
|
|
35
39
|
"""
|
|
36
|
-
assert NavigationState._db is not None
|
|
37
40
|
NavigationState.truncate()
|
|
38
41
|
NavigationState.bulk_insert([{"expanded_folder_id": folder} for folder in state])
|
|
39
|
-
|
|
42
|
+
commit(NavigationState)
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
### navigation_state.py ends here
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Database tool functions."""
|
|
2
|
+
|
|
3
|
+
##############################################################################
|
|
4
|
+
# Python imports.
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
##############################################################################
|
|
8
|
+
# TypeDAL imports.
|
|
9
|
+
from typedal import TypedField, TypedTable
|
|
10
|
+
from typedal.helpers import get_db, get_field
|
|
11
|
+
from typedal.types import Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##############################################################################
|
|
15
|
+
def safely_index(
|
|
16
|
+
table: type[TypedTable], name: str, field: str | Field | TypedField[Any]
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Create an index on a type, but handle errors.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
table: The table to create the index against.
|
|
22
|
+
name: The name of the index.
|
|
23
|
+
field: The field to index.
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
From what I can gather TypeDAL *should* only create the index if it
|
|
27
|
+
doesn't exist. Instead it throws an error if it exists. So here I
|
|
28
|
+
swallow the `RuntimeError`. Hopefully there is a better way and I've
|
|
29
|
+
just missed it.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
table.create_index(
|
|
33
|
+
name, get_field(field) if isinstance(field, TypedField) else field
|
|
34
|
+
)
|
|
35
|
+
except RuntimeError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
##############################################################################
|
|
40
|
+
def commit(table: type[TypedTable]) -> None:
|
|
41
|
+
"""Commit changes."""
|
|
42
|
+
get_db(table()).commit()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### tools.py ends here
|