mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-02 20:16:54 +01:00
Get Account.framework building.
This commit is contained in:
parent
306836766a
commit
e78fc0d696
@ -9,6 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import Data
|
import Data
|
||||||
|
import RSParser
|
||||||
|
|
||||||
public enum AccountType: Int {
|
public enum AccountType: Int {
|
||||||
|
|
||||||
@ -23,9 +24,9 @@ public enum AccountType: Int {
|
|||||||
|
|
||||||
public final class Account: DisplayNameProvider, Hashable {
|
public final class Account: DisplayNameProvider, Hashable {
|
||||||
|
|
||||||
public let identifier: String
|
public let accountID: String
|
||||||
public let type: AccountType
|
public let type: AccountType
|
||||||
public var nameForDisplay: String?
|
public var nameForDisplay = ""
|
||||||
public let delegate: AccountDelegate
|
public let delegate: AccountDelegate
|
||||||
public let hashValue: Int
|
public let hashValue: Int
|
||||||
let settingsFile: String
|
let settingsFile: String
|
||||||
@ -33,18 +34,20 @@ public final class Account: DisplayNameProvider, Hashable {
|
|||||||
var topLevelObjects = [AnyObject]()
|
var topLevelObjects = [AnyObject]()
|
||||||
var feedIDDictionary = [String: Feed]()
|
var feedIDDictionary = [String: Feed]()
|
||||||
var username: String?
|
var username: String?
|
||||||
|
|
||||||
init?(dataFolder: String, settingsFile: String, type: AccountType, identifier: String) {
|
|
||||||
|
|
||||||
self.identifier = identifier
|
static public let accounts = [String: Account]()
|
||||||
|
|
||||||
|
init?(dataFolder: String, settingsFile: String, type: AccountType, accountID: String) {
|
||||||
|
|
||||||
|
self.accountID = accountID
|
||||||
self.type = type
|
self.type = type
|
||||||
self.settingsFile = settingsFile
|
self.settingsFile = settingsFile
|
||||||
self.dataFolder = dataFolder
|
self.dataFolder = dataFolder
|
||||||
self.hashValue = identifier.hashValue
|
self.hashValue = accountID.hashValue
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
|
|
||||||
case onMyMac:
|
case .onMyMac:
|
||||||
self.delegate = LocalAccountDelegate()
|
self.delegate = LocalAccountDelegate()
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
@ -53,11 +56,21 @@ public final class Account: DisplayNameProvider, Hashable {
|
|||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
||||||
|
static public func existingAccountWithID(_ accountID: String) -> Account? {
|
||||||
|
|
||||||
|
return accounts[accountID]
|
||||||
|
}
|
||||||
|
|
||||||
public func refreshAll() {
|
public func refreshAll() {
|
||||||
|
|
||||||
delegate.refreshAll(for: self)
|
delegate.refreshAll(for: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: RSVoidCompletionBlock) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Equatable
|
// MARK: - Equatable
|
||||||
|
|
||||||
public class func ==(lhs: Account, rhs: Account) -> Bool {
|
public class func ==(lhs: Account, rhs: Account) -> Bool {
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841974241F6DDCE4006346C4 /* AccountDelegate.swift */; };
|
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841974241F6DDCE4006346C4 /* AccountDelegate.swift */; };
|
||||||
8469F8171F6DD0AD0084783E /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8401A17D1F6DC388002B1BE2 /* Database.framework */; };
|
8469F8171F6DD0AD0084783E /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8401A17D1F6DC388002B1BE2 /* Database.framework */; };
|
||||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
||||||
|
846E77451F6EF9B900A165E2 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419740D1F6DD25F006346C4 /* Container.swift */; };
|
||||||
|
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742C1F6DDE84006346C4 /* LocalAccountDelegate.swift */; };
|
||||||
|
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; };
|
||||||
|
846E77521F6EFDFB00A165E2 /* Feed+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77511F6EFDFB00A165E2 /* Feed+Account.swift */; };
|
||||||
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
|
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
|
||||||
848935051F62485000CEBD24 /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935041F62485000CEBD24 /* AccountTests.swift */; };
|
848935051F62485000CEBD24 /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935041F62485000CEBD24 /* AccountTests.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@ -105,6 +109,7 @@
|
|||||||
841974241F6DDCE4006346C4 /* AccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDelegate.swift; sourceTree = "<group>"; };
|
841974241F6DDCE4006346C4 /* AccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDelegate.swift; sourceTree = "<group>"; };
|
||||||
8419742C1F6DDE84006346C4 /* LocalAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountDelegate.swift; sourceTree = "<group>"; };
|
8419742C1F6DDE84006346C4 /* LocalAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountDelegate.swift; sourceTree = "<group>"; };
|
||||||
8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountRefresher.swift; sourceTree = "<group>"; };
|
8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountRefresher.swift; sourceTree = "<group>"; };
|
||||||
|
846E77511F6EFDFB00A165E2 /* Feed+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+Account.swift"; sourceTree = "<group>"; };
|
||||||
848934F61F62484F00CEBD24 /* Account.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Account.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
848934F61F62484F00CEBD24 /* Account.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Account.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
848934FA1F62484F00CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
848934FA1F62484F00CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
848934FF1F62484F00CEBD24 /* AccountTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AccountTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
848934FF1F62484F00CEBD24 /* AccountTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AccountTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -209,6 +214,7 @@
|
|||||||
848935101F62486800CEBD24 /* Account.swift */,
|
848935101F62486800CEBD24 /* Account.swift */,
|
||||||
841974241F6DDCE4006346C4 /* AccountDelegate.swift */,
|
841974241F6DDCE4006346C4 /* AccountDelegate.swift */,
|
||||||
841974001F6DD1EC006346C4 /* Folder.swift */,
|
841974001F6DD1EC006346C4 /* Folder.swift */,
|
||||||
|
846E77511F6EFDFB00A165E2 /* Feed+Account.swift */,
|
||||||
841974141F6DD4FF006346C4 /* Container */,
|
841974141F6DD4FF006346C4 /* Container */,
|
||||||
8419742B1F6DDE84006346C4 /* Local */,
|
8419742B1F6DDE84006346C4 /* Local */,
|
||||||
8469F80F1F6DC3C10084783E /* Frameworks */,
|
8469F80F1F6DC3C10084783E /* Frameworks */,
|
||||||
@ -430,10 +436,14 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
846E77521F6EFDFB00A165E2 /* Feed+Account.swift in Sources */,
|
||||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,
|
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,
|
||||||
|
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
||||||
8419741A1F6DD583006346C4 /* Account+Container.swift in Sources */,
|
8419741A1F6DD583006346C4 /* Account+Container.swift in Sources */,
|
||||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||||
|
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */,
|
||||||
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
||||||
|
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||||
841974181F6DD535006346C4 /* Folder+Container.swift in Sources */,
|
841974181F6DD535006346C4 /* Folder+Container.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -10,9 +10,6 @@ import Foundation
|
|||||||
|
|
||||||
public protocol AccountDelegate {
|
public protocol AccountDelegate {
|
||||||
|
|
||||||
func refreshAll(for account: Account) {
|
func refreshAll(for account: Account)
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Data
|
||||||
|
|
||||||
extension Account: Container {
|
extension Account: Container {
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ extension Account: Container {
|
|||||||
|
|
||||||
public func canAddItem(_ item: AnyObject) -> Bool {
|
public func canAddItem(_ item: AnyObject) -> Bool {
|
||||||
|
|
||||||
return delegate.canAddItem(item, toContainer: self)
|
return false // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isChild(_ obj: AnyObject) -> Bool {
|
public func isChild(_ obj: AnyObject) -> Bool {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
|
import Data
|
||||||
|
|
||||||
public typealias VisitBlock = (_ obj: AnyObject) -> Bool // Return true to stop
|
public typealias VisitBlock = (_ obj: AnyObject) -> Bool // Return true to stop
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ extension NSNotification.Name {
|
|||||||
public static let ChildrenDidChange = Notification.Name("ChildrenDidChangeNotification")
|
public static let ChildrenDidChange = Notification.Name("ChildrenDidChangeNotification")
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol Container: class {
|
public protocol Container {
|
||||||
|
|
||||||
//Recursive
|
//Recursive
|
||||||
func hasAtLeastOneFeed() -> Bool
|
func hasAtLeastOneFeed() -> Bool
|
||||||
@ -29,34 +30,6 @@ public protocol Container: class {
|
|||||||
// visitBlock should return true to stop visiting.
|
// visitBlock should return true to stop visiting.
|
||||||
// visitObjects returns true if a visitBlock returned true.
|
// visitObjects returns true if a visitBlock returned true.
|
||||||
func visitObjects(_ recurse: Bool, _ visitBlock: VisitBlock) -> Bool
|
func visitObjects(_ recurse: Bool, _ visitBlock: VisitBlock) -> Bool
|
||||||
|
|
||||||
//
|
|
||||||
//// func objectIsChild(_ obj: AnyObject) -> Bool
|
|
||||||
//// func objectIsDescendant(_ obj: AnyObject) -> Bool
|
|
||||||
////
|
|
||||||
//// func fetchArticles() -> [Article]
|
|
||||||
//
|
|
||||||
// // visitBlock should return true to stop visiting.
|
|
||||||
// // visitObjects returns true if a visitBlock returned true.
|
|
||||||
//// func visitObjects(_ recurse: Bool, visitBlock: VisitBlock) -> Bool
|
|
||||||
//// func visitChildren(visitBlock: VisitBlock) -> Bool // Above with recurse = false
|
|
||||||
////
|
|
||||||
//// func findObject(_ recurse: Bool, visitBlock: VisitBlock) -> AnyObject?
|
|
||||||
//
|
|
||||||
// func canAddItem(_ item: AnyObject) -> Bool
|
|
||||||
// func addItem(_ item: AnyObject) -> Bool // Return true even if item already exists.
|
|
||||||
// func addItems(_ items: [AnyObject]) -> Bool // Return true even if some items already exist.
|
|
||||||
//
|
|
||||||
// func canAddFolderWithName(_ folderName: String) -> Bool // Special case: folder with name exists. Return true in that case.
|
|
||||||
// func ensureFolderWithName(_ folderName: String) -> Folder? // Return folder even if item already exists.
|
|
||||||
//
|
|
||||||
// // Does not recurse.
|
|
||||||
// func existingFolderWithName(_ name: String) -> Folder?
|
|
||||||
//
|
|
||||||
// // Doesn't add feed. Just creates instance.
|
|
||||||
// func createFeedWithName(_ name: String?, editedName: String?, urlString: String) -> Feed?
|
|
||||||
//
|
|
||||||
// func deleteItems(_ items: [AnyObject])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Container {
|
public extension Container {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Data
|
||||||
|
|
||||||
extension Folder: Container {
|
extension Folder: Container {
|
||||||
|
|
||||||
|
19
Frameworks/Account/Feed+Account.swift
Normal file
19
Frameworks/Account/Feed+Account.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Feed+Account.swift
|
||||||
|
// Account
|
||||||
|
//
|
||||||
|
// Created by Brent Simmons on 9/17/17.
|
||||||
|
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Data
|
||||||
|
|
||||||
|
public extension Feed {
|
||||||
|
|
||||||
|
var account: Account? {
|
||||||
|
get {
|
||||||
|
return Account.existingAccountWithID(accountID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,36 +34,7 @@ public final class Folder: DisplayNameProvider, UnreadCountProvider {
|
|||||||
|
|
||||||
self.accountID = accountID
|
self.accountID = accountID
|
||||||
self.nameForDisplay = nameForDisplay
|
self.nameForDisplay = nameForDisplay
|
||||||
|
|
||||||
// NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Notifications
|
|
||||||
|
|
||||||
// @objc dynamic public func unreadCountDidChange(_ note: Notification) {
|
|
||||||
//
|
|
||||||
// guard let obj = note.object else {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// let potentialChild = obj as AnyObject
|
|
||||||
// if isChild(potentialChild) {
|
|
||||||
// updateUnreadCount()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public var unreadCount = 0 {
|
|
||||||
// didSet {
|
|
||||||
// if unreadCount != oldValue {
|
|
||||||
// postUnreadCountDidChangeNotification()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public func updateUnreadCount() {
|
|
||||||
//
|
|
||||||
// unreadCount = calculateUnreadCount(childObjects)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Folder: OPMLRepresentable {
|
extension Folder: OPMLRepresentable {
|
||||||
|
@ -10,6 +10,7 @@ import Foundation
|
|||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import RSParser
|
||||||
import RSWeb
|
import RSWeb
|
||||||
|
import Data
|
||||||
|
|
||||||
final class LocalAccountRefresher: DownloadSessionDelegate {
|
final class LocalAccountRefresher: DownloadSessionDelegate {
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
|
|||||||
|
|
||||||
public func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
|
public func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
|
||||||
|
|
||||||
guard let feed = representedObject as? LocalFeed else {
|
guard let feed = representedObject as? Feed else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
|
|||||||
|
|
||||||
public func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) {
|
public func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) {
|
||||||
|
|
||||||
guard let feed = representedObject as? LocalFeed, !data.isEmpty else {
|
guard let feed = representedObject as? Feed, !data.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,10 +69,10 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
|
|||||||
let parserData = ParserData(url: feed.url, data: data)
|
let parserData = ParserData(url: feed.url, data: data)
|
||||||
FeedParser.parse(parserData) { (parsedFeed, error) in
|
FeedParser.parse(parserData) { (parsedFeed, error) in
|
||||||
|
|
||||||
guard let account = self.account, let parsedFeed = parsedFeed, error == nil else {
|
guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account.update(feed, parsedFeed: parsedFeed) {
|
account.update(feed, with: parsedFeed) {
|
||||||
|
|
||||||
if let httpResponse = response as? HTTPURLResponse {
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
let conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
|
let conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse)
|
||||||
@ -87,7 +88,7 @@ final class LocalAccountRefresher: DownloadSessionDelegate {
|
|||||||
|
|
||||||
public func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
|
public func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
|
||||||
|
|
||||||
guard let feed = representedObject as? LocalFeed else {
|
guard let feed = representedObject as? Feed else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
|
import RSWeb
|
||||||
|
|
||||||
public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
||||||
|
|
||||||
@ -18,6 +19,8 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
|||||||
public var name: String?
|
public var name: String?
|
||||||
public var editedName: String?
|
public var editedName: String?
|
||||||
public var accountInfo: AccountInfo? //If account needs to store more data
|
public var accountInfo: AccountInfo? //If account needs to store more data
|
||||||
|
public var conditionalGetInfo: HTTPConditionalGetInfo?
|
||||||
|
public var contentHash: String?
|
||||||
public let hashValue: Int
|
public let hashValue: Int
|
||||||
|
|
||||||
// MARK: - DisplayNameProvider
|
// MARK: - DisplayNameProvider
|
||||||
|
Loading…
x
Reference in New Issue
Block a user