mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-27 09:21:21 +01:00
Display flat feed list
This commit is contained in:
parent
f4a0c56a54
commit
8e99f8deea
@ -18,7 +18,7 @@ struct NewsBlurFeedsResponse: Decodable {
|
|||||||
|
|
||||||
struct Feed: Hashable, Codable {
|
struct Feed: Hashable, Codable {
|
||||||
let title: String
|
let title: String
|
||||||
let feedId: Int
|
let feedID: Int
|
||||||
let feedURL: String
|
let feedURL: String
|
||||||
let siteURL: String?
|
let siteURL: String?
|
||||||
let favicon: String?
|
let favicon: String?
|
||||||
@ -26,7 +26,7 @@ struct NewsBlurFeedsResponse: Decodable {
|
|||||||
|
|
||||||
struct Folder: Hashable, Codable {
|
struct Folder: Hashable, Codable {
|
||||||
let name: String
|
let name: String
|
||||||
let feedIds: [Int]
|
let feedIDs: [Int]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ extension NewsBlurFeedsResponse {
|
|||||||
let folderContainer = try container.nestedContainer(keyedBy: NewsBlurGenericCodingKeys.self, forKey: .folders)
|
let folderContainer = try container.nestedContainer(keyedBy: NewsBlurGenericCodingKeys.self, forKey: .folders)
|
||||||
try folderContainer.allKeys.forEach { key in
|
try folderContainer.allKeys.forEach { key in
|
||||||
let subscriptionIds = try folderContainer.decode([Int].self, forKey: key)
|
let subscriptionIds = try folderContainer.decode([Int].self, forKey: key)
|
||||||
let folder = Folder(name: key.stringValue, feedIds: subscriptionIds)
|
let folder = Folder(name: key.stringValue, feedIDs: subscriptionIds)
|
||||||
|
|
||||||
folders.append(folder)
|
folders.append(folder)
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ extension NewsBlurFeedsResponse {
|
|||||||
extension NewsBlurFeedsResponse.Feed {
|
extension NewsBlurFeedsResponse.Feed {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case title = "feed_title"
|
case title = "feed_title"
|
||||||
case feedId = "id"
|
case feedID = "id"
|
||||||
case feedURL = "feed_address"
|
case feedURL = "feed_address"
|
||||||
case siteURL = "feed_link"
|
case siteURL = "feed_link"
|
||||||
case favicon = "favicon_url"
|
case favicon = "favicon_url"
|
||||||
|
@ -16,8 +16,8 @@ struct NewsBlurStoriesResponse: Decodable {
|
|||||||
let stories: [Story]
|
let stories: [Story]
|
||||||
|
|
||||||
struct Story: Decodable {
|
struct Story: Decodable {
|
||||||
let storyId: String
|
let storyID: String
|
||||||
let feedId: Int
|
let feedID: Int
|
||||||
let title: String?
|
let title: String?
|
||||||
let url: String?
|
let url: String?
|
||||||
let authorName: String?
|
let authorName: String?
|
||||||
@ -34,8 +34,8 @@ extension NewsBlurStoriesResponse {
|
|||||||
|
|
||||||
extension NewsBlurStoriesResponse.Story {
|
extension NewsBlurStoriesResponse.Story {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case storyId = "story_hash"
|
case storyID = "story_hash"
|
||||||
case feedId = "story_feed_id"
|
case feedID = "story_feed_id"
|
||||||
case title = "story_title"
|
case title = "story_title"
|
||||||
case url = "story_permalink"
|
case url = "story_permalink"
|
||||||
case authorName = "story_authors"
|
case authorName = "story_authors"
|
||||||
|
@ -139,38 +139,6 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshUnreadStories(for account: Account, hashes: [NewsBlurStoryHash]?, updateFetchDate: Date?, completion: @escaping (Result<Void, Error>) -> Void) {
|
|
||||||
guard let hashes = hashes, !hashes.isEmpty else {
|
|
||||||
if let lastArticleFetch = updateFetchDate {
|
|
||||||
self.accountMetadata?.lastArticleFetchStartTime = lastArticleFetch
|
|
||||||
self.accountMetadata?.lastArticleFetchEndTime = Date()
|
|
||||||
}
|
|
||||||
completion(.success(()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let numberOfStories = min(hashes.count, 100) // api limit
|
|
||||||
let hashesToFetch = Array(hashes[..<numberOfStories])
|
|
||||||
|
|
||||||
caller.retrieveStories(hashes: hashesToFetch) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let stories):
|
|
||||||
self.processStories(account: account, stories: stories) { error in
|
|
||||||
self.refreshProgress.completeTask()
|
|
||||||
|
|
||||||
if let error = error {
|
|
||||||
completion(.failure(error))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.refreshUnreadStories(for: account, hashes: Array(hashes[numberOfStories...]), updateFetchDate: updateFetchDate, completion: completion)
|
|
||||||
}
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshMissingStories(for account: Account, completion: @escaping (Result<Void, Error>)-> Void) {
|
func refreshMissingStories(for account: Account, completion: @escaping (Result<Void, Error>)-> Void) {
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
@ -181,19 +149,6 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
|||||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapStoriesToParsedItems(stories: [NewsBlurStory]?) -> Set<ParsedItem> {
|
|
||||||
guard let stories = stories else {
|
|
||||||
return Set<ParsedItem>()
|
|
||||||
}
|
|
||||||
|
|
||||||
let parsedItems: [ParsedItem] = stories.map { story in
|
|
||||||
let author = Set([ParsedAuthor(name: story.authorName, url: nil, avatarURL: nil, emailAddress: nil)])
|
|
||||||
return ParsedItem(syncServiceID: story.storyId, uniqueID: String(story.storyId), feedURL: String(story.feedId), url: story.url, externalURL: nil, title: story.title, contentHTML: story.contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: story.datePublished, dateModified: nil, authors: author, tags: nil, attachments: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Set(parsedItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> ()) {
|
func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
@ -277,11 +232,109 @@ extension NewsBlurAccountDelegate {
|
|||||||
caller.retrieveFeeds { result in
|
caller.retrieveFeeds { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let feeds):
|
case .success(let feeds):
|
||||||
print(feeds)
|
self.refreshProgress.completeTask()
|
||||||
|
|
||||||
|
self.syncFeeds(account, feeds)
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func syncFeeds(_ account: Account, _ feeds: [NewsBlurFeed]?) {
|
||||||
|
guard let feeds = feeds else { return }
|
||||||
|
|
||||||
|
os_log(.debug, log: log, "Syncing feeds with %ld feeds.", feeds.count)
|
||||||
|
|
||||||
|
let subFeedIds = feeds.map { String($0.feedID) }
|
||||||
|
|
||||||
|
// Remove any feeds that are no longer in the subscriptions
|
||||||
|
if let folders = account.folders {
|
||||||
|
for folder in folders {
|
||||||
|
for feed in folder.topLevelWebFeeds {
|
||||||
|
if !subFeedIds.contains(feed.webFeedID) {
|
||||||
|
folder.removeWebFeed(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for feed in account.topLevelWebFeeds {
|
||||||
|
if !subFeedIds.contains(feed.webFeedID) {
|
||||||
|
account.removeWebFeed(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any feeds we don't have and update any we do
|
||||||
|
var feedsToAdd = Set<NewsBlurFeed>()
|
||||||
|
feeds.forEach { feed in
|
||||||
|
let subFeedId = String(feed.feedID)
|
||||||
|
|
||||||
|
if let webFeed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||||
|
webFeed.name = feed.title
|
||||||
|
// If the name has been changed on the server remove the locally edited name
|
||||||
|
webFeed.editedName = nil
|
||||||
|
webFeed.homePageURL = feed.siteURL
|
||||||
|
webFeed.subscriptionID = String(feed.feedID)
|
||||||
|
webFeed.faviconURL = feed.favicon
|
||||||
|
webFeed.iconURL = feed.favicon
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
feedsToAdd.insert(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually add feeds all in one go, so we don’t trigger various rebuilding things that Account does.
|
||||||
|
feedsToAdd.forEach { feed in
|
||||||
|
let webFeed = account.createWebFeed(with: feed.title, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.siteURL)
|
||||||
|
webFeed.subscriptionID = String(feed.feedID)
|
||||||
|
account.addWebFeed(webFeed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func refreshUnreadStories(for account: Account, hashes: [NewsBlurStoryHash]?, updateFetchDate: Date?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
guard let hashes = hashes, !hashes.isEmpty else {
|
||||||
|
if let lastArticleFetch = updateFetchDate {
|
||||||
|
self.accountMetadata?.lastArticleFetchStartTime = lastArticleFetch
|
||||||
|
self.accountMetadata?.lastArticleFetchEndTime = Date()
|
||||||
|
}
|
||||||
|
completion(.success(()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let numberOfStories = min(hashes.count, 100) // api limit
|
||||||
|
let hashesToFetch = Array(hashes[..<numberOfStories])
|
||||||
|
|
||||||
|
caller.retrieveStories(hashes: hashesToFetch) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let stories):
|
||||||
|
self.processStories(account: account, stories: stories) { error in
|
||||||
|
self.refreshProgress.completeTask()
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.refreshUnreadStories(for: account, hashes: Array(hashes[numberOfStories...]), updateFetchDate: updateFetchDate, completion: completion)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func mapStoriesToParsedItems(stories: [NewsBlurStory]?) -> Set<ParsedItem> {
|
||||||
|
guard let stories = stories else {
|
||||||
|
return Set<ParsedItem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedItems: [ParsedItem] = stories.map { story in
|
||||||
|
let author = Set([ParsedAuthor(name: story.authorName, url: nil, avatarURL: nil, emailAddress: nil)])
|
||||||
|
return ParsedItem(syncServiceID: story.storyID, uniqueID: String(story.storyID), feedURL: String(story.feedID), url: story.url, externalURL: nil, title: story.title, contentHTML: story.contentHTML, contentText: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: story.datePublished, dateModified: nil, authors: author, tags: nil, attachments: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Set(parsedItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user