Sync.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. var urlStr: String = ""
  80. //if initial{
  81. // urlStr = "https://margolariak.com/API/v3/fastsync.php?client=com.margolariak.iosapp&user=\(uId)\(strVersions)"
  82. //}
  83. //else{
  84. urlStr = "https://margolariak.com/API/v3/sync.php?client=com.margolariak.iosapp&user=\(uId)\(strVersions)"
  85. //}
  86. let url = URL(string: urlStr)
  87. NSLog(":SYNC:LOG: URL built: \(String(describing: url))")
  88. return url!
  89. }
  90. /**
  91. Performs an asynchronous sync.
  92. It fetches the info from the server and stores as Core Data
  93. :param: url The url to sync.
  94. */
  95. func sync(url: URL){
  96. NSLog(":SYNC:LOG: Sync started.")
  97. let delegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
  98. delegate.syncController?.nowSyncing = true
  99. //Synchronously get data
  100. let task = URLSession.shared.dataTask(with: url) { dat, response, error in
  101. guard error == nil else {
  102. NSLog(":SYNC:ERROR: Unknown error.")
  103. return
  104. }
  105. guard let rawData = dat else {
  106. NSLog(":SYNC:ERROR: Data is empty.")
  107. return
  108. }
  109. var data = String(data:rawData, encoding: String.Encoding.utf8)
  110. NSLog(":SYNC:LOG: Data received.")
  111. // Loop tables and save them to core data
  112. var table: String;
  113. var content: String;
  114. while (data?.indexOf(target: "}]") != nil){
  115. table = data!.subStr(start: 3, end: data!.indexOf(target: "\":")! - 1)
  116. content = data!.subStr(start: table.length + 5, end: (data?.indexOf(target: "}]"))! + 1)
  117. if table == "settings"{ // Special case
  118. self.saveSettings(content: content)
  119. }
  120. else if table == "version"{ // Special case
  121. self.saveVersion(content: content)
  122. }
  123. else{
  124. self.saveTable(table: table, content: content)
  125. }
  126. data = data?.subStr(start: (data?.indexOf(target: "}]"))! + 1, end: (data?.length)! - 1)
  127. }
  128. // If it's the initial sync, hide the segue
  129. if self.initial == true{
  130. NSLog(":SYNC:LOG: Finishing synchronous sync.")
  131. let delegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
  132. delegate.syncController?.nowSyncing = false
  133. }
  134. }
  135. task.resume()
  136. }
  137. /**
  138. Saves the data in the table.
  139. :param: table Name of the table
  140. :param: content JSON string containing the rows of the table.
  141. */
  142. func saveTable(table: String, content: String){
  143. NSLog(":SYNC:LOG: Saving table \(table)")
  144. let dateFormatter = DateFormatter()
  145. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  146. dateFormatter.timeZone = TimeZone.ReferenceType.local
  147. //Set up context
  148. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  149. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  150. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  151. let entity = NSEntityDescription.entity(forEntityName: table.capitalize(), in: context)
  152. //Delete all previous entries
  153. let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: table.capitalize())
  154. let request = NSBatchDeleteRequest(fetchRequest: fetch)
  155. do {
  156. try context.execute(request)
  157. } catch let error as NSError {
  158. NSLog(":SYNC:ERROR: Could not clean up \(table.capitalize()) entity: \(error), \(error.userInfo).")
  159. } catch {
  160. NSLog(":SYNC:ERROR: Could not clean up \(table.capitalize()) entity.")
  161. }
  162. //Loop rows
  163. var data: String = content
  164. var row: String
  165. var column: String
  166. var value: String
  167. var tuple: String
  168. var query: NSManagedObject
  169. while data.indexOf(target: "}") != nil{
  170. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  171. row = "\(row),\""
  172. query = NSManagedObject(entity: entity!, insertInto: context)
  173. while row.indexOf(target: ",\"") != nil{
  174. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  175. column = tuple.subStr(start: tuple.indexOf(target: "\"")! + 1, end: tuple.indexOf(target: "\":")! - 1)
  176. if column.length + 4 > tuple.length - 2{
  177. value = "ul"
  178. }
  179. else{
  180. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  181. }
  182. if value != "ul"{ // 'ul' from 'null' or empty. If it is, just do nothing.
  183. if entity?.attributesByName[column]?.attributeType == .stringAttributeType{
  184. query.setValue(value, forKey: column)
  185. }
  186. else if entity?.attributesByName[column]?.attributeType == .integer16AttributeType || entity?.attributesByName[column]?.attributeType == .integer32AttributeType || entity?.attributesByName[column]?.attributeType == .integer64AttributeType{
  187. query.setValue(Int(value), forKey: column)
  188. }
  189. else if entity?.attributesByName[column]?.attributeType == .doubleAttributeType{
  190. query.setValue(Double(value), forKey: column)
  191. }
  192. else if entity?.attributesByName[column]?.attributeType == .booleanAttributeType{
  193. if Int(value) == 1{
  194. query.setValue(true, forKey: column)
  195. }
  196. else if Int(value) == 0{
  197. query.setValue(false, forKey: column)
  198. }
  199. }
  200. else if entity?.attributesByName[column]?.attributeType == .dateAttributeType{
  201. if value.length < 11{
  202. value = "\(value) 00:00:00"
  203. }
  204. query.setValue(dateFormatter.date(from: value)!, forKey: column)
  205. }
  206. else{ //Regular string
  207. }
  208. // Special case: "festivl_event_citiy" and "festival_event_gm" have a special column: "day"
  209. if (table == "festival_event_city" || table == "festival_event_gm") && column == "start" {
  210. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  211. dateFormatter.calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.ISO8601)! as Calendar
  212. dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale?
  213. dateFormatter.timeZone = NSTimeZone.local
  214. let timeString = value//"\(value.subStr(start: 0, end: 19))"
  215. let dayString = "\(value.subStr(start: 0, end: 10)) 00:00:00"
  216. var day = dateFormatter.date(from: dayString)!
  217. let start = dateFormatter.date(from: timeString)!
  218. // If on the first hours of the next day...
  219. let calendar = Calendar.current
  220. let hours = calendar.component(.hour, from: start )
  221. // ... the event belongs to the previous day.
  222. if hours < 6{
  223. day = Calendar.current.date(byAdding: .day, value: -1, to: day)!
  224. }
  225. query.setValue(day, forKey: "day")
  226. }
  227. }
  228. let start: Int = row.indexOf(target: ",\"")! + 2
  229. let end: Int = row.length
  230. if (start == end){
  231. row = ""
  232. }
  233. else{
  234. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  235. }
  236. }
  237. do{
  238. // Save the row
  239. try context.save()
  240. }
  241. catch let error as NSError {
  242. NSLog(":SYNC:ERROR: Could not save a row for table \(table.capitalize()): \(error), \(error.userInfo).")
  243. }
  244. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  245. }
  246. }
  247. /**
  248. Saves the received settings.
  249. :param: content JSON string containing the rows of the table.
  250. */
  251. func saveSettings(content: String){
  252. NSLog(":SYNC:LOG: Saving settings")
  253. var row: String
  254. var tuple: String
  255. var column: String
  256. var value: String
  257. var name: String = "dummy"
  258. let defaults = UserDefaults.standard
  259. //Loop rows
  260. var data: String = content
  261. while (data.indexOf(target: "}") != nil){
  262. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  263. row = "\(row),\""
  264. while (row.indexOf(target: ",\"") != nil){
  265. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  266. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  267. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  268. if (column == "name"){
  269. name = value
  270. }
  271. else if (column == "value"){
  272. defaults.set(value, forKey: name)
  273. }
  274. let start: Int = row.indexOf(target: ",\"")! + 1
  275. let end: Int = row.length - 1
  276. if (start == end){
  277. row = ""
  278. }
  279. else{
  280. row = row.subStr(start: start, end: end)
  281. }
  282. }
  283. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  284. }
  285. }
  286. /**
  287. Saves the new versions of the tables
  288. :param: content: JSON string.
  289. */
  290. func saveVersion(content: String){
  291. NSLog(":SYNC:LOG: Saving versions")
  292. var data: String = content
  293. var row: String
  294. var section: String = "dummy"
  295. var version: String = "0"
  296. var column: String
  297. var tuple: String
  298. var value: String
  299. //Set up context
  300. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  301. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  302. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  303. let entity = NSEntityDescription.entity(forEntityName: "Version", in: context)
  304. //Loop rows
  305. var query: NSManagedObject
  306. while (data.indexOf(target: "}") != nil){
  307. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  308. row = "\(row),\""
  309. while (row.indexOf(target: ",\"") != nil){
  310. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  311. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  312. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  313. if column == "section"{
  314. section = value
  315. }
  316. else if column == "version"{
  317. version = value
  318. // Check if version is already in database.
  319. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  320. let fetchRequest: NSFetchRequest<Version> = Version.fetchRequest()
  321. fetchRequest.predicate = NSPredicate(format: "section = %@", section)
  322. do {
  323. let searchResults = try context.fetch(fetchRequest)
  324. if searchResults.count > 0{
  325. let v = searchResults[0]
  326. v.setValue(Int(version), forKey: "version")
  327. }
  328. else{
  329. query = NSManagedObject(entity: entity!, insertInto: context)
  330. query.setValue(section, forKey: "section")
  331. query.setValue(Int(version), forKey: "version")
  332. }
  333. // Save the version
  334. try context.save()
  335. }
  336. catch let error as NSError {
  337. NSLog(":SYNC:ERROR: Could not store a version: \(error), \(error.userInfo).")
  338. }
  339. catch {
  340. NSLog(":SYNC:ERROR: Could not store a version.")
  341. }
  342. }
  343. let start: Int = row.indexOf(target: ",\"")! + 2
  344. let end: Int = row.length
  345. if (start == end){
  346. row = ""
  347. }
  348. else{
  349. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  350. }
  351. }
  352. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  353. }
  354. }
  355. }