<commit_before>//
//  ArticleUtilities.swift
//  NetNewsWire
//
//  Created by <NAME> on 7/25/15.
//  Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//

import Foundation
import RSCore
import Articles
import Account

// These handle multiple accounts.

func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) {
	
	let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)

	for (accountID, accountArticles) in d {
		guard let account = AccountManager.shared.existingAccount(with: accountID) else {
			continue
		}
		account.markArticles(accountArticles, statusKey: statusKey, flag: flag)
	}
}

private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String: Set<Article>] {
	
	let d = Dictionary(grouping: articles, by: { $0.accountID })
	return d.mapValues{ Set($0) }
}

extension Article {
	
	var webFeed: WebFeed? {
		return account?.existingWebFeed(withWebFeedID: webFeedID)
	}
	
	var preferredLink: String? {
		if let url = url, !url.isEmpty {
			return url
		}
		if let externalURL = externalURL, !externalURL.isEmpty {
			return externalURL
		}
		return nil
	}
	
	var body: String? {
		return contentHTML ?? contentText ?? summary
	}
	
	var logicalDatePublished: Date {
		return datePublished ?? dateModified ?? status.dateArrived
	}
	
	var isAvailableToMarkUnread: Bool {
		guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
			switch behavior {
			case .disallowMarkAsUnreadAfterPeriod(let days):
				return days
			default:
				return nil
			}
		}).first else {
			return true
		}
		
		if logicalDatePublished.byAdding(days: markUnreadWindow) > Date() {
			return true
		} else {
			return false
		}
	}

	func iconImage() -> IconImage? {
		if let authors = authors, authors.count == 1, let author = authors.first {
			if let image = appDelegate.authorAvatarDownloader.image(for: author) {
				return image
			}
		}
		
		if let authors = webFeed?.authors, authors.count == 1, let author = authors.first {
			if let image = appDelegate.authorAvatarDownloader.image(for: author) {
				return image
			}
		}

		guard let webFeed = webFeed else {
			return nil
		}
		
		let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
		if feedIconImage != nil {
			return feedIconImage
		}
		
		if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
			return faviconImage
		}
		
		return FaviconGenerator.favicon(webFeed)
	}
	
	func iconImageUrl(webFeed: WebFeed) -> URL? {
		if let image = iconImage() {
			let fm = FileManager.default
			var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
			#if os(macOS)
			path.appendPathComponent(webFeed.webFeedID + "_smallIcon.tiff")
			#else
			path.appendPathComponent(webFeed.webFeedID + "_smallIcon.png")
			#endif
			fm.createFile(atPath: path.path, contents: image.image.dataRepresentation()!, attributes: nil)
			return path
		} else {
			return nil
		}
	}
	
	func byline() -> String {
		guard let authors = authors ?? webFeed?.authors, !authors.isEmpty else {
			return ""
		}

		// If the author's name is the same as the feed, then we don't want to display it.
		// This code assumes that multiple authors would never match the feed name so that
		// if there feed owner has an article co-author all authors are given the byline.
		if authors.count == 1, let author = authors.first {
			if author.name == webFeed?.nameForDisplay {
				return ""<commit_msg>Fix pathing problem with icon image urls<commit_after>//
//  ArticleUtilities.swift
//  NetNewsWire
//
//  Created by <NAME> on 7/25/15.
//  Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//

import Foundation
import RSCore
import Articles
import Account

// These handle multiple accounts.

func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) {
	
	let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)

	for (accountID, accountArticles) in d {
		guard let account = AccountManager.shared.existingAccount(with: accountID) else {
			continue
		}
		account.markArticles(accountArticles, statusKey: statusKey, flag: flag)
	}
}

private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String: Set<Article>] {
	
	let d = Dictionary(grouping: articles, by: { $0.accountID })
	return d.mapValues{ Set($0) }
}

extension Article {
	
	var webFeed: WebFeed? {
		return account?.existingWebFeed(withWebFeedID: webFeedID)
	}
	
	var preferredLink: String? {
		if let url = url, !url.isEmpty {
			return url
		}
		if let externalURL = externalURL, !externalURL.isEmpty {
			return externalURL
		}
		return nil
	}
	
	var body: String? {
		return contentHTML ?? contentText ?? summary
	}
	
	var logicalDatePublished: Date {
		return datePublished ?? dateModified ?? status.dateArrived
	}
	
	var isAvailableToMarkUnread: Bool {
		guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
			switch behavior {
			case .disallowMarkAsUnreadAfterPeriod(let days):
				return days
			default:
				return nil
			}
		}).first else {
			return true
		}
		
		if logicalDatePublished.byAdding(days: markUnreadWindow) > Date() {
			return true
		} else {
			return false
		}
	}

	func iconImage() -> IconImage? {
		if let authors = authors, authors.count == 1, let author = authors.first {
			if let image = appDelegate.authorAvatarDownloader.image(for: author) {
				return image
			}
		}
		
		if let authors = webFeed?.authors, authors.count == 1, let author = authors.first {
			if let image = appDelegate.authorAvatarDownloader.image(for: author) {
				return image
			}
		}

		guard let webFeed = webFeed else {
			return nil
		}
		
		let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
		if feedIconImage != nil {
			return feedIconImage
		}
		
		if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
			return faviconImage
		}
		
		return FaviconGenerator.favicon(webFeed)
	}
	
	func iconImageUrl(webFeed: WebFeed) -> URL? {
		if let image = iconImage() {
			let fm = FileManager.default
			var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
			let feedID = webFeed.webFeedID.replacingOccurrences(of: "/", with: "_")
			#if os(macOS)
			path.appendPathComponent(feedID + "_smallIcon.tiff")
			#else
			path.appendPathComponent(feedID + "_smallIcon.png")
			#endif
			fm.createFile(atPath: path.path, contents: image.image.dataRepresentation()!, attributes: nil)
			return path
		} else {
			return nil
		}
	}
	
	func byline() -> String {
		guard let authors = authors ?? webFeed?.authors, !authors.isEmpty else {
			return ""
		}

		// If the author's name is the same as the feed, then we don't want to display it.
		// This code assumes that multiple authors would never match the feed name so that
		// if there feed owner has an article co-author all authors are given the byline.
		if authors.count == 1, let author = authors.first {
			if author.name == webFeed?.nameForDisplay {
				return ""
{'fraction_non_alphanumeric': 0.1024508406953548, 'fraction_numerical': 0.004274722143060701, 'mean_word_length': 3.5815926892950394, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4427180', 'n_tokens_mistral': 2518, 'n_tokens_neox': 2145, 'n_words': 632}