Sync.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. // Copyright (C) 2016 Inigo Valentin
  2. //
  3. // This file is part of the Gasteizko Margolariak IOS app.
  4. //
  5. // The Gasteizko Margolariak IOS app is free software: you can
  6. // redistribute it and/or modify it under the terms of the
  7. // GNU General Public License as published by the Free Software
  8. // Foundation, either version 3 of the License, or (at your
  9. // option) any later version.
  10. //
  11. // The Gasteizko Margolariak IOS app is distributed in the
  12. // hope that it will be useful, but WITHOUT ANY WARRANTY;
  13. // without even the implied warranty of MERCHANTABILITY or
  14. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  15. // Public License for more details.
  16. //
  17. // You should have received a copy of the GNU General Public
  18. // License along with the Gasteizko Margolariak IOS app.
  19. // If not, see <http://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CoreData
  22. import UIKit
  23. /**
  24. Class to handle server sync.
  25. */
  26. class Sync{
  27. var initial: Bool = false
  28. /**
  29. Starts the sync process.
  30. Always asynchronously.
  31. */
  32. init(){
  33. let url = buildUrl();
  34. sync(url: url)
  35. }
  36. /**
  37. Starts the sync process, synchronously or asynchronously.
  38. :param: synchronous True for synchronous sync, false for asynchronously
  39. */
  40. init(synchronous: Bool){
  41. if synchronous == true{
  42. NSLog(":SYNC:LOG: Synchronous sync started.")
  43. self.initial = true
  44. }
  45. let url = buildUrl();
  46. sync(url: url)
  47. }
  48. /**
  49. Builds the URL to perform the sync against.
  50. :returns: The URL.
  51. */
  52. func buildUrl() -> URL{
  53. // Get user ID
  54. let defaults = UserDefaults.standard
  55. var uId: String = "unknown"
  56. if defaults.value(forKey: "userId") != nil{
  57. uId = defaults.value(forKey: "userId") as! String
  58. }
  59. // Get versions
  60. var strVersions = ""
  61. do{
  62. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  63. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  64. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  65. let fetchRequest: NSFetchRequest<Version> = Version.fetchRequest()
  66. let searchResults = try context.fetch(fetchRequest)
  67. var s: String = ""
  68. var v: Int = 0
  69. for r in searchResults {
  70. s = r.value(forKey: "section")! as! String
  71. v = r.value(forKey: "version")! as! Int
  72. strVersions = strVersions + "&\(s)=\(v)"
  73. }
  74. }
  75. catch let error as NSError{
  76. NSLog(":SYNC:ERROR: Error getting stored versions: \(error)")
  77. }
  78. // Build URL
  79. // TODO: Change host for production
  80. var urlStr: String = ""
  81. //if initial{
  82. // urlStr = "https://margolariak.com/API/v3/fastsync.php?client=com.margolariak.app&user=\(uId)\(strVersions)"
  83. //}
  84. //else{
  85. urlStr = "https://margolariak.com/API/v3/sync.php?client=com.margolariak.app&user=\(uId)\(strVersions)"
  86. //}
  87. let url = URL(string: urlStr)
  88. NSLog(":SYNC:LOG: URL built: \(String(describing: url))")
  89. return url!
  90. }
  91. /**
  92. Performs an asynchronous sync.
  93. It fetches the info from the server and stores as Core Data
  94. :param: url The url to sync.
  95. */
  96. func sync(url: URL){
  97. NSLog(":SYNC:LOG: Sync started.")
  98. let delegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
  99. delegate.syncController?.nowSyncing = true
  100. //Synchronously get data
  101. let task = URLSession.shared.dataTask(with: url) { dat, response, error in
  102. guard error == nil else {
  103. NSLog(":SYNC:ERROR: Unknown error.")
  104. return
  105. }
  106. guard let rawData = dat else {
  107. NSLog(":SYNC:ERROR: Data is empty.")
  108. return
  109. }
  110. var data = String(data:rawData, encoding: String.Encoding.utf8)
  111. NSLog(":SYNC:LOG: Data received.")
  112. // Loop tables and save them to core data
  113. var table: String;
  114. var content: String;
  115. while (data?.indexOf(target: "}]") != nil){
  116. table = data!.subStr(start: 3, end: data!.indexOf(target: "\":")! - 1)
  117. content = data!.subStr(start: table.length + 5, end: (data?.indexOf(target: "}]"))! + 1)
  118. if table == "settings"{ // Special case
  119. self.saveSettings(content: content)
  120. }
  121. else if table == "version"{ // Special case
  122. self.saveVersion(content: content)
  123. }
  124. else{
  125. self.saveTable(table: table, content: content)
  126. }
  127. data = data?.subStr(start: (data?.indexOf(target: "}]"))! + 1, end: (data?.length)! - 1)
  128. }
  129. // If it's the initial sync, hide the segue
  130. if self.initial == true{
  131. NSLog(":SYNC:LOG: Finishing synchronous sync.")
  132. let delegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
  133. delegate.syncController?.nowSyncing = false
  134. }
  135. }
  136. task.resume()
  137. }
  138. /**
  139. Saves the data in the table.
  140. :param: table Name of the table
  141. :param: content JSON string containing the rows of the table.
  142. */
  143. func saveTable(table: String, content: String){
  144. NSLog(":SYNC:LOG: Saving table \(table)")
  145. let dateFormatter = DateFormatter()
  146. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  147. dateFormatter.timeZone = TimeZone.ReferenceType.local
  148. //Set up context
  149. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  150. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  151. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  152. let entity = NSEntityDescription.entity(forEntityName: table.capitalize(), in: context)
  153. //Delete all previous entries
  154. let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: table.capitalize())
  155. let request = NSBatchDeleteRequest(fetchRequest: fetch)
  156. do {
  157. try context.execute(request)
  158. } catch let error as NSError {
  159. NSLog(":SYNC:ERROR: Could not clean up \(table.capitalize()) entity: \(error), \(error.userInfo).")
  160. } catch {
  161. NSLog(":SYNC:ERROR: Could not clean up \(table.capitalize()) entity.")
  162. }
  163. //Loop rows
  164. var data: String = content
  165. var row: String
  166. var column: String
  167. var value: String
  168. var tuple: String
  169. var query: NSManagedObject
  170. while data.indexOf(target: "}") != nil{
  171. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  172. row = "\(row),\""
  173. query = NSManagedObject(entity: entity!, insertInto: context)
  174. while row.indexOf(target: ",\"") != nil{
  175. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  176. column = tuple.subStr(start: tuple.indexOf(target: "\"")! + 1, end: tuple.indexOf(target: "\":")! - 1)
  177. if column.length + 4 >= tuple.length - 2{
  178. value = "ul"
  179. }
  180. else{
  181. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  182. }
  183. if value != "ul"{ // 'ul' rom 'null' or empty. If it is, just do nothing.
  184. if entity?.attributesByName[column]?.attributeType == .stringAttributeType{
  185. query.setValue(value, forKey: column)
  186. }
  187. else if entity?.attributesByName[column]?.attributeType == .integer16AttributeType || entity?.attributesByName[column]?.attributeType == .integer32AttributeType || entity?.attributesByName[column]?.attributeType == .integer64AttributeType{
  188. query.setValue(Int(value), forKey: column)
  189. }
  190. else if entity?.attributesByName[column]?.attributeType == .doubleAttributeType{
  191. query.setValue(Double(value), forKey: column)
  192. }
  193. else if entity?.attributesByName[column]?.attributeType == .booleanAttributeType{
  194. if Int(value) == 1{
  195. query.setValue(true, forKey: column)
  196. }
  197. else if Int(value) == 0{
  198. query.setValue(false, forKey: column)
  199. }
  200. }
  201. else if entity?.attributesByName[column]?.attributeType == .dateAttributeType{
  202. if value.length < 11{
  203. value = "\(value) 00:00:00"
  204. }
  205. query.setValue(dateFormatter.date(from: value)!, forKey: column)
  206. }
  207. else{ //Regular string
  208. }
  209. // Special case: "festivl_event_citiy" and "festival_event_gm" have a special column: "day"
  210. if (table == "festival_event_city" || table == "festival_event_gm") && column == "start" {
  211. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  212. dateFormatter.calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.ISO8601)! as Calendar
  213. dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale?
  214. dateFormatter.timeZone = NSTimeZone.local
  215. let timeString = value//"\(value.subStr(start: 0, end: 19))"
  216. let dayString = "\(value.subStr(start: 0, end: 10)) 00:00:00"
  217. var day = dateFormatter.date(from: dayString)!
  218. let start = dateFormatter.date(from: timeString)!
  219. // If on the first hours of the next day...
  220. let calendar = Calendar.current
  221. let hours = calendar.component(.hour, from: start )
  222. // ... the event belongs to the previous day.
  223. if hours < 6{
  224. day = Calendar.current.date(byAdding: .day, value: -1, to: day)!
  225. }
  226. query.setValue(day, forKey: "day")
  227. }
  228. }
  229. let start: Int = row.indexOf(target: ",\"")! + 2
  230. let end: Int = row.length
  231. if (start == end){
  232. row = ""
  233. }
  234. else{
  235. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  236. }
  237. }
  238. do{
  239. // Save the row
  240. try context.save()
  241. }
  242. catch let error as NSError {
  243. NSLog(":SYNC:ERROR: Could not save a row for table \(table.capitalize()): \(error), \(error.userInfo).")
  244. }
  245. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  246. }
  247. }
  248. /**
  249. Saves the received settings.
  250. :param: content JSON string containing the rows of the table.
  251. */
  252. func saveSettings(content: String){
  253. NSLog(":SYNC:LOG: Saving settings")
  254. var row: String
  255. var tuple: String
  256. var column: String
  257. var value: String
  258. var name: String = "dummy"
  259. let defaults = UserDefaults.standard
  260. //Loop rows
  261. var data: String = content
  262. while (data.indexOf(target: "}") != nil){
  263. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  264. row = "\(row),\""
  265. while (row.indexOf(target: ",\"") != nil){
  266. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  267. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  268. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  269. if (column == "name"){
  270. name = value
  271. }
  272. else if (column == "value"){
  273. defaults.set(value, forKey: name)
  274. }
  275. let start: Int = row.indexOf(target: ",\"")! + 1
  276. let end: Int = row.length - 1
  277. if (start == end){
  278. row = ""
  279. }
  280. else{
  281. row = row.subStr(start: start, end: end)
  282. }
  283. }
  284. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  285. }
  286. }
  287. /**
  288. Saves the new versions of the tables
  289. :param: content: JSON string.
  290. */
  291. func saveVersion(content: String){
  292. NSLog(":SYNC:LOG: Saving versions")
  293. var data: String = content
  294. var row: String
  295. var section: String = "dummy"
  296. var version: String = "0"
  297. var column: String
  298. var tuple: String
  299. var value: String
  300. //Set up context
  301. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  302. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  303. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  304. let entity = NSEntityDescription.entity(forEntityName: "Version", in: context)
  305. //Loop rows
  306. var query: NSManagedObject
  307. while (data.indexOf(target: "}") != nil){
  308. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  309. row = "\(row),\""
  310. while (row.indexOf(target: ",\"") != nil){
  311. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  312. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  313. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  314. if column == "section"{
  315. section = value
  316. }
  317. else if column == "version"{
  318. version = value
  319. // Check if version is already in database.
  320. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  321. let fetchRequest: NSFetchRequest<Version> = Version.fetchRequest()
  322. fetchRequest.predicate = NSPredicate(format: "section = %@", section)
  323. do {
  324. let searchResults = try context.fetch(fetchRequest)
  325. if searchResults.count > 0{
  326. let v = searchResults[0]
  327. v.setValue(Int(version), forKey: "version")
  328. }
  329. else{
  330. query = NSManagedObject(entity: entity!, insertInto: context)
  331. query.setValue(section, forKey: "section")
  332. query.setValue(Int(version), forKey: "version")
  333. }
  334. // Save the version
  335. try context.save()
  336. }
  337. catch let error as NSError {
  338. NSLog(":SYNC:ERROR: Could not store a version: \(error), \(error.userInfo).")
  339. }
  340. catch {
  341. NSLog(":SYNC:ERROR: Could not store a version.")
  342. }
  343. }
  344. let start: Int = row.indexOf(target: ",\"")! + 2
  345. let end: Int = row.length
  346. if (start == end){
  347. row = ""
  348. }
  349. else{
  350. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  351. }
  352. }
  353. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  354. }
  355. }
  356. }