Sync.swift 13 KB

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