From 5cec50e9b5275afcf8d69d0d5e037401a4c07057 Mon Sep 17 00:00:00 2001
From: lemon24 <damian.adrian24@gmail.com>
Date: Fri, 22 Oct 2021 11:38:57 +0300
Subject: [PATCH] Add Entry.source.

For #239.
---
 src/reader/_storage.py | 11 +++++++++--
 src/reader/_types.py   |  5 +++++
 src/reader/types.py    | 10 ++++++++++
 tests/test_reader.py   |  6 ++++++
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/reader/_storage.py b/src/reader/_storage.py
index 1bb50fa6..bebfaec2 100644
--- a/src/reader/_storage.py
+++ b/src/reader/_storage.py
@@ -130,6 +130,7 @@ def create_entries(db: sqlite3.Connection, name: str = 'entries') -> None:
             read_modified TIMESTAMP,
             important INTEGER NOT NULL DEFAULT 0,
             important_modified TIMESTAMP,
+            source TEXT NOT NULL,
             last_updated TIMESTAMP NOT NULL,
             first_updated TIMESTAMP NOT NULL,
             first_updated_epoch TIMESTAMP NOT NULL,
@@ -277,6 +278,7 @@ def update_from_33_to_34(db: sqlite3.Connection) -> None:  # pragma: no cover
             read_modified,
             important,
             important_modified,
+            source,
             last_updated,
             first_updated,
             first_updated_epoch,
@@ -300,6 +302,7 @@ def update_from_33_to_34(db: sqlite3.Connection) -> None:  # pragma: no cover
             read_modified,
             important,
             important_modified,
+            'feed',
             last_updated,
             first_updated_epoch,
             first_updated_epoch,
@@ -925,7 +928,8 @@ def make_params() -> Iterable[Mapping[str, Any]]:
                         feed_order,
                         original_feed,
                         data_hash,
-                        data_hash_changed
+                        data_hash_changed,
+                        source
                     ) VALUES (
                         :id,
                         :feed_url,
@@ -961,7 +965,8 @@ def make_params() -> Iterable[Mapping[str, Any]]:
                         :feed_order,
                         NULL, -- original_feed
                         :data_hash,
-                        :data_hash_changed
+                        :data_hash_changed,
+                        :source
                     );
                     """,
                     make_params(),
@@ -1376,6 +1381,7 @@ def make_get_entries_query(
             entries.first_updated
             entries.last_updated
             entries.original_feed
+            entries.source
             """.split()
         )
         .FROM("entries")
@@ -1410,6 +1416,7 @@ def entry_factory(t: Tuple[Any, ...]) -> Entry:
         t[25],
         t[26],
         t[27] or feed.url,
+        t[28],
         feed,
     )
     return Entry._make(entry)
diff --git a/src/reader/_types.py b/src/reader/_types.py
index ba2e3148..8f7b7ed4 100644
--- a/src/reader/_types.py
+++ b/src/reader/_types.py
@@ -22,6 +22,7 @@
 from .types import Enclosure
 from .types import Entry
 from .types import EntryInput
+from .types import EntrySource
 from .types import ExceptionInfo
 from .types import Feed
 from .types import FeedInput
@@ -112,6 +113,7 @@ def as_entry(self, **kwargs: object) -> Entry:
         attrs.pop('hash', None)
         attrs.update(kwargs)
         attrs.setdefault('original_feed_url', feed_url)
+        attrs.setdefault('source', 'feed')
         return Entry(**attrs)
 
     @property
@@ -223,6 +225,9 @@ class EntryUpdateIntent(NamedTuple):
     #: Same as EntryForUpdate.hash_changed.
     hash_changed: Optional[int]
 
+    #: Same as Entry.source.
+    source: EntrySource = 'feed'
+
     @property
     def new(self) -> bool:
         """Whether the entry is new or not."""
diff --git a/src/reader/types.py b/src/reader/types.py
index e6f9a9b5..87a3ff3b 100644
--- a/src/reader/types.py
+++ b/src/reader/types.py
@@ -186,6 +186,9 @@ def from_exception(cls: Type[_EI], exc: BaseException) -> _EI:
         )
 
 
+EntrySource = Literal['feed', 'user']
+
+
 @dataclass(frozen=True)
 class Entry(_namedtuple_compat):
 
@@ -294,6 +297,13 @@ def feed_url(self) -> str:
     # we don't check for it in __post_init__ because it's still useful
     # to have it None in tests. The cast is to please mypy.
 
+    #: The source of the entry. One of ``'feed'``, ``'user'``.
+    #:
+    #: Other values may be added in the future.
+    #:
+    #: .. versionadded:: 2.5
+    source: EntrySource = cast(EntrySource, None)
+
     #: The entry's feed.
     feed: Feed = cast(Feed, None)
 
diff --git a/tests/test_reader.py b/tests/test_reader.py
index 332af0d1..4cf2c825 100644
--- a/tests/test_reader.py
+++ b/tests/test_reader.py
@@ -3363,3 +3363,9 @@ def test_allow_invalid_url(make_reader, feed_root, url):
     assert excinfo.value.url == url
 
     reader.change_feed_url(old, url, allow_invalid_url=True)
+
+
+@rename_argument('reader', 'reader_with_one_feed')
+def test_entry_source(reader):
+    reader.update_feeds()
+    assert next(reader.get_entries()).source == 'feed'