Sync.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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 = "http://192.168.1.101/API/v3/fastsync.php?client=com.margolariak.app&user=\(uId)\(strVersions)"
  83. }
  84. else{
  85. urlStr = "http://192.168.1.101/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 == .booleanAttributeType{
  191. if Int(value) == 1{
  192. query.setValue(true, forKey: column)
  193. }
  194. else if Int(value) == 0{
  195. query.setValue(false, forKey: column)
  196. }
  197. }
  198. else if entity?.attributesByName[column]?.attributeType == .dateAttributeType{
  199. if value.length < 11{
  200. value = "\(value) 00:00:00"
  201. }
  202. query.setValue(dateFormatter.date(from: value)!, forKey: column)
  203. }
  204. else{ //Regular string
  205. }
  206. // Special case: "festivl_event_citiy" and "festival_event_gm" have a special column: "day"
  207. if (table == "festival_event_city" || table == "festival_event_gm") && column == "start" {
  208. NSLog("IVV Getting day")
  209. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  210. dateFormatter.calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.ISO8601)! as Calendar
  211. dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale!
  212. dateFormatter.timeZone = NSTimeZone.local
  213. let timeString = value//"\(value.subStr(start: 0, end: 19))"
  214. let dayString = "\(value.subStr(start: 0, end: 10)) 00:00:00"
  215. var day = dateFormatter.date(from: dayString)!
  216. let start = dateFormatter.date(from: timeString)!
  217. // If on the first hours of the next day...
  218. let calendar = Calendar.current
  219. let hours = calendar.component(.hour, from: start )
  220. // ... the event belongs to the previous day.
  221. if hours < 6{
  222. day = Calendar.current.date(byAdding: .day, value: -1, to: day)!
  223. }
  224. query.setValue(day, forKey: "day")
  225. }
  226. }
  227. let start: Int = row.indexOf(target: ",\"")! + 2
  228. let end: Int = row.length
  229. if (start == end){
  230. row = ""
  231. }
  232. else{
  233. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  234. }
  235. }
  236. do{
  237. // Save the row
  238. try context.save()
  239. }
  240. catch let error as NSError {
  241. NSLog(":SYNC:ERROR: Could not save a row for table \(table.capitalize()): \(error), \(error.userInfo).")
  242. }
  243. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  244. }
  245. }
  246. /**
  247. Saves the received settings.
  248. :param: content JSON string containing the rows of the table.
  249. */
  250. func saveSettings(content: String){
  251. NSLog(":SYNC:LOG: Saving settings")
  252. var row: String
  253. var tuple: String
  254. var column: String
  255. var value: String
  256. var name: String = "dummy"
  257. let defaults = UserDefaults.standard
  258. //Loop rows
  259. var data: String = content
  260. while (data.indexOf(target: "}") != nil){
  261. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  262. row = "\(row),\""
  263. while (row.indexOf(target: ",\"") != nil){
  264. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  265. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  266. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  267. if (column == "name"){
  268. name = value
  269. }
  270. else if (column == "value"){
  271. defaults.set(value, forKey: name)
  272. }
  273. let start: Int = row.indexOf(target: ",\"")! + 1
  274. let end: Int = row.length - 1
  275. if (start == end){
  276. row = ""
  277. }
  278. else{
  279. row = row.subStr(start: start, end: end)
  280. }
  281. }
  282. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  283. }
  284. }
  285. /**
  286. Saves the new versions of the tables
  287. :param: content: JSON string.
  288. */
  289. func saveVersion(content: String){
  290. NSLog(":SYNC:LOG: Saving versions")
  291. var data: String = content
  292. var row: String
  293. var section: String = "dummy"
  294. var version: String = "0"
  295. var column: String
  296. var tuple: String
  297. var value: String
  298. //Set up context
  299. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  300. let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  301. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  302. let entity = NSEntityDescription.entity(forEntityName: "Version", in: context)
  303. //Loop rows
  304. var query: NSManagedObject
  305. while (data.indexOf(target: "}") != nil){
  306. row = data.subStr(start: data.indexOf(target: "{")! + 1, end: data.indexOf(target: "}")! - 1)
  307. row = "\(row),\""
  308. while (row.indexOf(target: ",\"") != nil){
  309. tuple = row.subStr(start: 0, end: row.indexOf(target: ",\"")! - 1)
  310. column = tuple.subStr(start: 1, end: tuple.indexOf(target: "\":")! - 1)
  311. value = tuple.subStr(start: column.length + 4, end: tuple.length - 2)
  312. if column == "section"{
  313. section = value
  314. }
  315. else if column == "version"{
  316. version = value
  317. // Check if version is already in database.
  318. context.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
  319. let fetchRequest: NSFetchRequest<Version> = Version.fetchRequest()
  320. fetchRequest.predicate = NSPredicate(format: "section = %@", section)
  321. do {
  322. let searchResults = try context.fetch(fetchRequest)
  323. if searchResults.count > 0{
  324. let v = searchResults[0]
  325. v.setValue(Int(version), forKey: "version")
  326. }
  327. else{
  328. query = NSManagedObject(entity: entity!, insertInto: context)
  329. query.setValue(section, forKey: "section")
  330. query.setValue(Int(version), forKey: "version")
  331. }
  332. // Save the row
  333. try context.save()
  334. }
  335. catch let error as NSError {
  336. NSLog(":SYNC:ERROR: Could not store a version: \(error), \(error.userInfo).")
  337. }
  338. catch {
  339. NSLog(":SYNC:ERROR: Could not store a version.")
  340. }
  341. }
  342. let start: Int = row.indexOf(target: ",\"")! + 2
  343. let end: Int = row.length
  344. if (start == end){
  345. row = ""
  346. }
  347. else{
  348. row = row.subStr(start: row.indexOf(target: ",\"")! + 1, end: row.length - 1)
  349. }
  350. }
  351. data = data.subStr(start: data.indexOf(target: "}")! + 1, end: data.length - 1)
  352. }
  353. }
  354. }