@@ -0,0 +1,2224 @@
1+//
2+// File.swift
3+// golos
4+//
5+// Created by Ilya Kos on 12/12/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+import RealmSwift
11+import Alamofire
12+import CoreLocation
13+import Nuke
14+import SwiftyVK
15+
16+
17+typealias Token = String
18+typealias UserID = Int
19+typealias MessageID = Int
20+typealias MultichatID = Int
21+
22+class RealmPhoto: Object {
23+
24+ convenience init(surl: String, id: String) {
25+ self.init()
26+ self.imageSURL = surl
27+ self.id = id
28+ }
29+
30+ @objc dynamic var id: String = ""
31+ var imageURL: URL? {
32+ get {
33+ return imageSURL =>> {URL(string: $0)}
34+ }
35+ set {
36+ imageSURL = newValue?.absoluteString
37+ }
38+ }
39+ @objc dynamic var imageSURL: String? {
40+ didSet {
41+ if oldValue != imageSURL {
42+ localImageSURL = nil
43+ }
44+ }
45+ }
46+ @objc private dynamic var localImageSURL: String? {
47+ didSet {
48+ if oldValue != localImageSURL {
49+ imageCache = nil
50+ }
51+ }
52+ }
53+ private var imageCache: UIImage?
54+
55+ convenience init(id: String, surl: String?) {
56+ self.init()
57+ self.id = id
58+ self.imageSURL = surl
59+ }
60+
61+ class func by(_ id: String, surl: String?) -> RealmPhoto {
62+ do {
63+ let realm = try Realm()
64+ if let ob = realm.object(ofType: RealmPhoto.self, forPrimaryKey: id) {
65+ if realm.isInWriteTransaction {
66+ surl =>> {ob.imageSURL = $0}
67+ } else {
68+ try? realm.write {
69+ surl =>> {ob.imageSURL = $0}
70+ }
71+ }
72+ return ob
73+ } else {
74+ let ob = RealmPhoto(id: id, surl: surl)
75+ if realm.isInWriteTransaction {
76+ realm.add(ob)
77+ } else {
78+ try? realm.write {
79+ realm.add(ob)
80+ }
81+ }
82+ return ob
83+ }
84+ } catch {
85+ let ob = RealmPhoto(id: id, surl: surl)
86+ return ob
87+ }
88+ }
89+
90+ override static func primaryKey() -> String? {
91+ return "id"
92+ }
93+
94+ override static func ignoredProperties() -> [String] {
95+ return ["imageURL", "imageCache", "completions", "request"]
96+ }
97+
98+ var completions: [((() -> ()), RealmPhotoToken)] = []
99+ var views: [(UIImageView?, RealmPhotoToken)] = []
100+ var request: DataRequest?
101+
102+ @discardableResult func load(into view: UIImageView, completion: (() -> ())? = nil, error errorClosure: (() -> ())? = nil) -> RealmPhotoToken {
103+
104+ let token = RealmPhotoToken()
105+
106+ func getFromServer() {
107+ if request == nil {
108+ let surl = "\(id).png"
109+ if let url = imageURL {
110+ print("getting from network")
111+ request = Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
112+ self.request = nil
113+ switch responce.result {
114+ case .success(let data):
115+ UIImage(data: data) >>> {image in
116+ write(cache: UIImagePNGRepresentation(image), withName: surl) {
117+ realmWrite({ (realm) in
118+ realm.object(ofType: RealmPhoto.self, forPrimaryKey: self.id)?.localImageSURL = surl
119+ })
120+ }
121+ self.imageCache = image
122+ DispatchQueue.main.async {
123+ if token.isValid {
124+ self.views.map { if $0.1.isValid {$0.0?.image = image} }
125+ self.completions.map { if $0.1.isValid { $0.0() } }
126+ self.completions = []
127+ }
128+ }
129+ }
130+ case .failure(let error):
131+ errorClosure?()
132+ // TODO: Error handling
133+ break
134+ }
135+ }
136+ }
137+ }
138+ }
139+
140+ if let surl = localImageSURL, // TODO: Should probrably get thing from server ???
141+ let url = url(forName: surl) {
142+ Nuke.loadImage(with: url, into: view) { [weak view] (response, fromMemory) in
143+ switch response {
144+ case .fulfilled(let image):
145+ if token.isValid {
146+ view?.image = image
147+ completion?()
148+ }
149+ case .rejected(let error): // TODO: Handle error?
150+ completion >>> {self.completions.append(($0, token))}
151+ self.views.append((view, token))
152+ getFromServer()
153+ }
154+ }
155+ } else {
156+ completion >>> {self.completions.append(($0, token))}
157+ self.views.append((view, token))
158+ getFromServer()
159+ }
160+
161+ return token
162+ }
163+
164+ // func image(_ completion: @escaping ((UIImage?) -> ())) {
165+ // print(id)
166+ // let surl = "\(id).png"
167+ // if imageCache != nil {
168+ // print("we have caching!")
169+ // }
170+ // imageCache = imageCache ?? (localImageSURL =>> getLocalImage) // TODO: imageCache always seems to be nil... ie always loading from disk for some reason
171+ // if let sImage = imageCache {
172+ // DispatchQueue.main.async { completion(sImage) }
173+ // } else if let url = imageURL {
174+ // print("getting from network")
175+ // Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
176+ // switch responce.result {
177+ // case .success(let data):
178+ // UIImage(data: data) >>> {image in
179+ // write(cache: UIImagePNGRepresentation(image), withName: surl) {
180+ // DispatchQueue.main.async {
181+ // do {
182+ // let realm = try Realm()
183+ // if !realm.isInWriteTransaction {
184+ // try realm.write {
185+ // self.localImageSURL = surl
186+ // }
187+ // } else {
188+ // self.localImageSURL = surl
189+ // }
190+ // } catch { }
191+ // }
192+ // }
193+ // self.imageCache = image
194+ // DispatchQueue.main.async {completion(image)}
195+ // }
196+ // case .failure(let error):
197+ // // TODO: Error handling
198+ // break
199+ // }
200+ // }.resume()
201+ // }
202+ // }
203+}
204+
205+public class RealmPhotoToken {
206+ public func cancel() {
207+ isValid = false
208+ }
209+ fileprivate var isValid = true
210+}
211+
212+
213+class User: Object {
214+ @objc dynamic var id: UserID = 0
215+ @objc dynamic var firstName: String?
216+ @objc dynamic var lastName: String?
217+ var deactivated: Deactivated? {
218+ get {
219+ return deactivatedStr =>> {Deactivated.init(rawValue: $0)}
220+ }
221+ set {
222+ deactivatedStr = newValue >>> {$0.rawValue}
223+ }
224+ }
225+ @objc private dynamic var deactivatedStr: String?
226+
227+ @objc dynamic var about: String?
228+ @objc dynamic var birthday: Date?
229+ let blacklisted = RealmOptional<Bool>()
230+ let canSendFriendRequest = RealmOptional<Bool>()
231+ let canSendPrivateMessage = RealmOptional<Bool>()
232+ let mutualFriendCount = RealmOptional<Int>()
233+ // TODO: var connections:
234+ @objc dynamic var mobilePhone: String?
235+ @objc dynamic var homePhone: String?
236+ // TODO: var exports
237+ var friendStatus: FriendStatus? {
238+ get {
239+ return friendStatusInt.value =>> {FriendStatus(rawValue: $0)}
240+ }
241+ set {
242+ friendStatusInt.value = newValue >>> {$0.rawValue}
243+ }
244+ }
245+ private let friendStatusInt = RealmOptional<Int>()
246+ let online = RealmOptional<Bool>()
247+ @objc dynamic var avatar: RealmPhoto?
248+ var avatarID: String {
249+ return "user\(id)avatar"
250+ }
251+ @objc dynamic var photo: RealmPhoto?
252+ var photoID: String {
253+ return "user\(id)photo"
254+ }
255+ var site: URL? {
256+ get {
257+ return siteSURL =>> {URL.init(string: $0)}
258+ }
259+ set {
260+ siteSURL = newValue >>> {$0.absoluteString}
261+ }
262+ }
263+ @objc dynamic var siteSURL: String?
264+ @objc dynamic var status: String?
265+ @objc dynamic var lastSeen: Date?
266+ // dynamic var lastUpdated: Date = Date()
267+ let friendCount = RealmOptional<Int>()
268+
269+ convenience init(id: UserID, firstName: String, lastName: String) {
270+ self.init()
271+ self.id = id
272+ self.firstName = firstName
273+ self.lastName = lastName
274+ // self.lastUpdated = Date()
275+ }
276+ class func by(id: UserID) -> User {
277+ let realm = try! Realm()
278+ if let us = realm.object(ofType: User.self, forPrimaryKey: id) {
279+ return us
280+ } else {
281+ let us = User()
282+ us.id = id
283+ if realm.isInWriteTransaction {
284+ realm.add(us)
285+ } else {
286+ try? realm.write {
287+ realm.add(us)
288+
289+ }
290+ }
291+ return us
292+ }
293+ }
294+
295+ func get(completion: ((User?) -> ())? = nil) {
296+ VK.API.Users.get([VK.Arg.userIDs: "\(id)"]).send(onSuccess: { (response) in
297+ if let json = response.array?.first {
298+ let us = VKData.processUser(json: json)
299+ completion?(us)
300+ } else {
301+ completion?(nil)
302+ }
303+ }, onError: { (error) in
304+ completion?(nil)
305+ })
306+ }
307+
308+
309+ override static func primaryKey() -> String? {
310+ return "id"
311+ }
312+
313+ override static func ignoredProperties() -> [String] {
314+ return ["site", "photoID", "avatarID"]
315+ }
316+ // func update() { lastUpdated = Date() }
317+
318+ // MARK: Hashable override
319+
320+ override var hashValue: Int {
321+ return id
322+ }
323+}
324+
325+class Geolocation: Object {
326+ var geoCoordinates: CLLocation? {
327+ get {
328+ if let long = long.value, let latt = latt.value {
329+ return CLLocation(latitude: latt, longitude: long)
330+ }
331+ return nil
332+ }
333+ set {
334+ if let latt = newValue?.coordinate.latitude,
335+ let long = newValue?.coordinate.longitude {
336+ self.latt.value = latt
337+ self.long.value = long
338+ } else {
339+ self.long.value = nil
340+ self.latt.value = nil
341+ }
342+ }
343+ }
344+ private let latt = RealmOptional<Double>()
345+ private let long = RealmOptional<Double>()
346+ let id = RealmOptional<Int>()
347+ @objc dynamic var title: String?
348+ @objc dynamic var image: RealmPhoto?
349+ var imageID: String {
350+ return "geo_lat\(latt.value ?? 0)_long\(long.value ?? 0)image"
351+ }
352+ var city: String?
353+ var country: String?
354+
355+ override static func ignoredProperties() -> [String] {
356+ return ["geoCoordinates", "imageID"]
357+ }
358+ convenience init(long: Double, latt: Double) {
359+ self.init()
360+ self.long.value = long
361+ self.latt.value = latt
362+ }
363+
364+}
365+
366+class GenericMessage: Object {
367+ @objc dynamic var date = Date() //
368+ @objc dynamic var body = "" //
369+ @objc dynamic var user: User?
370+
371+ var outPhotos: [UIImage] = [] // TODO: ~
372+
373+ @objc dynamic var geolocation: Geolocation?
374+
375+ let attachments = List<RealmMedia>()
376+ let forwardedMessages = List<RealmForwardedMessage>()
377+ //let sentMessageId: MessageID? I DONT KNOW WHY I PUT THIS HERE
378+
379+ // convenience init(body: String) {
380+ // self.init()
381+ // self.body = body
382+ // self.out.value = true
383+ // }
384+ // convenience init(image: UIImage) {
385+ // self.init()
386+ // self.outPhotos.append(image)
387+ // }
388+ override static func ignoredProperties() -> [String] {
389+ return ["outPhotos"]
390+ }
391+}
392+
393+class Message: GenericMessage, Comparable, SortedArrayItem {
394+ @objc dynamic var id = 0
395+ let randomID = RealmOptional<MessageID>()
396+ @objc dynamic var read = false
397+ @objc dynamic var replied = false
398+ @objc dynamic var important = false
399+ @objc dynamic var deleted = false
400+
401+ func equal(to message: Message) -> Bool {
402+ // print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
403+ if let a = self.randomID.value,
404+ let b = message.randomID.value {
405+ if a == b {
406+ return true
407+ }
408+ }
409+ return self.id == message.id
410+ }
411+ internal func greater(than message: Message) -> Bool {
412+ return self.id < message.id
413+ }
414+
415+ static func ==(lhs: Message, rhs: Message) -> Bool {
416+ print("\(lhs.id) \(rhs.id) \(lhs.id == rhs.id)")
417+ return lhs.id == rhs.id
418+ }
419+
420+ @objc dynamic var out = false
421+
422+ // Multichat stuff
423+
424+ @objc dynamic var photoSURL: String?
425+ let chatID = RealmOptional<MultichatID>()
426+ @objc private dynamic var serviceMessageString: String?
427+ private var serviceMessageUser: User?
428+ private let serviceMessage = RealmOptional<Int>()
429+
430+ var action: ServiceMessageAction? {
431+ get {
432+ func getUser() -> ServiceMessageUser? {
433+ if let user = self.serviceMessageUser {
434+ return .user(user)
435+ } else if let email = self.serviceMessageString {
436+ return .email(email)
437+ } else {
438+ return nil
439+ }
440+ }
441+
442+ return serviceMessage.value =>> { service in
443+ switch service {
444+ case 0:
445+ return .photoUpdate
446+ case 1:
447+ return .photoRemove
448+ case 2:
449+ return self.serviceMessageString >>> { .create($0) }
450+ case 3:
451+ return self.serviceMessageString >>> { .titleUpdate($0) }
452+ case 4:
453+ return getUser() >>> { .inviteUser($0) }
454+ case 5:
455+ return getUser() >>> { .kickUser($0) }
456+ default:
457+ return nil
458+ }
459+ }
460+ }
461+ set {
462+ func set(user: ServiceMessageUser) {
463+ switch user {
464+ case .user(let use):
465+ self.serviceMessageUser = use
466+ case .email(let str):
467+ self.serviceMessageString = str
468+ }
469+ }
470+
471+ serviceMessageString = nil
472+ serviceMessageUser = nil
473+ serviceMessage.value = newValue >>> { new in
474+ switch new {
475+ case .photoUpdate:
476+ return 0
477+ case .photoRemove:
478+ return 1
479+ case .create(let str):
480+ self.serviceMessageString = str
481+ return 2
482+ case .titleUpdate(let str):
483+ self.serviceMessageString = str
484+ return 3
485+ case .inviteUser(let user):
486+ set(user: user)
487+ return 4
488+ case .kickUser(let user):
489+ set(user: user)
490+ return 5
491+ }
492+ }
493+ }
494+ }
495+
496+ func get(completion: ((Message?) -> ())? = nil) {
497+ VK.API.Messages.getById([VK.Arg.messageIds: String(id)]).send(onSuccess: { (response) in
498+ if let json = response.array?.first {
499+ let mes = VKData.processMessage(response)
500+ completion?(mes)
501+ } else {
502+ completion?(nil)
503+ }
504+ }, onError: { (error) in
505+ completion?(nil)
506+ })
507+ // TODO: Implement propperly?
508+ }
509+
510+ class func by(id: MessageID) -> Message {
511+ let realm = try! Realm()
512+ if let us = realm.object(ofType: Message.self, forPrimaryKey: id) {
513+ return us
514+ } else {
515+ let us = Message()
516+ us.id = id
517+ realmWrite({ (_) in
518+ realm.add(us)
519+ })
520+ return us
521+ }
522+ }
523+
524+ override static func primaryKey() -> String? {
525+ return "id"
526+ }
527+}
528+//func ==(lhs: Message, rhs: Message) -> Bool {
529+// print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
530+// return lhs.id.value == rhs.id.value
531+//}
532+func <(lhs: Message, rhs: Message) -> Bool {
533+ return lhs.id < rhs.id
534+}
535+
536+enum forwardedMessage {
537+ case genericMessage(GenericMessage)
538+ case message(Message)
539+}
540+
541+class RealmForwardedMessage: Object {
542+ var value: forwardedMessage? {
543+ get {
544+ if let gm = genericMessage {
545+ return .genericMessage(gm)
546+ } else if let m = message {
547+ return .message(m)
548+ } else {
549+ return nil
550+ }
551+ }
552+ set {
553+ realmWrite { _ in
554+ if let fm = newValue {
555+ switch fm {
556+ case .genericMessage(let gm):
557+ genericMessage = gm
558+ message = nil
559+ case .message(let m):
560+ genericMessage = nil
561+ message = m
562+ }
563+ } else {
564+ message = nil
565+ genericMessage = nil
566+ }
567+ }
568+ }
569+ }
570+ @objc private dynamic var genericMessage: GenericMessage?
571+ @objc private dynamic var message: Message?
572+
573+ var id: MessageID? {
574+ if let m = message {
575+ return m.id
576+ } else {
577+ return nil
578+ }
579+ }
580+
581+ convenience init(value: forwardedMessage) {
582+ self.init()
583+ self.value = value
584+ }
585+
586+ override static func ignoredProperties() -> [String] {
587+ return ["value"]
588+ }
589+}
590+
591+//struct Geolocation {
592+// var coordinates: CLLocation
593+// var title: String?
594+// var imageURL: URL? {
595+// get {
596+// return imageSURL =>> {URL.init(string: $0)}
597+// }
598+// set {
599+// imageSURL = newValue?.absoluteString
600+// }
601+// }
602+// var imageSURL: String?
603+// // func image(completion: ((UIImage) -> ())) {
604+// // imageFunc =>> {$0(completion)}
605+// // }
606+// var image: (@escaping ((UIImage) -> ()) -> ())?
607+// var city: String?
608+// var country: String?
609+// init(coordinates: CLLocation, title: String?, imageSURL: String?, image: (@escaping ((UIImage) -> ()) -> ()), city: String?, country: String?) {
610+// self.coordinates = coordinates
611+// self.title = title
612+// self.imageSURL = imageSURL
613+// self.image = image
614+// self.city = city
615+// self.country = country
616+// }
617+//}
618+typealias GeolocationID = Int
619+
620+enum Deactivated: String {
621+ case banned = "banned"
622+ case deleted = "deleted"
623+ case normal = "normal"
624+}
625+
626+enum FriendStatus: Int {
627+ case notFriends = 0
628+ case requestSent
629+ case requestRecieved
630+ case fiend
631+}
632+
633+enum MessageReadState: Int {
634+ case read = 0
635+ case notRead
636+}
637+enum MessageSendState: Int {
638+ case received = 0
639+ case sent
640+}
641+
642+enum MultichatNotificationSetting {
643+ case sound
644+ case disabled_until
645+}
646+
647+enum ServiceMessageAction {
648+ case photoUpdate // 0
649+ case photoRemove // 1
650+ case create(String) // 2
651+ case titleUpdate(String) // 3
652+ case inviteUser(ServiceMessageUser) // 4
653+ case kickUser(ServiceMessageUser) // 5
654+}
655+
656+enum ServiceMessageUser {
657+ case user(User)
658+ case email(String)
659+}
660+
661+typealias MessageChunkID = String
662+class MessageChunk: Object { // message ids go down
663+ subscript(index: Int) -> Message {
664+ return messages[index]
665+ }
666+
667+ @objc dynamic var id: MessageChunkID = ""
668+
669+ fileprivate let messages = List<Message>()
670+
671+ var firstMessage: Message? {
672+ return messages.first
673+ }
674+
675+ var lastMessage: Message? {
676+ return messages.last
677+ }
678+
679+ var firstID: MessageID? {
680+ return firstMessage?.id
681+ }
682+
683+ var lastID: MessageID? {
684+ return lastMessage?.id
685+ }
686+
687+ var count: Int {
688+ return messages.count
689+ }
690+
691+ fileprivate func append(message: Message?) {
692+ message >>> {messages.append($0)}
693+ }
694+
695+ override static func primaryKey() -> String? {
696+ return "id"
697+ }
698+
699+ override static func ignoredProperties() -> [String] {
700+ return ["firstMessage", "lastMessage", "firstID", "lastID", "count"]
701+ }
702+}
703+
704+func +=(lhs: MessageChunk, rhs: MessageChunk) {
705+ lhs.messages.append(objectsIn: rhs.messages)
706+}
707+
708+/// Returns: true if added. false if was already in list
709+func <~(lhs: MessageChunk?, rhs: Message?) -> Bool {
710+ var out: Bool = false
711+ realmWrite { (_) in
712+ out = lhs?.messages <~ rhs
713+ }
714+ return out
715+}
716+/// Returns: Indexes of added items
717+func <-(lhs: MessageChunk?, rhs: [Message?]?) -> [Int] {
718+ var out: [Int] = []
719+ realmWrite { (_) in
720+ out = lhs?.messages <- rhs
721+ }
722+ return out
723+}
724+/// Returns: Indexes of added items
725+func <-(lhs: MessageChunk?, rhs: MessageChunk?) -> [Int] {
726+ var out: [Int] = []
727+ realmWrite { (_) in
728+ out = lhs?.messages <- rhs?.messages
729+ }
730+ return out
731+}
732+
733+enum MessageEntry {
734+ case message(Message)
735+ case spacer
736+}
737+
738+typealias MessagesID = Int
739+class Messages: Object {
740+ subscript(ind: Int) -> MessageEntry? {
741+ assert(ind >= 0, "index out of bounds")
742+ if ind >= count || ind < 0 {
743+ return nil
744+ }
745+ var index = ind
746+ var i = 0
747+ while messageChunks[i].count < index {
748+ index -= messageChunks[i].count + 1
749+ i += 1
750+ }
751+ if index == messageChunks[i].count {
752+ return .spacer
753+ } else {
754+ return .message(messageChunks[i][index])
755+ }
756+ }
757+
758+ @objc dynamic var id: MessagesID = 0
759+
760+ @objc fileprivate dynamic var lastChunkID = 0
761+
762+ var endIndex: Int {
763+ return messageChunks.endIndex
764+ }
765+
766+ var first: Message? {
767+ return messageChunks.first?.firstMessage
768+ }
769+
770+ func count(upToChunk index: Int) -> Int {
771+ var n = 0
772+ for i in 0..<index {
773+ n += messageChunks[i].count + 1
774+ }
775+ return n
776+ }
777+
778+ public var count: Int {
779+ var n = messageChunks.count - 1
780+ for chunk in messageChunks {
781+ n += chunk.count
782+ }
783+ return n
784+ }
785+
786+ fileprivate let messageChunks = List<MessageChunk>()
787+
788+ public var chunks: List<MessageChunk> {
789+ return messageChunks
790+ }
791+
792+ fileprivate enum ChunkIndex {
793+ case inside(Int)
794+ case after(Int)
795+ case beforeFirst
796+ }
797+
798+ fileprivate func createChunk(at index: Int = 0) -> MessageChunk {
799+ let chunk = MessageChunk()
800+ realmWrite { (realm) in
801+ chunk.id = "\(id)_\(lastChunkID)"
802+ lastChunkID += 1
803+ realm.add(chunk)
804+ messageChunks.insert(chunk, at: index)
805+ }
806+ return chunk
807+ }
808+
809+ /// Return nil if there are no chunks
810+ fileprivate func chunkIndex(of message: Message) -> ChunkIndex? {
811+ if messageChunks.count == 0 {
812+ return nil
813+ }
814+ if let first = messageChunks.first?.firstMessage {
815+ if first.greater(than: message) {
816+ return .beforeFirst
817+ }
818+ }
819+ for (i, chunk) in messageChunks.enumerated() {
820+ if let lastMessage = chunk.lastMessage { // It should never be nil since a chunk is never empty
821+ if !message.greater(than: lastMessage) { // [ ... | ()] [...] ...
822+ if i == 0 {
823+ return .inside(i)
824+ } else {
825+ if let firstMessage = chunk.firstMessage { // It should never be nil since a chunk is never empty
826+ if message.greater(than: firstMessage) || message.equal(to: firstMessage) {
827+ return .inside(i)
828+ } else {
829+ return .after(i-1)
830+ }
831+ }
832+ }
833+ }
834+ }
835+ }
836+ return .after(endIndex-1)
837+ }
838+
839+
840+ override static func primaryKey() -> String? {
841+ return "id"
842+ }
843+
844+ override static func ignoredProperties() -> [String] {
845+ return ["endIndex"]
846+ }
847+
848+ class func by(id: MessagesID) -> Messages {
849+ let realm = try! Realm()
850+ if let us = realm.object(ofType: Messages.self, forPrimaryKey: id) {
851+ return us
852+ } else {
853+ let us = Messages()
854+ us.id = id
855+ realm.add(us)
856+ return us
857+ }
858+ }
859+
860+ // MARK: Messages obesrver
861+
862+ static let notificationName: Notification.Name = Notification.Name(rawValue: "MessagesWereModifiedNotificationName")
863+
864+ enum Changes {
865+ case added
866+ case modified
867+ case deleted
868+ }
869+}
870+
871+
872+
873+/// Returns: true if added. false if was already in list
874+func <~(lhs: Messages?, rhs: Message?) -> Bool {
875+ var out: Bool = false
876+ if let lhs = lhs,
877+ let rhs = rhs {
878+ realmWrite { (_) in
879+ if let index = lhs.chunkIndex(of: rhs) {
880+ switch index {
881+ case .after(let i):
882+ lhs.messageChunks[i].append(message: rhs)
883+ out = true
884+ case .inside(let i):
885+ out = lhs.messageChunks[i] <~ rhs
886+ case .beforeFirst:
887+ out = lhs.createChunk(at: 0) <~ rhs
888+ }
889+ } else {
890+ out = lhs.createChunk() <~ rhs
891+ }
892+ }
893+ }
894+ return out
895+}
896+/// Only accepts a list of SEQUENTIAL messages. Will silently break if not passed sequentially.
897+/// Returns: Indexes of added items
898+func <-(lhs: Messages?, rhs: [Message?]?) /*-> [Int]*/ {
899+ print(rhs)
900+ print(lhs)
901+ if let lhs = lhs,
902+ let rhs = rhs?.flatMap({$0}),
903+ let first = rhs.first,
904+ let last = rhs.last {
905+
906+ var deleted: [Int] = []
907+ var modified: [Int] = []
908+ var added: [Int] = []
909+ if let startI = lhs.chunkIndex(of: first),
910+ let endI = lhs.chunkIndex(of: last) {
911+ var start: Int!
912+ var end: Int!
913+ print("\t", startI, endI)
914+ switch startI {
915+ case .inside(let i):
916+ start = i
917+ case .after(let i):
918+ start = i + 1
919+ case .beforeFirst:
920+ start = -1
921+ }
922+ switch endI {
923+ case .after(let i):
924+ end = i
925+ case .inside(let i):
926+ end = i
927+ case .beforeFirst:
928+ end = -1
929+ }
930+
931+ if start < end {
932+ print(start, end)
933+ start = max(start, 0)
934+ var spacerIndexes = (start+1...end).map({lhs.count(upToChunk: $0) - 1})
935+ // deleted += spacerIndexes
936+ let count = lhs.count(upToChunk: start)
937+ var dIndex = 0
938+ for i in 0..<spacerIndexes.count {
939+ spacerIndexes[i] -= dIndex
940+ deleted.append(spacerIndexes[i])
941+ spacerIndexes[i] -= count
942+ dIndex += 1
943+ }
944+ var addedMs: [Int]!
945+ realmWriteWithCompletion { realm, completion in
946+ for x in start+1...end {
947+ print(lhs.chunks.count, x)
948+ lhs.chunks[start] += lhs.chunks[start+1]
949+ realm.delete(lhs.chunks[start+1])
950+ // lhs.chunks.remove(objectAtIndex: start+1)
951+ }
952+ let chunk = lhs.chunks[start]
953+ addedMs = chunk <- rhs
954+
955+ addedMs.map {$0 + count}
956+ added += addedMs
957+
958+ completion {
959+ print("after notification", lhs.chunks)
960+ NotificationCenter.default.post(name: Messages.notificationName,
961+ object: lhs.id,
962+ userInfo: [Messages.Changes.added: added,
963+ Messages.Changes.modified: modified,
964+ Messages.Changes.deleted: deleted])
965+ }
966+ }
967+ } else { // within one
968+ assert(start == end, "start index is larger than end index!")
969+ realmWriteWithCompletion { _, completion in
970+ if start == -1 {
971+ added += lhs.createChunk(at: 0) <- rhs
972+ added.append(0) // IDK o[]o[]o []o[]o
973+ } else {
974+ added += (lhs.messageChunks[start] <- rhs).map {$0 + lhs.count(upToChunk: start)}
975+ }
976+ completion {
977+ NotificationCenter.default.post(name: Messages.notificationName,
978+ object: lhs.id,
979+ userInfo: [Messages.Changes.added: added,
980+ Messages.Changes.modified: modified,
981+ Messages.Changes.deleted: deleted])
982+ }
983+ }
984+ }
985+ } else { // empty
986+ realmWriteWithCompletion { _, completion in
987+ added += lhs.createChunk() <- rhs
988+ completion {
989+ NotificationCenter.default.post(name: Messages.notificationName,
990+ object: lhs.id,
991+ userInfo: [Messages.Changes.added: added,
992+ Messages.Changes.modified: modified,
993+ Messages.Changes.deleted: deleted])
994+ }
995+ }
996+ }
997+ print(lhs)
998+ print(added)
999+ print(deleted)
1000+ }
1001+}
1002+
1003+enum Media {
1004+ case photo(Photo)
1005+ case video(Video)
1006+ case audio(Audio)
1007+ case doc(Document)
1008+ case wall(WallPost)
1009+ case wallReply(WallPostReply)
1010+ case sticker(Sticker)
1011+ case link(Link)
1012+ case gift(Gift)
1013+ case market(Market)
1014+ case marketAlbum(MarketAlbum)
1015+ case graffiti(Document)
1016+ case note(Note)
1017+ case poll(Poll)
1018+ case album(PhotoAlbum)
1019+ var realm: RealmMedia {
1020+ return RealmMedia(value: self)
1021+ }
1022+}
1023+
1024+class RealmMedia: Object {
1025+ @objc private dynamic var photo: Photo?
1026+ @objc private dynamic var video: Video?
1027+ @objc private dynamic var audio: Audio?
1028+ @objc private dynamic var doc: Document?
1029+ @objc private dynamic var wall: WallPost?
1030+ @objc private dynamic var wallReply: WallPostReply?
1031+ @objc private dynamic var sticker: Sticker?
1032+ @objc private dynamic var link: Link?
1033+ @objc private dynamic var gift: Gift?
1034+ @objc private dynamic var market: Market?
1035+ @objc private dynamic var marketAlbum: MarketAlbum?
1036+ @objc private dynamic var graffiti: Document?
1037+ @objc private dynamic var note: Note?
1038+ @objc private dynamic var poll: Poll?
1039+ @objc private dynamic var album: PhotoAlbum?
1040+
1041+ var value: Media? {
1042+ get {
1043+ if let sureThing = self.photo { return .photo(sureThing) }
1044+ if let sureThing = self.video { return .video(sureThing) }
1045+ if let sureThing = self.audio { return .audio(sureThing) }
1046+ if let sureThing = self.doc { return .doc(sureThing) }
1047+ if let sureThing = self.wall { return .wall(sureThing) }
1048+ if let sureThing = self.wallReply { return .wallReply(sureThing) }
1049+ if let sureThing = self.sticker { return .sticker(sureThing) }
1050+ if let sureThing = self.link { return .link(sureThing) }
1051+ if let sureThing = self.gift { return .gift(sureThing) }
1052+ if let sureThing = self.market { return .market(sureThing) }
1053+ if let sureThing = self.marketAlbum { return .marketAlbum(sureThing) }
1054+ if let sureThing = self.graffiti { return .graffiti(sureThing) }
1055+ if let sureThing = self.note { return .note(sureThing) }
1056+ if let sureThing = self.poll { return .poll(sureThing) }
1057+ if let sureThing = self.album { return .album(sureThing) }
1058+ return nil
1059+ }
1060+ set {
1061+
1062+ photo = nil
1063+ video = nil
1064+ audio = nil
1065+ doc = nil
1066+ wall = nil
1067+ wallReply = nil
1068+ sticker = nil
1069+ link = nil
1070+ gift = nil
1071+ market = nil
1072+ marketAlbum = nil
1073+ graffiti = nil
1074+ note = nil
1075+ poll = nil
1076+ album = nil
1077+
1078+ if let val = newValue {
1079+ switch val {
1080+ case .photo(let foo): photo = foo
1081+ case .video(let foo): video = foo
1082+ case .audio(let foo): audio = foo
1083+ case .doc(let foo): doc = foo
1084+ case .wall(let foo): wall = foo
1085+ case .wallReply(let foo): wallReply = foo
1086+ case .sticker(let foo): sticker = foo
1087+ case .link(let foo): link = foo
1088+ case .gift(let foo): gift = foo
1089+ case .market(let foo): market = foo
1090+ case .marketAlbum(let foo): marketAlbum = foo
1091+ case .graffiti(let foo): graffiti = foo
1092+ case .note(let foo): note = foo
1093+ case .poll(let foo): poll = foo
1094+ case .album(let foo): album = foo
1095+ }
1096+ }
1097+ }
1098+ }
1099+ convenience init(value: Media) {
1100+ self.init()
1101+ self.value = value
1102+ }
1103+}
1104+
1105+class Poll: Object {
1106+ @objc dynamic var id: PollID = 0
1107+ @objc dynamic var owner: User?
1108+ @objc dynamic var date: Date?
1109+ @objc dynamic var question: String?
1110+ let votes = RealmOptional<Int>()
1111+ let answerID = RealmOptional<PollAnswerID>()
1112+ let answers = List<PollAnswer>()
1113+ convenience init(id: PollID, owner: User, date: Date, question: String, votes: Int) {
1114+ self.init()
1115+ self.id = id
1116+ self.owner = owner
1117+ self.date = date
1118+ self.question = question
1119+ self.votes.value = votes
1120+ // self.answerID.value = answerID
1121+ }
1122+ class func by(id: PollID) -> Poll {
1123+ let realm = try! Realm()
1124+ if let us = realm.object(ofType: Poll.self, forPrimaryKey: id) {
1125+ return us
1126+ } else {
1127+ let us = Poll()
1128+ us.id = id
1129+ realm.add(us)
1130+ return us
1131+ }
1132+ }
1133+
1134+ override class func primaryKey() -> String? {
1135+ return "id"
1136+ }
1137+}
1138+typealias PollID = Int
1139+
1140+class PollAnswer: Object {
1141+ @objc dynamic var id: PollAnswerID = 0
1142+ @objc dynamic var body: String = ""
1143+ @objc dynamic var votes: Int = 0
1144+ @objc dynamic var rating: Int = 0
1145+
1146+ convenience init(id: PollAnswerID, body: String, votes: Int, rating: Int) {
1147+ self.init()
1148+ self.id = id
1149+ self.body = body
1150+ self.votes = votes
1151+ self.rating = rating
1152+ }
1153+}
1154+typealias PollAnswerID = Int
1155+
1156+class Note: Object {
1157+ @objc dynamic var id: NoteID = 0
1158+ @objc dynamic var owner: User?
1159+ @objc dynamic var title: String?
1160+ @objc dynamic var body: String?
1161+ @objc dynamic var date: Date?
1162+ var url: URL? {
1163+ get {
1164+ return surl =>> {URL(string: $0)}
1165+ }
1166+ set {
1167+ surl = newValue >>> {$0.absoluteString}
1168+ }
1169+ }
1170+ @objc dynamic var surl: String?
1171+ let commentCount = RealmOptional<Int>()
1172+ convenience init(id: NoteID, owner: User, title: String, body: String, date: Date, url: URL, commentCount: Int) {
1173+ self.init()
1174+ self.id = id
1175+ self.owner = owner
1176+ self.title = title
1177+ self.body = body
1178+ self.date = date
1179+ self.url = url
1180+ self.commentCount.value = commentCount
1181+ }
1182+ class func by(id: NoteID) -> Note {
1183+ let realm = try! Realm()
1184+ if let us = realm.object(ofType: Note.self, forPrimaryKey: id) {
1185+ return us
1186+ } else {
1187+ let us = Note()
1188+ us.id = id
1189+ realm.add(us)
1190+ return us
1191+ }
1192+ }
1193+ override class func ignoredProperties() -> [String] {
1194+ return ["url"]
1195+ }
1196+
1197+ override class func primaryKey() -> String? {
1198+ return "id"
1199+ }
1200+}
1201+typealias NoteID = Int
1202+
1203+typealias GraffitiID = Int
1204+class Graffiti: Document {
1205+} // Use document.image ?
1206+
1207+class Photo: Object {
1208+ @objc dynamic var id: PhotoID = 0
1209+ @objc dynamic var owner: User?
1210+ @objc dynamic var date: Date?
1211+ @objc dynamic var thumbnail: RealmPhoto?
1212+ var thumbnailID: String {
1213+ return "photo\(id)thumbnail"
1214+ }
1215+ @objc dynamic var image: RealmPhoto?
1216+ var imageID: String {
1217+ return "photo\(id)image"
1218+ }
1219+ let width = RealmOptional<Int>()
1220+ let height = RealmOptional<Int>()
1221+ @objc dynamic var accessKey: Key?
1222+
1223+ convenience init(id: PhotoID, owner: User, date: Date){
1224+ self.init()
1225+ self.id = id
1226+ self.owner = owner
1227+ self.date = date
1228+ }
1229+
1230+ class func by(id: PhotoID) -> Photo {
1231+ let realm = try! Realm()
1232+ if let us = realm.object(ofType: Photo.self, forPrimaryKey: id) {
1233+ return us
1234+ } else {
1235+ let us = Photo()
1236+ us.id = id
1237+ realm.add(us)
1238+ return us
1239+ }
1240+ }
1241+
1242+ func get(completion: ((Photo?) -> ())? = nil) {
1243+ if let ownerID = owner?.id {
1244+ var ph = "\(ownerID)_\(id)"
1245+ if let key = accessKey {
1246+ ph.append("_\(key)")
1247+ }
1248+ VK.API.Photos.getById([VK.Arg.photos: ph]).send(onSuccess: { (response) in
1249+ if let json = response.array?.first {
1250+ let photo = VKData.processPhoto(json)
1251+ completion?(photo)
1252+ } else {
1253+ completion?(nil)
1254+ }
1255+ }, onError: { (error) in
1256+ completion?(nil)
1257+ })
1258+ } else {
1259+ completion?(nil)
1260+ }
1261+ }
1262+
1263+ override class func primaryKey() -> String? {
1264+ return "id"
1265+ }
1266+
1267+ override static func ignoredProperties() -> [String] {
1268+ return ["thumbnailID", "imageID"]
1269+ }
1270+}
1271+
1272+class PhotoAlbum: Object {
1273+ @objc dynamic var id: PhotoAlbumID = 0
1274+ @objc dynamic var thumb: Photo?
1275+ @objc dynamic var owner: User?
1276+ @objc dynamic var title: String?
1277+ @objc dynamic var albumDescription: String?
1278+ @objc dynamic var dateCreated: Date?
1279+ @objc dynamic var dateUpdated: Date?
1280+ let count = RealmOptional<Int>()
1281+ let photos = List<Photo>()
1282+ convenience init(id: PhotoAlbumID) {
1283+ self.init()
1284+ self.id = id
1285+ }
1286+
1287+ override class func primaryKey() -> String? {
1288+ return "id"
1289+ }
1290+ class func by(id: PhotoAlbumID) -> PhotoAlbum {
1291+ let realm = try! Realm()
1292+ if let us = realm.object(ofType: PhotoAlbum.self, forPrimaryKey: id) {
1293+ return us
1294+ } else {
1295+ let us = PhotoAlbum()
1296+ us.id = id
1297+ realm.add(us)
1298+ return us
1299+ }
1300+ }
1301+
1302+
1303+ // init(id: PhotoAlbumID, thumbID: PhotoID, ownerID: UserID, title: String, dateCreated: Date, dateUpdated: Date, count: Int) {
1304+ // self.id = id
1305+ // self.thumbID = thumbID
1306+ // self.ownerID = ownerID
1307+ // self.title = title
1308+ // self.dateCreated = dateCreated
1309+ // self.dateUpdated = dateUpdated
1310+ // self.count = count
1311+ // }
1312+
1313+}
1314+typealias PhotoID = Int
1315+typealias PhotoAlbumID = Int
1316+typealias Key = String
1317+
1318+class WallPost: Object {
1319+ @objc dynamic var id: Int = 0
1320+ @objc dynamic var owner: User?
1321+ @objc dynamic var creator: User?
1322+ @objc dynamic var date: Date?
1323+ @objc dynamic var body: String?
1324+ let commentCount = RealmOptional<Int>()
1325+ let canPostComment = RealmOptional<Bool>()
1326+ let likeCount = RealmOptional<Int>()
1327+ @objc dynamic var liked: Bool = false
1328+ let canLike = RealmOptional<Bool>()
1329+ let repostCount = RealmOptional<Int>()
1330+ let canDelete = RealmOptional<Bool>()
1331+ let didRepost = RealmOptional<Bool>()
1332+ // Optional ⬇
1333+ let attachments = List<RealmMedia>()
1334+ @objc dynamic var geo: Geolocation?
1335+ // TODO: let postSource
1336+ @objc dynamic var signed: User?
1337+ @objc dynamic var ownerOfCopy: User?
1338+ @objc dynamic var original: WallPost?
1339+ let historyOfCopy = List<WallPost>()
1340+ let canPin = RealmOptional<Bool>()
1341+ let canEdit = RealmOptional<Bool>()
1342+ let isPinned = RealmOptional<Bool>()
1343+
1344+ // convenience init(id: WallPostID, ownerID: UserID, creatorID: UserID, date: Date, body: String, commentCount: Int, canPostComment: Bool, likeCount: Int, liked: Bool, canLike: Bool, repostCount: Int, didRepost: Bool, attachments: [Media], geo: Geolocation?, signed: UserID?, copyID: WallPostID?, canDelete: Bool, canPin: Bool?, canEdit: Bool?, isPinned: Bool?, copyHistory: [WallPostID], copyOwnerID: UserID?) {
1345+ // self.id = id
1346+ // self.ownerID = ownerID
1347+ // self.creatorID = creatorID
1348+ // self.date = date
1349+ // self.body = body
1350+ // self.commentCount = commentCount
1351+ // self.canPostComment = canPostComment
1352+ // self.likeCount = likeCount
1353+ // self.liked = liked
1354+ // self.canLike = canLike
1355+ // self.repostCount = repostCount
1356+ // self.didRepost = didRepost
1357+ // self.attachments = attachments
1358+ // self.geo = geo
1359+ // self.signed = signed
1360+ // self.copyID = copyID
1361+ // self.canDelete = canDelete
1362+ // self.canPin = canPin
1363+ // self.canEdit = canEdit
1364+ // self.isPinned = isPinned
1365+ // self.copyHistory = copyHistory
1366+ // self.copyOwnerID = copyOwnerID
1367+ // }
1368+
1369+ func get(completion: ((WallPost?) -> ())? = nil) {
1370+ if let ownerID = owner?.id {
1371+ var ph = "\(ownerID)_\(id)"
1372+ // if let key = accessKey {
1373+ // ph.append("_\(key)")
1374+ // }
1375+ VK.API.Wall.getById([VK.Arg.posts: ph]).send(onSuccess: { (response) in
1376+ if let json = response.array?.first {
1377+ let photo = VKData.processWallPost(json)
1378+ completion?(photo)
1379+ } else {
1380+ completion?(nil)
1381+ }
1382+ }, onError: { (error) in
1383+ completion?(nil)
1384+ })
1385+ } else {
1386+ completion?(nil)
1387+ }
1388+ }
1389+
1390+
1391+ override class func primaryKey() -> String? {
1392+ return "id"
1393+ }
1394+
1395+ class func by(id: UserID) -> WallPost {
1396+ let realm = try! Realm()
1397+ if let us = realm.object(ofType: WallPost.self, forPrimaryKey: id) {
1398+ return us
1399+ } else {
1400+ let us = WallPost()
1401+ us.id = id
1402+ realm.add(us)
1403+ return us
1404+ }
1405+ }
1406+
1407+}
1408+typealias WallPostID = Int
1409+
1410+class WallPostReply: Object {
1411+ @objc dynamic var id: WallPostReplyID = 0
1412+ @objc dynamic var author: User?
1413+ @objc dynamic var wallPost: WallPost?
1414+ @objc dynamic var wallOwner: User?
1415+ @objc dynamic var date: Date?
1416+ @objc dynamic var body: String?
1417+ let likeCount = RealmOptional<Int>()
1418+ let didLike = RealmOptional<Bool>()
1419+ let canLike = RealmOptional<Bool>()
1420+ @objc dynamic var inReplyToUser: User?
1421+ let attachments = List<RealmMedia>()
1422+ @objc dynamic var inReplyToComment: WallPostReply?
1423+ class func by(id: WallPostReplyID) -> WallPostReply {
1424+ let realm = try! Realm()
1425+ if let us = realm.object(ofType: WallPostReply.self, forPrimaryKey: id) {
1426+ return us
1427+ } else {
1428+ let us = WallPostReply()
1429+ us.id = id
1430+ realm.add(us)
1431+ return us
1432+ }
1433+ }
1434+
1435+ override class func primaryKey() -> String? {
1436+ return "id"
1437+ }
1438+}
1439+typealias WallPostReplyID = Int
1440+
1441+class Sticker: Object {
1442+ @objc dynamic var id: StickerID = 0
1443+ @objc dynamic var packID: StickerPackID = 0
1444+ @objc dynamic var image: RealmPhoto?
1445+ var imageID: String {
1446+ return "sticker\(id)image"
1447+ }
1448+ let width = RealmOptional<Int>()
1449+ let height = RealmOptional<Int>()
1450+
1451+ convenience init(id: StickerID, packID: StickerPackID, width: Int, height: Int) {
1452+ self.init()
1453+ self.id = id
1454+ self.packID = packID
1455+ self.width.value = width
1456+ self.height.value = height
1457+ }
1458+ class func by(id: StickerID) -> Sticker {
1459+ let realm = try! Realm()
1460+ if let us = realm.object(ofType: Sticker.self, forPrimaryKey: id) {
1461+ return us
1462+ } else {
1463+ let us = Sticker()
1464+ us.id = id
1465+ realm.add(us)
1466+ return us
1467+ }
1468+ }
1469+
1470+ override class func primaryKey() -> String? {
1471+ return "id"
1472+ }
1473+ override static func ignoredProperties() -> [String] {
1474+ return ["imageID"]
1475+ }
1476+}
1477+typealias StickerID = Int
1478+typealias StickerPackID = Int
1479+
1480+class Gift: Object {
1481+ @objc dynamic var id: GiftID = 0
1482+ @objc dynamic var image: RealmPhoto?
1483+ var imageID: String {
1484+ return "gift\(id)image"
1485+ }
1486+ convenience init(id: GiftID, surl: String) {
1487+ self.init()
1488+ self.id = id
1489+ self.image = RealmPhoto.by(imageID, surl: surl)
1490+ }
1491+ class func by(id: GiftID) -> Gift {
1492+ let realm = try! Realm()
1493+ if let us = realm.object(ofType: Gift.self, forPrimaryKey: id) {
1494+ return us
1495+ } else {
1496+ let us = Gift()
1497+ us.id = id
1498+ realm.add(us)
1499+ return us
1500+ }
1501+ }
1502+
1503+ override class func primaryKey() -> String? {
1504+ return "id"
1505+ }
1506+ override static func ignoredProperties() -> [String] {
1507+ return ["imageID"]
1508+ }
1509+}
1510+typealias GiftID = Int
1511+
1512+class Link: Object {
1513+ var url: URL? {
1514+ get {
1515+ return surl =>> {URL(string: $0)}
1516+ }
1517+ }
1518+ @objc dynamic var surl: String = ""
1519+ @objc dynamic var title: String?
1520+ @objc dynamic var caption: String?
1521+ @objc dynamic var linkDescription: String?
1522+ @objc dynamic var photo: Photo?
1523+ let isExternal = RealmOptional<Bool>()
1524+ //var product: Market?
1525+ var rating: Rating? {
1526+ get {
1527+ if let stars = ratingStars.value,
1528+ let count = ratingReviewCount.value {
1529+ return Rating(stars: stars, reviewCount: count)
1530+ }
1531+ return nil
1532+ }
1533+ set {
1534+ ratingStars.value = newValue?.stars
1535+ ratingReviewCount.value = newValue?.reviewCount
1536+ }
1537+ }
1538+ private let ratingStars = RealmOptional<Int>()
1539+ private let ratingReviewCount = RealmOptional<Int>()
1540+ //let application: Application?
1541+ var button: LinkButton? {
1542+ get {
1543+ if let title = buttonTitle,
1544+ let url = buttonURL {
1545+ return LinkButton(title: title, url: url)
1546+ }
1547+ return nil
1548+ }
1549+ set {
1550+ buttonURL = newValue?.url
1551+ buttonTitle = newValue?.title
1552+ }
1553+ }
1554+ @objc private dynamic var buttonTitle: String?
1555+ private var buttonURL: URL? {
1556+ get {
1557+ return buttonSURL =>> {URL(string: $0)}
1558+ }
1559+ set {
1560+ buttonSURL = newValue?.absoluteString
1561+ }
1562+ }
1563+ private var buttonSURL: String?
1564+ //let previewURL: URL
1565+
1566+ class func by(surl: String) -> Link {
1567+ let realm = try! Realm()
1568+ if let us = realm.object(ofType: Link.self, forPrimaryKey: surl) {
1569+ return us
1570+ } else {
1571+ let us = Link()
1572+ us.surl = surl
1573+ realm.add(us)
1574+ return us
1575+ }
1576+ }
1577+ override class func primaryKey() -> String? {
1578+ return "surl"
1579+ }
1580+ override static func ignoredProperties() -> [String] {
1581+ return ["url", "rating", "button", "buttonURL"]
1582+ }
1583+}
1584+
1585+
1586+struct Rating {
1587+ let stars: Int
1588+ let reviewCount: Int
1589+}
1590+
1591+struct LinkButton {
1592+ let title: String
1593+ let url: URL
1594+}
1595+
1596+class Market: Object {
1597+ @objc dynamic var id: MarketID = 0
1598+ @objc dynamic var owner: User?
1599+ @objc dynamic var title: String?
1600+ @objc dynamic var marketDescription: String?
1601+ let amount = RealmOptional<Int>()
1602+ let price = RealmOptional<Int>()
1603+ let currencyID = RealmOptional<CurrencyID>()
1604+ @objc dynamic var currencyName: String?
1605+ @objc dynamic var priceString: String?
1606+ let categoryID = RealmOptional<MarketCategoryID>()
1607+ @objc dynamic var categoryName: String?
1608+ let sectionID = RealmOptional<MarketSectionID>()
1609+ @objc dynamic var sectionName: String?
1610+ @objc dynamic var image: RealmPhoto?
1611+ var imageID: String {
1612+ return "market\(id)image"
1613+ }
1614+ @objc dynamic var creationDate: Date?
1615+ var availability: MarketAvailability? {
1616+ set {
1617+ availabilityInt.value = newValue?.rawValue
1618+ }
1619+ get {
1620+ return availabilityInt.value =>> {MarketAvailability(rawValue: $0)}
1621+ }
1622+ }
1623+ private let availabilityInt = RealmOptional<Int>()
1624+ let photos = List<Photo>()
1625+ let canComment = RealmOptional<Bool>()
1626+ let canRepost = RealmOptional<Bool>()
1627+ let likesCount = RealmOptional<Int>()
1628+ let liked = RealmOptional<Bool>()
1629+
1630+ class func by(id: MarketID) -> Market {
1631+ let realm = try! Realm()
1632+ if let us = realm.object(ofType: Market.self, forPrimaryKey: id) {
1633+ return us
1634+ } else {
1635+ let us = Market()
1636+ us.id = id
1637+ realm.add(us)
1638+ return us
1639+ }
1640+ }
1641+
1642+ override class func primaryKey() -> String? {
1643+ return "id"
1644+ }
1645+ override static func ignoredProperties() -> [String] {
1646+ return ["imageID"]
1647+ }
1648+}
1649+typealias CurrencyID = Int
1650+typealias MarketCategoryID = Int
1651+typealias MarketSectionID = Int
1652+enum MarketAvailability: Int {
1653+ case availability = 0
1654+ case deleted
1655+ case notAvailability
1656+}
1657+typealias MarketID = Int
1658+
1659+class MarketAlbum: Object {
1660+ @objc dynamic var id: MarketAlbumID = 0
1661+ @objc dynamic var owner: User?
1662+ @objc dynamic var title: String?
1663+ @objc dynamic var photo: Photo?
1664+ let count = RealmOptional<Int>()
1665+ @objc dynamic var updateTime: Date?
1666+ override class func primaryKey() -> String? {
1667+ return "id"
1668+ }
1669+}
1670+typealias MarketAlbumID = Int
1671+
1672+class Video: Object {
1673+ @objc dynamic var id: VideoID = 0
1674+ @objc dynamic var owner: User?
1675+ @objc dynamic var title: String?
1676+ @objc dynamic var videoDescription: String?
1677+ let duration = RealmOptional<TimeInterval>()
1678+ @objc dynamic var thumbnail: RealmPhoto?
1679+ var thumbnailID: String {
1680+ return "video\(id)thumbnail"
1681+ }
1682+ var player: URL? {
1683+ get {
1684+ return playerSURL =>> {URL(string: $0)}
1685+ }
1686+ set {
1687+ playerSURL = newValue?.absoluteString
1688+ }
1689+ }
1690+ @objc dynamic var playerSURL: String?
1691+ @objc dynamic var accessKey: String?
1692+ @objc dynamic var isProcessing: Bool = false
1693+ @objc dynamic var isLive: Bool = false
1694+ convenience init(id: VideoID, owner: User, title: String, description: String, duration: TimeInterval) {
1695+ self.init()
1696+ self.id = id
1697+ self.owner = owner
1698+ self.title = title
1699+ self.videoDescription = description
1700+ self.duration.value = duration
1701+ }
1702+
1703+ class func by(id: VideoID) -> Video {
1704+ let realm = try! Realm()
1705+ if let us = realm.object(ofType: Video.self, forPrimaryKey: id) {
1706+ return us
1707+ } else {
1708+ let us = Video()
1709+ us.id = id
1710+ realm.add(us)
1711+ return us
1712+ }
1713+ }
1714+
1715+ func get(completion: ((Video?) -> ())? = nil) {
1716+ if let peerID = owner?.id {
1717+ var vi = "\(peerID)_\(id)"
1718+ if let key = accessKey {
1719+ vi.append("_\(key)")
1720+ }
1721+ VK.API.Video.get([VK.Arg.videos: vi]).send(onSuccess: { (response) in
1722+ if let json = response["items"].array?.first {
1723+ let video = VKData.processVideo(json)
1724+ completion?(video)
1725+ } else {
1726+ completion?(nil)
1727+ }
1728+ }, onError: { (error) in
1729+ completion?(nil)
1730+ })
1731+ } else {
1732+ completion?(nil)
1733+ }
1734+ }
1735+
1736+ override class func primaryKey() -> String? {
1737+ return "id"
1738+ }
1739+ override static func ignoredProperties() -> [String] {
1740+ return ["player", "thumbnailID"]
1741+ }
1742+}
1743+typealias VideoID = Int
1744+
1745+class Audio: Object {
1746+ @objc dynamic var id: AudioID = 0
1747+ @objc dynamic var owner: User?
1748+ @objc dynamic var artist: String?
1749+ @objc dynamic var title: String?
1750+ let duration = RealmOptional<TimeInterval>()
1751+ var url: URL? {
1752+ get {
1753+ return surl =>> {URL(string: $0)}
1754+ }
1755+ set {
1756+ surl = newValue?.absoluteString
1757+ }
1758+ }
1759+ @objc dynamic var surl: String?
1760+
1761+ convenience init(id: AudioID) {
1762+ self.init()
1763+ self.id = id
1764+ }
1765+ class func by(id: AudioID) -> Audio {
1766+ let realm = try! Realm()
1767+ if let us = realm.object(ofType: Audio.self, forPrimaryKey: id) {
1768+ return us
1769+ } else {
1770+ let us = Audio()
1771+ us.id = id
1772+ realm.add(us)
1773+ return us
1774+ }
1775+ }
1776+
1777+ override class func primaryKey() -> String? {
1778+ return "id"
1779+ }
1780+ override static func ignoredProperties() -> [String] {
1781+ return ["url"]
1782+ }
1783+}
1784+typealias AudioID = Int
1785+typealias LyricID = Int
1786+typealias AudioAlbumID = Int
1787+typealias GenreID = Int
1788+
1789+class Document: Object {
1790+ @objc dynamic var id: DocumentID = 0
1791+ @objc dynamic var owner: User?
1792+ @objc dynamic var title: String?
1793+ let size = RealmOptional<Int>()
1794+ @objc dynamic var fileExtension: String?
1795+ var url: URL? {
1796+ get {
1797+ return surl =>> {URL(string: $0)}
1798+ }
1799+ set {
1800+ surl = newValue?.absoluteString
1801+ }
1802+ }
1803+ @objc dynamic var surl: String?
1804+ @objc dynamic var image: RealmPhoto?
1805+ var imageID: String {
1806+ return "doc\(id)image"
1807+ }
1808+ var type: DocumentType? {
1809+ get {
1810+ return typeInt.value =>> {DocumentType(rawValue: $0)}
1811+ }
1812+ set {
1813+ typeInt.value = newValue?.rawValue
1814+ }
1815+ }
1816+ private let typeInt = RealmOptional<Int>()
1817+ convenience init(id: DocumentID, owner: User, title: String, size: Int, ext: String, url: URL, type: DocumentType) {
1818+ self.init()
1819+ self.id = id
1820+ self.owner = owner
1821+ self.title = title
1822+ self.size.value = size
1823+ self.fileExtension = ext
1824+ self.url = url
1825+ self.type = type
1826+ }
1827+ class func by(id: DocumentID) -> Document {
1828+ let realm = try! Realm()
1829+ if let us = realm.object(ofType: Document.self, forPrimaryKey: id) {
1830+ return us
1831+ } else {
1832+ let us = Document()
1833+ us.id = id
1834+ realm.add(us)
1835+ return us
1836+ }
1837+ }
1838+
1839+ func get(completion: ((Document?) -> ())? = nil) {
1840+ if let ownerID = owner?.id {
1841+ var doc = "\(ownerID)_\(id)"
1842+ // if let key = accessKey {
1843+ // doc.append("_\(key)")
1844+ // }
1845+ VK.API.Docs.getById([VK.Arg.docs: doc]).send(onSuccess: { response in
1846+ if let json = response.array?.first {
1847+ let document = VKData.processDocument(json)
1848+ completion?(document)
1849+ } else {
1850+ completion?(nil)
1851+ }
1852+ }, onError: { (error) in
1853+ completion?(nil)
1854+ })
1855+ } else {
1856+ completion?(nil)
1857+ }
1858+ }
1859+
1860+ override class func primaryKey() -> String? {
1861+ return "id"
1862+ }
1863+ override static func ignoredProperties() -> [String] {
1864+ return ["url", "imageID"]
1865+ }
1866+}
1867+typealias DocumentID = Int
1868+enum DocumentType: Int {
1869+ case text = 1
1870+ case archive
1871+ case gif
1872+ case image
1873+ case audio
1874+ case video
1875+ case eBook
1876+ case unknown
1877+}
1878+
1879+protocol Dialog {
1880+ var firstMessage: Message? { get }
1881+ var important: Bool { get set }
1882+ var name: String? { get }
1883+ func loadImage(into view: UIImageView, completion: (() -> ())?) -> RealmPhotoToken?
1884+ var messages: Messages? { get }
1885+ var id: Int { get }
1886+ var lastReadMessageID: Int { get set }
1887+ var count: RealmOptional<Int> { get }
1888+ var notificationSettings: RealmNotificatonSettings? { get set }
1889+ var peerID: Int { get }
1890+}
1891+
1892+func dialog(for id: DialogID) -> Dialog {
1893+ if id > multichatchatId { // multichat
1894+ let chat = Multichat.by(id: id - multichatchatId)
1895+ return chat
1896+
1897+ //} else if id < 0 { // Pablic
1898+
1899+ } else { // Normal user
1900+ let chat = Chat.by(user: User.by(id: id))
1901+ return chat
1902+ }
1903+}
1904+
1905+// For name conflicts
1906+func dialogFor(_ id: DialogID) -> Dialog {
1907+ return dialog(for: id)
1908+}
1909+
1910+class RealmDialog: Object, Comparable {
1911+
1912+ // TODO: Remove these two
1913+ @objc dynamic var dateLastSent: Date?
1914+ /// Number of times a message has been sent in the dateLastSent second
1915+ @objc dynamic var dateSentNumber = 0
1916+
1917+ var value: Dialog? {
1918+ get {
1919+ return dialog(for: id)
1920+ }
1921+ }
1922+
1923+ var peerID: Int? {
1924+ return value?.peerID
1925+ }
1926+
1927+ @objc dynamic var id: DialogID = 0
1928+
1929+ @objc dynamic var date: Date?
1930+
1931+ class func by(dialog: Dialog) -> RealmDialog {
1932+ return RealmDialog.by(peerID: dialog.peerID)
1933+ }
1934+
1935+ class func by(peerID: Int) -> RealmDialog {
1936+ if let dialog = (try? Realm())?.object(ofType: RealmDialog.self, forPrimaryKey: peerID) {
1937+ return dialog
1938+ } else {
1939+ let dialog = RealmDialog()
1940+ dialog.id = peerID
1941+ realmWrite({ (realm) in
1942+ realm.add(dialog)
1943+ })
1944+ return dialog
1945+ }
1946+ }
1947+
1948+ override class func primaryKey() -> String? {
1949+ return "id"
1950+ }
1951+ override static func ignoredProperties() -> [String] {
1952+ return ["value", "peerID"]
1953+ }
1954+}
1955+func ==(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
1956+ return lhs.date == rhs.date
1957+}
1958+func <(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
1959+ if let l = lhs.date,
1960+ let r = rhs.date {
1961+ return l < r
1962+ } else {
1963+ return false
1964+ }
1965+}
1966+typealias DialogID = Int
1967+
1968+//func ==(lhs: Dialog, rhs: Dialog) -> Bool {
1969+// return lhs.date == rhs.date
1970+//}
1971+//func <(lhs: Dialog, rhs: Dialog) -> Bool {
1972+// return lhs.date < rhs.date
1973+//}
1974+
1975+class RealmNotificatonSettings: Object {
1976+ var value: ChatNotificationSettings? {
1977+ get {
1978+ if let sound = notiSound.value,
1979+ let never = notiDateNever.value {
1980+ if never {
1981+ return ChatNotificationSettings(sound: sound, disabledUntil: .never)
1982+ } else {
1983+ return notiDate >>> {ChatNotificationSettings(sound: sound, disabledUntil: .date($0))}
1984+ }
1985+ }
1986+ return nil
1987+ }
1988+ set {
1989+ notiSound.value = newValue?.sound
1990+ if let newV = newValue {
1991+ switch newV.disabledUntil {
1992+ case .date(let date):
1993+ notiDate = date
1994+ notiDateNever.value = false
1995+ case .never:
1996+ notiDateNever.value = true
1997+ notiDate = nil
1998+ }
1999+ }
2000+ }
2001+ }
2002+ private let notiSound = RealmOptional<Bool>()
2003+ private let notiDateNever = RealmOptional<Bool>()
2004+ @objc private dynamic var notiDate: Date?
2005+ convenience init(value: ChatNotificationSettings?) {
2006+ self.init()
2007+ self.value = value
2008+ }
2009+}
2010+
2011+class Multichat: Object {
2012+ @objc dynamic var id: MultichatID = 0
2013+ @objc dynamic var admin: User?
2014+ var timers: [UserID: Timer] = [:]
2015+ var typingUsers: Set<User> = []
2016+ @objc dynamic var title: String?
2017+ let users = List<MultiChatUser>()
2018+ @objc dynamic var messages: Messages?
2019+ // let messages = List<Message>()
2020+ @objc dynamic var lastReadMessageID = 0
2021+ @objc dynamic var important = false
2022+ @objc dynamic var answered = false
2023+ @objc dynamic var date = Date()
2024+ let count = RealmOptional<Int>()
2025+ @objc dynamic var image: RealmPhoto?
2026+ var imageID: String {
2027+ return "mchat\(id)image"
2028+ }
2029+ let attachments = List<RealmMedia>()
2030+
2031+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2032+
2033+ var participationStatus: MultichatStatus? {
2034+ get {
2035+ return participationStatusInt.value =>> {MultichatStatus(rawValue: $0)}
2036+ }
2037+ set {
2038+ participationStatusInt.value = newValue?.rawValue
2039+ }
2040+ }
2041+ private let participationStatusInt = RealmOptional<Int>()
2042+
2043+ // convenience init(id: MultichatID, adminID: UserID, title: String, userIDs: [(user: UserID, invitedBy: UserID)]?, notificationSettings: MultichatNotificationSettings?, participationStatus: MultichatStatus?) {
2044+ // self.id = id
2045+ // self.adminID = adminID
2046+ // self.title = title
2047+ // self.userIDs = userIDs
2048+ // self.notificationSetting = notificationSettings
2049+ // self.participationStatus = participationStatus
2050+ // }
2051+ class func by(id: MultichatID) -> Multichat {
2052+ let realm = try! Realm()
2053+ if let us = realm.object(ofType: Multichat.self, forPrimaryKey: id) {
2054+ return us
2055+ } else {
2056+ VkServices.getMultichat(for: id)
2057+ let us = Multichat()
2058+ realmWrite { realm in
2059+ us.id = id
2060+ us.messages = Messages.by(id: us.peerID)
2061+ realm.add(us)
2062+ }
2063+ return us
2064+ }
2065+ }
2066+
2067+ override class func primaryKey() -> String? {
2068+ return "id"
2069+ }
2070+ override static func ignoredProperties() -> [String] {
2071+ return ["imageID", "typingUsers", "timers"]
2072+ }
2073+}
2074+
2075+extension Multichat: Dialog {
2076+ var firstMessage: Message? {
2077+ return messages?.first
2078+ }
2079+
2080+ var name: String? {
2081+ return title
2082+ }
2083+
2084+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2085+ return image?.load(into: view, completion: completion)
2086+ }
2087+ // func image(completion: @escaping ((UIImage?) -> ())) {
2088+ // if let im = image?.image {
2089+ // im(completion)
2090+ // } else {
2091+ // completion(nil)
2092+ // }
2093+ // }
2094+
2095+ var peerID: Int {
2096+ return id + multichatchatId
2097+ }
2098+}
2099+
2100+class MultiChatUser: Object {
2101+ @objc dynamic var user: User?
2102+ @objc dynamic var invitedBy: User?
2103+ convenience init(_ user: User, invitedBy: User) {
2104+ self.init()
2105+ self.user = user
2106+ self.invitedBy = invitedBy
2107+ }
2108+}
2109+
2110+enum MultichatStatus: Int {
2111+ case active
2112+ case left
2113+ case kicked
2114+}
2115+
2116+class Chat: Object {
2117+ @objc dynamic var user: User? {
2118+ didSet {
2119+ user >>> {userID = $0.id}
2120+ }
2121+ }
2122+ var timer: Timer?
2123+ @objc dynamic var date = Date()
2124+ @objc dynamic var userID = 0
2125+ @objc dynamic var isTyping = false
2126+ @objc dynamic var lastReadMessageID = 0
2127+ let count = RealmOptional<Int>()
2128+ @objc dynamic var messages: Messages?
2129+ // let messages = List<Message>()
2130+ @objc dynamic var important = false
2131+ @objc dynamic var answered = false
2132+ let attachments = List<RealmMedia>()
2133+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2134+ class func by(user: User) -> Chat {
2135+ let realm = try! Realm()
2136+ if let us = realm.object(ofType: Chat.self, forPrimaryKey: user.id) {
2137+ return us
2138+ } else {
2139+ let us = Chat()
2140+ realmWrite { realm in
2141+ us.user = user
2142+ us.messages = Messages.by(id: us.peerID)
2143+ realm.add(us)
2144+ }
2145+ return us
2146+ }
2147+ }
2148+
2149+ convenience init(user: User) {
2150+ self.init()
2151+ self.user = user
2152+ }
2153+ override static func ignoredProperties() -> [String] {
2154+ return ["timer"]
2155+ }
2156+ override class func primaryKey() -> String? {
2157+ return "userID"
2158+ }
2159+}
2160+
2161+extension Chat: Dialog {
2162+ var firstMessage: Message? {
2163+ return messages?.first
2164+ }
2165+
2166+ var name: String? {
2167+ if let firstName = user?.firstName,
2168+ let lastName = user?.lastName {
2169+ return "\(firstName) \(lastName)"
2170+ } else {
2171+ return nil
2172+ }
2173+ }
2174+
2175+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2176+ return user?.avatar?.load(into: view, completion: completion)
2177+ }
2178+
2179+ // func image(completion: @escaping ((UIImage?) -> ())) {
2180+ // if let im = user?.avatar?.image {
2181+ // im(completion)
2182+ // } else {
2183+ // completion(nil)
2184+ // }
2185+ // }
2186+
2187+ var id: Int {
2188+ return userID
2189+ }
2190+
2191+ var peerID: Int {
2192+ return userID
2193+ }
2194+}
2195+
2196+struct ChatNotificationSettings {
2197+ var sound: Bool
2198+ var disabledUntil: disableDate
2199+}
2200+
2201+enum disableDate {
2202+ case never
2203+ case date(Date)
2204+ init?(value: Int) {
2205+ switch value {
2206+ case -1:
2207+ self = .never
2208+ case 0:
2209+ return nil
2210+ default:
2211+ self = .date(Date(timeIntervalSince1970: TimeInterval(value)))
2212+ }
2213+ }
2214+}
2215+
2216+func isPhoneNumber(_ phone: String) -> Bool {
2217+ let nums = "1234567890".characters
2218+ let f002 = phone.characters.map{nums.contains($0) ? 1 : 0}
2219+ let foo1 = Double(sum(acc: 0, arr: f002))
2220+ let foo = foo1/Double(phone.characters.count)
2221+ return foo >= 0.4 ? true : false
2222+}
2223+
2224+