Revision control

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
import Foundation
6
import Shared
7
import Storage
8
import XCGLogger
9
import SwiftyJSON
10
11
// Int64.max / 1000.
12
private let MaxSecondsToConvertInt64: Int64 = 9223372036854775
13
private let MaxSecondsToConvertDouble = Double(9223372036854775 as Int64)
14
15
private let log = Logger.browserLogger
16
17
open class TabsPayload: CleartextPayloadJSON {
18
open class Tab {
19
let title: String
20
let urlHistory: [String]
21
let lastUsed: Timestamp
22
let icon: String?
23
24
fileprivate init(title: String, urlHistory: [String], lastUsed: Timestamp, icon: String?) {
25
self.title = title
26
self.urlHistory = urlHistory
27
self.lastUsed = lastUsed
28
self.icon = icon
29
}
30
31
func toRemoteTabForClient(_ guid: GUID) -> RemoteTab? {
32
let urls = urlHistory.compactMap({ $0.asURL })
33
if urls.isEmpty {
34
log.debug("Bug 1201875 - Discarding tab as history has no conforming URLs.")
35
return nil
36
}
37
38
return RemoteTab(clientGUID: guid, URL: urls[0], title: self.title, history: urls, lastUsed: self.lastUsed, icon: self.icon?.asURL)
39
}
40
41
class func remoteTabFromJSON(_ json: JSON, clientGUID: GUID) -> RemoteTab? {
42
return fromJSON(json)?.toRemoteTabForClient(clientGUID)
43
}
44
45
class func fromJSON(_ json: JSON) -> Tab? {
46
func getLastUsed(_ json: JSON) -> Timestamp? {
47
let lastUsed = json["lastUsed"]
48
if lastUsed.isBool() {
49
return nil
50
}
51
// This might be a string or a number.
52
if let num = lastUsed.int64 {
53
if num < 0 {
54
// Timestamps are unsigned.
55
return nil
56
}
57
if num > MaxSecondsToConvertInt64 {
58
// This will overflow when multiplied.
59
return nil
60
}
61
return Timestamp(num * 1000)
62
}
63
64
if let num = lastUsed.double {
65
if num < 0 {
66
// Timestamps are unsigned.
67
return nil
68
}
69
if num > MaxSecondsToConvertDouble {
70
// This will overflow when multiplied.
71
return nil
72
}
73
return Timestamp(num * 1000)
74
}
75
76
if let num = lastUsed.string {
77
// Try parsing.
78
return someKindOfTimestampStringToTimestamp(num)
79
}
80
81
return nil
82
}
83
84
if let title = json["title"].string,
85
let urlHistory = jsonsToStrings(json["urlHistory"].array),
86
let lastUsed = getLastUsed(json) {
87
return Tab(title: title, urlHistory: urlHistory, lastUsed: lastUsed, icon: json["icon"].string)
88
}
89
90
return nil
91
}
92
}
93
94
override open func isValid() -> Bool {
95
if !super.isValid() {
96
return false
97
}
98
99
if self["deleted"].bool ?? false {
100
return true
101
}
102
103
return self["clientName"].isString() &&
104
self["tabs"].isArray()
105
}
106
107
// Eventually it'd be nice to unify RemoteTab and Tab. We want to kill the GUID in RemoteTab,
108
// at which point the only distinction between the two is that RemoteTab is "simple" and
109
// lives in Storage, and Tab is more closely tied to TabsPayload.
110
111
var remoteTabs: [RemoteTab] {
112
if let clientGUID = self["id"].string {
113
let payloadTabs = self["tabs"].arrayValue
114
let remoteTabs = payloadTabs.compactMap({ Tab.remoteTabFromJSON($0, clientGUID: clientGUID) })
115
if payloadTabs.count != remoteTabs.count {
116
log.debug("Bug 1201875 - Missing remote tabs from sync")
117
}
118
return remoteTabs
119
}
120
log.debug("no client ID for remote tabs")
121
return []
122
}
123
124
var tabs: [Tab] {
125
return self["tabs"].arrayValue.compactMap(Tab.fromJSON)
126
}
127
128
var clientName: String {
129
return self["clientName"].string!
130
}
131
132
override open func equalPayloads(_ obj: CleartextPayloadJSON) -> Bool {
133
if !(obj is TabsPayload) {
134
return false
135
}
136
137
if !super.equalPayloads(obj) {
138
return false
139
}
140
141
let p = obj as! TabsPayload
142
if p.clientName != self.clientName {
143
return false
144
}
145
146
// TODO: compare tabs.
147
/*
148
if p.tabs != self.tabs {
149
return false;
150
}
151
*/
152
153
return true
154
}
155
}