BackupRestoreView.swift 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. //
  2. // BackupRestoreView.swift
  3. // NexilisLite
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 16/02/23.
  6. //
  7. import UIKit
  8. import QuickLook
  9. //import Zip
  10. import NotificationBannerSwift
  11. public class BackupRestoreView: UIViewController, UITableViewDataSource, UITableViewDelegate {
  12. private var tableView: UITableView!
  13. var centerLogo = UIImageView()
  14. var centerLogoIsRotated = false
  15. let activityIndicatorBackup = UIActivityIndicatorView(style: .medium)
  16. let activityIndicatorRestore = UIActivityIndicatorView(style: .medium)
  17. let titleBackup = UILabel()
  18. let titleRestore = UILabel()
  19. let titleLastBackup = UILabel()
  20. let titleTotalSize = UILabel()
  21. let labelRestoring = UILabel()
  22. let labelPreparing = UILabel()
  23. var isBackupStart = false
  24. var isRestoreStart = false
  25. var valueLastBackup = ""
  26. var valuesizeBackup = ""
  27. var dayLastBackup = ""
  28. var timeLastBackup = ""
  29. var choosenOption = "M"
  30. var fileIdBackup = ""
  31. var recordSizeBackup = ""
  32. var optionBackup = ""
  33. var recordSizeRestore: Int64 = 0
  34. let separator = String(unicodeCodepoint: 0x06) ?? ""
  35. public override func viewDidLoad() {
  36. super.viewDidLoad()
  37. navigationController?.navigationBar.topItem?.backButtonTitle = "Back".localized()
  38. tableView = UITableView()
  39. view.addSubview(tableView)
  40. tableView.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor)
  41. tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellBackupRestore")
  42. tableView.dataSource = self
  43. tableView.delegate = self
  44. tableView.separatorStyle = .none
  45. if #available(iOS 15.0, *) {
  46. tableView.sectionHeaderTopPadding = 0
  47. }
  48. let center: NotificationCenter = NotificationCenter.default
  49. center.addObserver(self, selector: #selector(backupAvailability(notification:)), name: NSNotification.Name(rawValue: "backupAvailability"), object: nil)
  50. requestBackupAvailability()
  51. }
  52. public override func viewDidAppear(_ animated: Bool) {
  53. self.navigationController?.navigationBar.topItem?.title = "Backup & Restore".localized()
  54. self.navigationController?.navigationBar.setNeedsLayout()
  55. self.title = "Backup & Restore".localized()
  56. }
  57. @objc private func backupAvailability(notification: NSNotification) {
  58. DispatchQueue.main.async { [self] in
  59. let data:[AnyHashable : Any] = notification.userInfo!
  60. if let message = data["message"] as? TMessage {
  61. fileIdBackup = message.getBody(key: CoreMessage_TMessageKey.FILE_ID, default_value: "")
  62. recordSizeBackup = message.getBody(key: CoreMessage_TMessageKey.RECORD_SIZE, default_value: "0")
  63. optionBackup = message.getBody(key: CoreMessage_TMessageKey.TYPE, default_value: "")
  64. let filesize = message.getBody(key: CoreMessage_TMessageKey.FILE_SIZE, default_value: "0")
  65. let createdDate = message.getBody(key: CoreMessage_TMessageKey.CREATED_DATE, default_value: "\(Date().currentTimeMillis())")
  66. if optionBackup != "AUTO" {
  67. let date = Date(milliseconds: Int64(createdDate)!)
  68. let calendar = Calendar.current
  69. if (calendar.isDateInToday(date)) {
  70. dayLastBackup = "Today".localized()
  71. } else {
  72. let startOfNow = calendar.startOfDay(for: Date())
  73. let startOfTimeStamp = calendar.startOfDay(for: date)
  74. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  75. let day = -(components.day!)
  76. if day == 1{
  77. dayLastBackup = "Yesterday".localized()
  78. } else {
  79. let formatter = DateFormatter()
  80. formatter.dateFormat = "dd MMMM yyyy"
  81. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  82. let stringFormat = formatter.string(from: date as Date)
  83. dayLastBackup = stringFormat
  84. }
  85. }
  86. let formatter = DateFormatter()
  87. formatter.dateFormat = "HH:mm"
  88. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  89. timeLastBackup = formatter.string(from: date as Date)
  90. valueLastBackup = dayLastBackup.localized() + ", " + timeLastBackup
  91. valuesizeBackup = Units(bytes: Int64(filesize)!).getReadableUnit()
  92. tableView.beginUpdates()
  93. tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none)
  94. tableView.endUpdates()
  95. } else {
  96. valueLastBackup = "-"
  97. valuesizeBackup = "-"
  98. tableView.beginUpdates()
  99. tableView.reloadRows(at: [IndexPath(row: 0, section: 0), IndexPath(row: 0, section: 2)], with: .none)
  100. tableView.endUpdates()
  101. }
  102. }
  103. }
  104. }
  105. private func requestBackupAvailability() {
  106. DispatchQueue.global().async {
  107. _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupAvailability())
  108. }
  109. }
  110. private func getFileName(option: String = "", fileId: String = "", withoutZIP: Bool = false) -> String {
  111. if !fileId.isEmpty {
  112. if withoutZIP {
  113. return "\(User.getMyPin()!)_\(option)_\(fileId)"
  114. }
  115. return "\(User.getMyPin()!)_\(option)_\(fileId).zip"
  116. }
  117. return "\(User.getMyPin()!).zip"
  118. }
  119. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  120. if section == 2 && isBackupStart {
  121. let container = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 20))
  122. container.addSubview(labelPreparing)
  123. labelPreparing.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
  124. labelPreparing.textColor = .gray
  125. labelPreparing.font = .systemFont(ofSize: 12)
  126. return container
  127. } else if section == 3 && isRestoreStart {
  128. let container = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 20))
  129. container.addSubview(labelRestoring)
  130. labelRestoring.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
  131. labelRestoring.textColor = .gray
  132. labelRestoring.font = .systemFont(ofSize: 12)
  133. return container
  134. }
  135. return UIView()
  136. }
  137. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  138. if section == 2 && isBackupStart {
  139. return 30
  140. } else if section == 3 && isRestoreStart {
  141. return 30
  142. }
  143. return 20
  144. }
  145. public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  146. .leastNormalMagnitude
  147. }
  148. public func numberOfSections(in tableView: UITableView) -> Int {
  149. return 4
  150. }
  151. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  152. if section == 1 {
  153. return 2
  154. } else if section == 3 {
  155. return 0
  156. }
  157. return 1
  158. }
  159. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  160. let cell = tableView.dequeueReusableCell(withIdentifier: "cellBackupRestore", for: indexPath as IndexPath)
  161. cell.backgroundColor = .secondaryColor
  162. makeViewBackup(cell: cell, indexPath: indexPath)
  163. return cell
  164. }
  165. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  166. tableView.deselectRow(at: indexPath, animated: true)
  167. if indexPath.section == 1 && indexPath.row == 0 {
  168. let controller = BackupRestoreOption()
  169. controller.selected = choosenOption
  170. controller.isSelected = { choosen in
  171. self.choosenOption = choosen
  172. tableView.beginUpdates()
  173. tableView.reloadRows(at: [indexPath], with: .none)
  174. tableView.endUpdates()
  175. }
  176. navigationController?.show(controller, sender: nil)
  177. } else if indexPath.section == 1 && indexPath.row == 1 {
  178. if isBackupStart || isRestoreStart {
  179. return
  180. }
  181. if !CheckConnection.isConnectedToNetwork() {
  182. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  183. imageView.tintColor = .white
  184. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  185. banner.show()
  186. return
  187. }
  188. isBackupStart = true
  189. labelPreparing.text = "Preparing...".localized();
  190. tableView.beginUpdates()
  191. tableView.reloadRows(at: [indexPath], with: .none)
  192. tableView.reloadSections(IndexSet(integer: 2), with: .none)
  193. tableView.endUpdates()
  194. animateBackup()
  195. backupData(indexPath: indexPath)
  196. } else if indexPath.section == 2 {
  197. if isBackupStart || isRestoreStart || valueLastBackup == "-" {
  198. return
  199. }
  200. if !CheckConnection.isConnectedToNetwork() {
  201. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  202. imageView.tintColor = .white
  203. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  204. banner.show()
  205. return
  206. }
  207. isRestoreStart = true
  208. labelRestoring.text = "Downloading...".localized();
  209. tableView.beginUpdates()
  210. tableView.reloadRows(at: [indexPath, IndexPath(row: 1, section: 1)], with: .none)
  211. tableView.reloadSections(IndexSet(integer: 3), with: .none)
  212. tableView.endUpdates()
  213. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  214. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  215. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  216. if let dirPath = paths.first {
  217. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(getFileName(option: optionBackup, fileId: fileIdBackup))
  218. if !FileManager.default.fileExists(atPath: fileURL.path) {
  219. Download().start(forKey: getFileName(option: optionBackup, fileId: fileIdBackup)) { (name, progress) in
  220. DispatchQueue.main.async { [self] in
  221. guard progress == 100 else {
  222. labelRestoring.text = "Downloading...".localized() + " \(progress)%"
  223. return
  224. }
  225. labelRestoring.text = "Restoring...".localized()
  226. restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
  227. }
  228. }
  229. } else {
  230. labelRestoring.text = "Restoring...".localized()
  231. restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
  232. }
  233. }
  234. }
  235. }
  236. private func animateBackup() {
  237. UIView.animate(withDuration: 2, animations: { [self] in
  238. centerLogo.transform = centerLogo.transform.rotated(by: .pi)
  239. })
  240. UIView.animate(withDuration: 2, animations: { [self] in
  241. centerLogo.transform = centerLogo.transform.rotated(by: .pi)
  242. })
  243. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
  244. if isBackupStart {
  245. animateBackup()
  246. }
  247. })
  248. }
  249. private func enableButtonBackup() {
  250. titleBackup.text = "Back Up Now".localized()
  251. titleBackup.textColor = .systemBlue
  252. activityIndicatorBackup.stopAnimating()
  253. }
  254. private func disableButtonBackup(isRestore: Bool = false) {
  255. titleBackup.textColor = .gray
  256. if !isRestore{
  257. titleBackup.text = "Backing Up...".localized()
  258. activityIndicatorBackup.startAnimating()
  259. }
  260. }
  261. private func enableButtonRestore() {
  262. titleRestore.text = "Restore Now".localized()
  263. titleRestore.textColor = .systemBlue
  264. activityIndicatorRestore.stopAnimating()
  265. }
  266. private func disableButtonRestore(isBackup: Bool = false) {
  267. titleRestore.textColor = .gray
  268. if !isBackup && !activityIndicatorRestore.isAnimating {
  269. titleRestore.text = "Restoring...".localized()
  270. activityIndicatorRestore.startAnimating()
  271. } else {
  272. titleRestore.text = "Restore Now".localized()
  273. activityIndicatorRestore.stopAnimating()
  274. }
  275. }
  276. private func makeViewBackup(cell: UITableViewCell, indexPath: IndexPath) {
  277. cell.contentView.subviews.forEach { $0.removeFromSuperview() }
  278. if indexPath.section == 0 {
  279. cell.selectionStyle = .none
  280. let container = UIView()
  281. container.addTopBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  282. container.addBottomBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  283. let content = cell.contentView
  284. content.addSubview(container)
  285. container.anchor(top: content.topAnchor, left: content.leftAnchor, bottom: content.bottomAnchor, right: content.rightAnchor)
  286. let containerLogo = UIView()
  287. containerLogo.layer.borderWidth = 1
  288. containerLogo.layer.borderColor = UIColor.lightGray.cgColor
  289. containerLogo.layer.cornerRadius = 8
  290. containerLogo.clipsToBounds = true
  291. container.addSubview(containerLogo)
  292. containerLogo.anchor(top: container.topAnchor, left: container.leftAnchor, paddingTop: 10, paddingLeft: 10, width: 70, height: 70)
  293. let logo = UIImageView()
  294. logo.image = UIImage(systemName: "icloud")
  295. logo.contentMode = .scaleAspectFit
  296. logo.tintColor = .systemBlue
  297. containerLogo.addSubview(logo)
  298. logo.anchor(centerX: containerLogo.centerXAnchor, centerY: containerLogo.centerYAnchor, width: 60, height: 60)
  299. centerLogo.image = UIImage(systemName: "arrow.clockwise")
  300. centerLogo.contentMode = .scaleAspectFit
  301. centerLogo.tintColor = .systemBlue
  302. if !isBackupStart {
  303. if !centerLogoIsRotated{
  304. centerLogoIsRotated = true
  305. centerLogo.transform = centerLogo.transform.rotated(by: .pi / 2)
  306. print("LOHE \(centerLogo.transform)")
  307. }
  308. }
  309. logo.addSubview(centerLogo)
  310. centerLogo.anchor(top: logo.topAnchor, left: logo.leftAnchor, paddingTop: 22, paddingLeft: 23)
  311. container.addSubview(titleLastBackup)
  312. titleLastBackup.anchor(top: container.topAnchor, left: containerLogo.rightAnchor, paddingTop: 25, paddingLeft: 10)
  313. titleLastBackup.text = "Last Backup".localized() + ": " + valueLastBackup
  314. titleLastBackup.textColor = .gray
  315. titleLastBackup.font = .systemFont(ofSize: 12)
  316. container.addSubview(titleTotalSize)
  317. titleTotalSize.anchor(top: titleLastBackup.bottomAnchor, left: containerLogo.rightAnchor, paddingLeft: 10)
  318. titleTotalSize.text = "Total Size".localized() + ": " + valuesizeBackup
  319. titleTotalSize.textColor = .gray
  320. titleTotalSize.font = .systemFont(ofSize: 12)
  321. let descBackup = UILabel()
  322. container.addSubview(descBackup)
  323. descBackup.anchor(top: containerLogo.bottomAnchor, left: container.leftAnchor, bottom: container.bottomAnchor, right: container.rightAnchor, paddingTop: 2, paddingLeft: 10, paddingBottom: 10, paddingRight: 10)
  324. descBackup.text = "Back up your chat history to server so if you lose your phone or switch to a new one or logout your account, your chat history is safe. You can restore your chat history when you relogin your account.".localized()
  325. descBackup.numberOfLines = 0
  326. descBackup.textColor = .black
  327. descBackup.font = .systemFont(ofSize: 12)
  328. } else if indexPath.section == 1 {
  329. if indexPath.row == 0 {
  330. let container = UIView()
  331. container.addTopBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  332. container.addBottomBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 0.5, x: 10)
  333. let content = cell.contentView
  334. content.addSubview(container)
  335. container.anchor(top: content.topAnchor, left: content.leftAnchor, bottom: content.bottomAnchor, right: content.rightAnchor)
  336. let titleBackupOption = UILabel()
  337. container.addSubview(titleBackupOption)
  338. titleBackupOption.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
  339. titleBackupOption.text = "Back Up Option".localized()
  340. titleBackupOption.textColor = .systemBlue
  341. titleBackupOption.font = .systemFont(ofSize: 14)
  342. let arrowRight = UIImageView()
  343. arrowRight.tintColor = .gray
  344. arrowRight.image = UIImage(systemName: "chevron.right")
  345. container.addSubview(arrowRight)
  346. arrowRight.anchor(right: container.rightAnchor, paddingRight: 10, centerY: container.centerYAnchor)
  347. let titleChoosenOption = UILabel()
  348. container.addSubview(titleChoosenOption)
  349. titleChoosenOption.anchor(right: arrowRight.leftAnchor, paddingRight: 10, centerY: container.centerYAnchor)
  350. titleChoosenOption.text = BackupRestoreOption().convertSelectedOptionWithCode(code: choosenOption)
  351. titleChoosenOption.textColor = .lightGray
  352. titleChoosenOption.font = .systemFont(ofSize: 14)
  353. } else {
  354. let container = UIView()
  355. container.addBottomBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  356. let content = cell.contentView
  357. content.addSubview(container)
  358. container.anchor(top: content.topAnchor, left: content.leftAnchor, bottom: content.bottomAnchor, right: content.rightAnchor)
  359. container.addSubview(titleBackup)
  360. titleBackup.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
  361. titleBackup.font = .systemFont(ofSize: 14)
  362. container.addSubview(activityIndicatorBackup)
  363. activityIndicatorBackup.anchor(right: container.rightAnchor, paddingRight: 10, centerY: container.centerYAnchor)
  364. if isBackupStart || isRestoreStart {
  365. cell.selectionStyle = .none
  366. disableButtonBackup(isRestore: isRestoreStart)
  367. } else {
  368. cell.selectionStyle = .default
  369. enableButtonBackup()
  370. }
  371. }
  372. } else {
  373. let container = UIView()
  374. container.addTopBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  375. container.addBottomBorder(with: .lightGray.withAlphaComponent(0.5), andWidth: 1)
  376. let content = cell.contentView
  377. content.addSubview(container)
  378. container.anchor(top: content.topAnchor, left: content.leftAnchor, bottom: content.bottomAnchor, right: content.rightAnchor)
  379. container.addSubview(titleRestore)
  380. titleRestore.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
  381. titleRestore.font = .systemFont(ofSize: 14)
  382. container.addSubview(activityIndicatorRestore)
  383. activityIndicatorRestore.anchor(right: container.rightAnchor, paddingRight: 10, centerY: container.centerYAnchor)
  384. if isBackupStart || isRestoreStart || valueLastBackup == "-" {
  385. cell.selectionStyle = .none
  386. disableButtonRestore(isBackup: isBackupStart || valueLastBackup == "-")
  387. } else {
  388. cell.selectionStyle = .default
  389. enableButtonRestore()
  390. }
  391. }
  392. }
  393. private func restoreMessage(nameColumn: [String], message: [String]) {
  394. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  395. do {
  396. var cValues: [String: Any] = [:]
  397. var columnNameMessage: [String] = []
  398. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(MESSAGE)") {
  399. while tableInfo.next() {
  400. columnNameMessage.append(tableInfo.string(forColumn: "name")!)
  401. }
  402. tableInfo.close()
  403. }
  404. for i in 0..<message.count {
  405. if i > nameColumn.count - 1 {
  406. continue
  407. }
  408. if columnNameMessage.contains(nameColumn[i]) {
  409. cValues[nameColumn[i]] = message[i] == "<empty>" || message[i] == "null" ? "" : message[i]
  410. }
  411. }
  412. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "MESSAGE", cvalues: cValues, replace: true)
  413. if !(cValues["thumb_id"] as? String ?? "").isEmpty {
  414. let thumbId = cValues["thumb_id"] as! String
  415. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  416. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  417. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  418. if let dirPath = paths.first {
  419. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbId)
  420. if !FileManager.default.fileExists(atPath: thumbURL.path) {
  421. Download().start(forKey: thumbId) { (name, progress) in}
  422. }
  423. }
  424. }
  425. recordSizeRestore += 1
  426. } catch {
  427. rollback.pointee = true
  428. print(error)
  429. }
  430. })
  431. }
  432. private func restoreUcList(dataUcList: [String]) {
  433. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  434. do {
  435. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
  436. "l_pin" : dataUcList[0],
  437. "message_id" : dataUcList[1],
  438. "counter" : 0
  439. ], replace: true)
  440. recordSizeRestore += 1
  441. } catch {
  442. rollback.pointee = true
  443. print(error)
  444. }
  445. })
  446. }
  447. private func restoreFormData(nameColumn: [String], data: [String]) {
  448. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  449. do {
  450. var cValues: [String: Any] = [:]
  451. var columnNameMessage: [String] = []
  452. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(FORM_DATA)") {
  453. while tableInfo.next() {
  454. columnNameMessage.append(tableInfo.string(forColumn: "name")!)
  455. }
  456. tableInfo.close()
  457. }
  458. for i in 0..<data.count {
  459. if columnNameMessage.contains(nameColumn[i]) {
  460. cValues[nameColumn[i]] = data[i] == "<empty>" || data[i] == "null" ? "" : data[i]
  461. }
  462. }
  463. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "FORM_DATA", cvalues: cValues, replace: true)
  464. recordSizeRestore += 1
  465. } catch {
  466. rollback.pointee = true
  467. print(error)
  468. }
  469. })
  470. }
  471. private func restoreTaskPIC(nameColumn: [String], data: [String]) {
  472. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  473. do {
  474. var cValues: [String: Any] = [:]
  475. var columnNameMessage: [String] = []
  476. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(TASK_PIC)") {
  477. while tableInfo.next() {
  478. columnNameMessage.append(tableInfo.string(forColumn: "name")!)
  479. }
  480. tableInfo.close()
  481. }
  482. for i in 0..<data.count {
  483. if columnNameMessage.contains(nameColumn[i]) {
  484. cValues[nameColumn[i]] = data[i] == "<empty>" || data[i] == "null" ? "" : data[i]
  485. }
  486. }
  487. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "TASK_PIC", cvalues: cValues, replace: true)
  488. recordSizeRestore += 1
  489. } catch {
  490. rollback.pointee = true
  491. print(error)
  492. }
  493. })
  494. }
  495. private func restoreTaskDetail(nameColumn: [String], data: [String]) {
  496. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  497. do {
  498. var cValues: [String: Any] = [:]
  499. var columnNameMessage: [String] = []
  500. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(TASK_DETAIL)") {
  501. while tableInfo.next() {
  502. columnNameMessage.append(tableInfo.string(forColumn: "name")!)
  503. }
  504. tableInfo.close()
  505. }
  506. for i in 0..<data.count {
  507. if columnNameMessage.contains(nameColumn[i]) {
  508. cValues[nameColumn[i]] = data[i] == "<empty>" || data[i] == "null" ? "" : data[i]
  509. }
  510. }
  511. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "TASK_DETAIL", cvalues: cValues, replace: true)
  512. recordSizeRestore += 1
  513. } catch {
  514. rollback.pointee = true
  515. print(error)
  516. }
  517. })
  518. }
  519. private func restoreData(file: URL, dirPath: String, indexPath: IndexPath) {
  520. recordSizeRestore = 0
  521. let fileManager = FileManager()
  522. var destinationURL = URL(fileURLWithPath: dirPath)
  523. destinationURL.appendPathComponent("unzipItem\(Date().currentTimeMillis())")
  524. do {
  525. try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
  526. // try Zip.unzipFile(file, destination: destinationURL, overwrite: true, password: nil)
  527. let files = try FileManager.default.contentsOfDirectory(atPath: destinationURL.path)
  528. for file in files {
  529. let nameFile = (file as NSString).lastPathComponent
  530. let fileURL = destinationURL.appendingPathComponent(nameFile)
  531. var newFileExt: URL?
  532. newFileExt = fileURL.deletingPathExtension().appendingPathExtension("txt")
  533. try FileManager.default.moveItem(at: fileURL, to: newFileExt!)
  534. var textReading = try String(contentsOf: newFileExt ?? fileURL, encoding: .utf8)
  535. textReading = textReading.replacingOccurrences(of: "<NL>", with: "\n").replacingOccurrences(of: "<CR>", with: "\r")
  536. let valueText = textReading.components(separatedBy: "\n")
  537. if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "MESSAGE" {
  538. let nameColumn = valueText[0].components(separatedBy: separator)
  539. for i in 1..<valueText.count - 1 {
  540. let dataMessage = valueText[i].components(separatedBy: separator)
  541. restoreMessage(nameColumn: nameColumn, message: dataMessage)
  542. }
  543. } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "UC_LIST" {
  544. for i in 1..<valueText.count - 1 {
  545. let dataUcList = valueText[i].components(separatedBy: separator)
  546. restoreUcList(dataUcList: dataUcList)
  547. }
  548. } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "FORM_DATA" {
  549. let nameColumn = valueText[0].components(separatedBy: separator)
  550. for i in 1..<valueText.count - 1 {
  551. let dataFormData = valueText[i].components(separatedBy: separator)
  552. restoreFormData(nameColumn: nameColumn, data: dataFormData)
  553. }
  554. } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "TASK_PIC" {
  555. let nameColumn = valueText[0].components(separatedBy: separator)
  556. for i in 1..<valueText.count - 1 {
  557. let dataTaskPIC = valueText[i].components(separatedBy: separator)
  558. restoreTaskPIC(nameColumn: nameColumn, data: dataTaskPIC)
  559. }
  560. }
  561. else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "TASK_DETAIL" {
  562. let nameColumn = valueText[0].components(separatedBy: separator)
  563. for i in 1..<valueText.count - 1 {
  564. let dataTaskDetail = valueText[i].components(separatedBy: separator)
  565. restoreTaskDetail(nameColumn: nameColumn, data: dataTaskDetail)
  566. }
  567. }
  568. }
  569. if recordSizeRestore < Int64(recordSizeBackup) ?? 0 {
  570. labelRestoring.text = "Backup files are corrupted".localized()
  571. tableView.reloadSections(IndexSet(integer: 3), with: .none)
  572. } else {
  573. labelRestoring.text = "Successfully Restored Data".localized()
  574. }
  575. DispatchQueue.global().async { [self] in
  576. _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupRestored(option: optionBackup, fileid: fileIdBackup))
  577. }
  578. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
  579. isRestoreStart = false
  580. valueLastBackup = "-"
  581. valuesizeBackup = "-"
  582. tableView.beginUpdates()
  583. tableView.reloadRows(at: [indexPath, IndexPath(row: 1, section: 1), IndexPath(row: 0, section: 0), IndexPath(row: 0, section: 2)], with: .none)
  584. tableView.reloadSections(IndexSet(integer: 3), with: .none)
  585. tableView.endUpdates()
  586. })
  587. } catch {
  588. print(error)
  589. self.view.makeToast("Backup files are corrupted".localized(), duration: 0.5)
  590. DispatchQueue.global().async { [self] in
  591. _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupRestored(option: optionBackup, fileid: fileIdBackup))
  592. }
  593. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
  594. isRestoreStart = false
  595. valueLastBackup = "-"
  596. valuesizeBackup = "-"
  597. tableView.beginUpdates()
  598. tableView.reloadRows(at: [indexPath, IndexPath(row: 1, section: 1), IndexPath(row: 0, section: 0), IndexPath(row: 0, section: 2)], with: .none)
  599. tableView.reloadSections(IndexSet(integer: 3), with: .none)
  600. tableView.endUpdates()
  601. })
  602. }
  603. }
  604. private func backupData(indexPath: IndexPath) {
  605. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  606. let documentDirectoryUrl = try! FileManager.default.url(
  607. for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true
  608. )
  609. var recordSize: Int64 = 0
  610. //Make File MESSAGE
  611. let file_message = documentDirectoryUrl.appendingPathComponent("MESSAGE").appendingPathExtension("")
  612. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(MESSAGE)") {
  613. var text_message = ""
  614. while tableInfo.next() {
  615. if text_message.isEmpty {
  616. text_message.append(tableInfo.string(forColumn: "name")!)
  617. } else {
  618. text_message.append(separator)
  619. text_message.append(tableInfo.string(forColumn: "name")!)
  620. }
  621. }
  622. text_message.append("\n")
  623. tableInfo.close()
  624. if let cursorData = Database.shared.getRecords(fmdb: fmdb,query: "SELECT * FROM MESSAGE") {
  625. let columnCount = cursorData.columnCount
  626. var value_m = ""
  627. while cursorData.next() {
  628. for i in 0..<columnCount {
  629. value_m.append(cursorData.string(forColumnIndex: i) == nil ? "null" : cursorData.string(forColumnIndex: i)!.isEmpty ? "<empty>" : cursorData.string(forColumnIndex: i)!)
  630. value_m.append(separator)
  631. }
  632. value_m.append("\n")
  633. recordSize += 1
  634. }
  635. text_message.append(value_m)
  636. cursorData.close()
  637. }
  638. do {
  639. try text_message.write(to: file_message, atomically: true, encoding: .utf8)
  640. }
  641. catch {print(error)}
  642. }
  643. //Make File UC_LIST
  644. let file_uc_list = documentDirectoryUrl.appendingPathComponent("UC_LIST").appendingPathExtension("")
  645. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(MESSAGE_SUMMARY)") {
  646. var text_uc_list = ""
  647. while tableInfo.next() {
  648. if tableInfo.string(forColumn: "name")! == "counter" {
  649. continue
  650. }
  651. if text_uc_list.isEmpty {
  652. text_uc_list.append(tableInfo.string(forColumn: "name")! == "l_pin" ? "opposite" : tableInfo.string(forColumn: "name")!)
  653. } else {
  654. text_uc_list.append(separator)
  655. text_uc_list.append(tableInfo.string(forColumn: "name")! == "l_pin" ? "opposite" : tableInfo.string(forColumn: "name")!)
  656. }
  657. }
  658. text_uc_list.append("\n")
  659. tableInfo.close()
  660. if let cursorData = Database.shared.getRecords(fmdb: fmdb,query: "SELECT * FROM MESSAGE_SUMMARY") {
  661. let columnCount = cursorData.columnCount
  662. var value_m = ""
  663. while cursorData.next() {
  664. for i in 0..<columnCount - 1 {
  665. value_m.append(cursorData.string(forColumnIndex: i) == nil ? "null" : cursorData.string(forColumnIndex: i)!.isEmpty ? "<empty>" : cursorData.string(forColumnIndex: i)!)
  666. value_m.append(separator)
  667. }
  668. value_m.append("\n")
  669. recordSize += 1
  670. }
  671. text_uc_list.append(value_m)
  672. cursorData.close()
  673. }
  674. do {
  675. try text_uc_list.write(to: file_uc_list, atomically: true, encoding: .utf8)
  676. }
  677. catch {print(error)}
  678. }
  679. //Make File FORM_DATA
  680. let file_form_data = documentDirectoryUrl.appendingPathComponent("FORM_DATA").appendingPathExtension("")
  681. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(FORM_DATA)") {
  682. var text_form_data = ""
  683. while tableInfo.next() {
  684. if text_form_data.isEmpty {
  685. text_form_data.append(tableInfo.string(forColumn: "name")!)
  686. } else {
  687. text_form_data.append(separator)
  688. text_form_data.append(tableInfo.string(forColumn: "name")!)
  689. }
  690. }
  691. text_form_data.append("\n")
  692. tableInfo.close()
  693. if let cursorData = Database.shared.getRecords(fmdb: fmdb,query: "SELECT * FROM FORM_DATA") {
  694. let columnCount = cursorData.columnCount
  695. var value_m = ""
  696. while cursorData.next() {
  697. for i in 0..<columnCount - 1 {
  698. value_m.append(cursorData.string(forColumnIndex: i) == nil ? "null" : cursorData.string(forColumnIndex: i)!.isEmpty ? "<empty>" : cursorData.string(forColumnIndex: i)!)
  699. value_m.append(separator)
  700. }
  701. value_m.append("\n")
  702. recordSize += 1
  703. }
  704. text_form_data.append(value_m)
  705. cursorData.close()
  706. }
  707. do {
  708. try text_form_data.write(to: file_form_data, atomically: true, encoding: .utf8)
  709. }
  710. catch {print(error)}
  711. }
  712. //Make File TASK_PIC
  713. let file_task_pic = documentDirectoryUrl.appendingPathComponent("TASK_PIC").appendingPathExtension("")
  714. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(TASK_PIC)") {
  715. var text_task_pic = ""
  716. while tableInfo.next() {
  717. if text_task_pic.isEmpty {
  718. text_task_pic.append(tableInfo.string(forColumn: "name")!)
  719. } else {
  720. text_task_pic.append(separator)
  721. text_task_pic.append(tableInfo.string(forColumn: "name")!)
  722. }
  723. }
  724. text_task_pic.append("\n")
  725. tableInfo.close()
  726. if let cursorData = Database.shared.getRecords(fmdb: fmdb,query: "SELECT * FROM TASK_PIC") {
  727. let columnCount = cursorData.columnCount
  728. var value_m = ""
  729. while cursorData.next() {
  730. for i in 0..<columnCount - 1 {
  731. value_m.append(cursorData.string(forColumnIndex: i) == nil ? "null" : cursorData.string(forColumnIndex: i)!.isEmpty ? "<empty>" : cursorData.string(forColumnIndex: i)!)
  732. value_m.append(separator)
  733. }
  734. value_m.append("\n")
  735. recordSize += 1
  736. }
  737. text_task_pic.append(value_m)
  738. cursorData.close()
  739. }
  740. do {
  741. try text_task_pic.write(to: file_task_pic, atomically: true, encoding: .utf8)
  742. }
  743. catch {print(error)}
  744. }
  745. //Make File TASK_DETAIL
  746. let file_task_detail = documentDirectoryUrl.appendingPathComponent("TASK_DETAIL").appendingPathExtension("")
  747. if let tableInfo = Database.shared.getRecords(fmdb: fmdb,query: "PRAGMA table_info(TASK_DETAIL)") {
  748. var text_task_detail = ""
  749. while tableInfo.next() {
  750. if text_task_detail.isEmpty {
  751. text_task_detail.append(tableInfo.string(forColumn: "name")!)
  752. } else {
  753. text_task_detail.append(separator)
  754. text_task_detail.append(tableInfo.string(forColumn: "name")!)
  755. }
  756. }
  757. text_task_detail.append("\n")
  758. tableInfo.close()
  759. if let cursorData = Database.shared.getRecords(fmdb: fmdb,query: "SELECT * FROM TASK_DETAIL") {
  760. let columnCount = cursorData.columnCount
  761. var value_m = ""
  762. while cursorData.next() {
  763. for i in 0..<columnCount - 1 {
  764. value_m.append(cursorData.string(forColumnIndex: i) == nil ? "null" : cursorData.string(forColumnIndex: i)!.isEmpty ? "<empty>" : cursorData.string(forColumnIndex: i)!)
  765. value_m.append(separator)
  766. }
  767. value_m.append("\n")
  768. recordSize += 1
  769. }
  770. text_task_detail.append(value_m)
  771. cursorData.close()
  772. }
  773. do {
  774. try text_task_detail.write(to: file_task_detail, atomically: true, encoding: .utf8)
  775. }
  776. catch {print(error)}
  777. }
  778. //ZIP ALL FILES
  779. let fileManager = FileManager()
  780. var destinationURL = documentDirectoryUrl
  781. destinationURL.appendPathComponent("zipItem\(Date().currentTimeMillis())")
  782. let listFiles: [URL] = [file_message, file_uc_list, file_form_data, file_task_pic, file_task_detail]
  783. do {
  784. try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
  785. let zipFiles = destinationURL.appendingPathComponent(getFileName(option: choosenOption, fileId: fileIdBackup, withoutZIP: true)).appendingPathExtension("zip")
  786. // try Zip.zipFiles(paths: listFiles, zipFilePath: zipFiles, password: nil, progress: {progress in
  787. // self.labelPreparing.text = "Preparing...".localized() + " \(progress * 100)%"
  788. // })
  789. self.labelPreparing.text = "Uploading...".localized()
  790. Network().upload(fileUrl: zipFiles, completion: { result,progress in
  791. if result {
  792. DispatchQueue.main.async { [self] in
  793. labelPreparing.text = "Uploading...".localized() + " \(progress)%"
  794. if progress == 100 {
  795. do {
  796. let path = zipFiles.path
  797. let attrib = try FileManager.default.attributesOfItem(atPath: path)
  798. let fileSize = attrib[.size] as! Int64
  799. DispatchQueue.global().async { [self] in
  800. _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupUploaded(option: choosenOption, fileid: fileIdBackup, filesize: String(fileSize), recordSize: String(recordSize)))
  801. }
  802. let date = Date()
  803. let calendar = Calendar.current
  804. if (calendar.isDateInToday(date)) {
  805. dayLastBackup = "Today".localized()
  806. } else {
  807. let startOfNow = calendar.startOfDay(for: Date())
  808. let startOfTimeStamp = calendar.startOfDay(for: date)
  809. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  810. let day = -(components.day!)
  811. if day == 1{
  812. dayLastBackup = "Yesterday".localized()
  813. } else {
  814. let formatter = DateFormatter()
  815. formatter.dateFormat = "dd MMMM yyyy"
  816. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  817. let stringFormat = formatter.string(from: date as Date)
  818. dayLastBackup = stringFormat
  819. }
  820. }
  821. let formatter = DateFormatter()
  822. formatter.dateFormat = "HH:mm"
  823. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  824. timeLastBackup = formatter.string(from: date as Date)
  825. valueLastBackup = dayLastBackup.localized() + ", " + timeLastBackup
  826. valuesizeBackup = Units(bytes: fileSize).getReadableUnit()
  827. labelPreparing.text = "Successfully Backup Data".localized()
  828. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
  829. isBackupStart = false
  830. tableView.beginUpdates()
  831. tableView.reloadRows(at: [indexPath, IndexPath(row: 0, section: 0)], with: .none)
  832. tableView.reloadSections(IndexSet(integer: 2), with: .none)
  833. tableView.endUpdates()
  834. })
  835. } catch {}
  836. }
  837. }
  838. }
  839. })
  840. } catch {print(error)}
  841. })
  842. }
  843. }
  844. extension String {
  845. func appendLineToURL(fileURL: URL) throws {
  846. try (self + "\n").appendToURL(fileURL: fileURL)
  847. }
  848. func appendToURL(fileURL: URL) throws {
  849. let data = self.data(using: String.Encoding.utf8)!
  850. try data.append(fileURL: fileURL)
  851. }
  852. }
  853. extension Data {
  854. func append(fileURL: URL) throws {
  855. if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
  856. defer {
  857. fileHandle.closeFile()
  858. }
  859. fileHandle.seekToEndOfFile()
  860. fileHandle.write(self)
  861. }
  862. else {
  863. try write(to: fileURL, options: .atomic)
  864. }
  865. }
  866. }