forked from merlos/iOS-Open-GPX-Tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCoreDataHelper.swift
800 lines (650 loc) · 34.4 KB
/
CoreDataHelper.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
//
// CoreDataHelper.swift
// OpenGpxTracker
//
// Created by Vincent on 9/4/19.
//
import UIKit
import CoreData
import CoreGPX
/// Core Data implementation. As all Core Data related logic is contained here, I considered it as a helper.
///
/// Implementation learnt / inspired
/// from 4 part series:
/// https://marcosantadev.com/coredata_crud_concurrency_swift_1/
///
class CoreDataHelper {
// MARK:- IDs
// ids to keep track of object's sequence
/// for waypoints
var waypointId = Int64()
/// for trackpoints
var trackpointId = Int64()
/// id to seperate trackpoints in different tracksegements
var tracksegmentId = Int64()
var isContinued = false
var lastTracksegmentId = Int64()
// MARK:- Other Declarations
/// app delegate.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
// arrays for handling retrieval of data when needed.
// recovered tracksegments
var tracksegments = [GPXTrackSegment]()
// recovered current segment
var currentSegment = GPXTrackSegment()
// recovered waypoints, inclusive of waypoints from previous file if file is loaded on recovery.
var waypoints = [GPXWaypoint]()
// last file name of the recovered file, if the recovered file was a continuation.
var lastFileName = String()
// MARK:- Add to Core Data
/// Adds the last file name to Core Data
///
/// - Parameters:
/// - lastFileName: Last file name of the previously logged GPX file.
///
func add(toCoreData lastFileName: String, willContinueAfterSave willContinue: Bool) {
let childManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Creates the link between child and parent
childManagedObjectContext.parent = appDelegate.managedObjectContext
childManagedObjectContext.perform {
let root = NSEntityDescription.insertNewObject(forEntityName: "CDRoot", into: childManagedObjectContext) as! CDRoot
root.lastFileName = lastFileName
root.continuedAfterSave = willContinue
root.lastTrackSegmentId = self.tracksegmentId
do {
try childManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the data from the child to the main context to be stored properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save parent context when adding last file name: \(error)")
}
}
}
catch {
print("Failure to save child context when adding last file name: \(error)")
}
}
}
/// Adds a trackpoint to Core Data
///
/// A track segment ID should also be provided, such that trackpoints would be seperated in their track segments when recovered.
/// - Parameters:
/// - trackpoint: the trackpoint meant to be added to Core Data
/// - Id: track segment ID that the trackpoint originally was in.
///
func add(toCoreData trackpoint: GPXTrackPoint, withTrackSegmentID Id: Int) {
let childManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Creates the link between child and parent
childManagedObjectContext.parent = appDelegate.managedObjectContext
childManagedObjectContext.perform {
print("Core Data Helper: Add trackpoint with id: \(self.trackpointId)")
let pt = NSEntityDescription.insertNewObject(forEntityName: "CDTrackpoint", into: childManagedObjectContext) as! CDTrackpoint
guard let elevation = trackpoint.elevation else { return }
guard let latitude = trackpoint.latitude else { return }
guard let longitude = trackpoint.longitude else { return }
pt.elevation = elevation
pt.latitude = latitude
pt.longitude = longitude
pt.time = trackpoint.time
pt.trackpointId = self.trackpointId
pt.trackSegmentId = Int64(Id)
// Serialization of trackpoint
do {
let serialized = try JSONEncoder().encode(trackpoint)
pt.serialized = serialized
}
catch {
print("Core Data Helper: serialization error when adding trackpoint: \(error)")
}
self.trackpointId += 1
do {
try childManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the data from the child to the main context to be stored properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save parent context when adding trackpoint: \(error)")
}
}
}
catch {
print("Failure to save child context when adding trackpoint: \(error)")
}
}
}
/// Adds a waypoint to Core Data
///
/// - Parameters:
/// - waypoint: the waypoint meant to be added to Core Data
///
func add(toCoreData waypoint: GPXWaypoint) {
let waypointChildManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Creates the link between child and parent
waypointChildManagedObjectContext.parent = appDelegate.managedObjectContext
waypointChildManagedObjectContext.perform {
print("Core Data Helper: Add waypoint with id: \(self.waypointId)")
let pt = NSEntityDescription.insertNewObject(forEntityName: "CDWaypoint", into: waypointChildManagedObjectContext) as! CDWaypoint
guard let latitude = waypoint.latitude else { return }
guard let longitude = waypoint.longitude else { return }
if let elevation = waypoint.elevation {
pt.elevation = elevation
}
else {
pt.elevation = .greatestFiniteMagnitude
}
pt.name = waypoint.name
pt.desc = waypoint.desc
pt.latitude = latitude
pt.longitude = longitude
pt.time = waypoint.time
pt.waypointId = self.waypointId
// Serialization of trackpoint
do {
let serialized = try JSONEncoder().encode(waypoint)
pt.serialized = serialized
}
catch {
print("Core Data Helper: serialization error when adding waypoint: \(error)")
}
self.waypointId += 1
do {
try waypointChildManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the data from the child to the main context to be stored properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save parent context when adding waypoint: \(error)")
}
}
}
catch {
print("Failure to save parent context when adding waypoint: \(error)")
}
}
}
// MARK:- Update Core Data
/// Updates a previously added waypoint to Core Data
///
/// The waypoint at the given index will be updated accordingly.
/// - Parameters:
/// - updatedWaypoint: the waypoint meant to replace a already added, Core Data waypoint.
/// - index: the waypoint that is meant to be replaced/updated to newer data.
///
func update(toCoreData updatedWaypoint: GPXWaypoint, from index: Int) {
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
// Creates a fetch request
let wptFetchRequest = NSFetchRequest<CDWaypoint>(entityName: "CDWaypoint")
let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in
print("Core Data Helper: updating waypoint in Core Data")
// Retrieves an array of points from Core Data
guard let waypointResults = asynchronousFetchResult.finalResult else { return }
privateManagedObjectContext.perform {
let objectID = waypointResults[index].objectID
guard let pt = self.appDelegate.managedObjectContext.object(with: objectID) as? CDWaypoint else { return }
guard let latitude = updatedWaypoint.latitude else { return }
guard let longitude = updatedWaypoint.longitude else { return }
if let elevation = updatedWaypoint.elevation {
pt.elevation = elevation
}
else {
pt.elevation = .greatestFiniteMagnitude
}
pt.name = updatedWaypoint.name
pt.desc = updatedWaypoint.desc
pt.latitude = latitude
pt.longitude = longitude
do {
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to update and save waypoint to parent context: \(error)")
}
}
}
catch {
print("Failure to update and save waypoint to context at child context: \(error)")
}
}
}
do {
try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest)
} catch {
print("NSAsynchronousFetchRequest (for finding updatable waypoint) error: \(error)")
}
}
// MARK:- Retrieval From Core Data
/// Retrieves everything from Core Data
///
/// Currently, it retrieves CDTrackpoint, CDWaypoint and CDRoot, to process from those Core Data types to CoreGPX types such as GPXTrackPoint, GPXWaypoint, etc.
///
/// It will also call on crashFileRecovery() method to continue the next procudure.
///
func retrieveFromCoreData() {
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
// Creates a fetch request
let trkptFetchRequest = NSFetchRequest<CDTrackpoint>(entityName: "CDTrackpoint")
let wptFetchRequest = NSFetchRequest<CDWaypoint>(entityName: "CDWaypoint")
let rootFetchRequest = NSFetchRequest<CDRoot>(entityName: "CDRoot")
// Ensure that fetched data is ordered
let sortTrkpt = NSSortDescriptor(key: "trackpointId", ascending: true)
let sortWpt = NSSortDescriptor(key: "waypointId", ascending: true)
trkptFetchRequest.sortDescriptors = [sortTrkpt]
wptFetchRequest.sortDescriptors = [sortWpt]
let asyncRootFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rootFetchRequest) { asynchronousFetchResult in
guard let rootResults = asynchronousFetchResult.finalResult else {
return }
DispatchQueue.main.async {
guard let objectID = rootResults.last?.objectID else { self.lastFileName = ""; return }
guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDRoot else { self.lastFileName = ""; return }
self.lastFileName = safePoint.lastFileName ?? ""
self.lastTracksegmentId = safePoint.lastTrackSegmentId
self.isContinued = safePoint.continuedAfterSave
}
}
// Creates `asynchronousFetchRequest` with the fetch request and the completion closure
let asynchronousTrackPointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: trkptFetchRequest) { asynchronousFetchResult in
print("Core Data Helper: fetching recoverable trackpoints from Core Data")
guard let trackPointResults = asynchronousFetchResult.finalResult else { return }
// Dispatches to use the data in the main queue
DispatchQueue.main.async {
self.tracksegmentId = trackPointResults.first?.trackSegmentId ?? 0
for result in trackPointResults {
let objectID = result.objectID
// thread safe
guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDTrackpoint else { continue }
if self.tracksegmentId != safePoint.trackSegmentId {
if self.currentSegment.trackpoints.count > 0 {
self.tracksegments.append(self.currentSegment)
self.currentSegment = GPXTrackSegment()
}
self.tracksegmentId = safePoint.trackSegmentId
}
let pt = GPXTrackPoint(latitude: safePoint.latitude, longitude: safePoint.longitude)
pt.time = safePoint.time
pt.elevation = safePoint.elevation
self.currentSegment.trackpoints.append(pt)
}
self.trackpointId = trackPointResults.last?.trackpointId ?? Int64()
self.tracksegments.append(self.currentSegment)
}
}
let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in
print("Core Data Helper: fetching recoverable waypoints from Core Data")
// Retrieves an array of points from Core Data
guard let waypointResults = asynchronousFetchResult.finalResult else { return }
// Dispatches to use the data in the main queue
DispatchQueue.main.async {
for result in waypointResults {
let objectID = result.objectID
// thread safe
guard let safePoint = self.appDelegate.managedObjectContext.object(with: objectID) as? CDWaypoint else { continue }
let pt = GPXWaypoint(latitude: safePoint.latitude, longitude: safePoint.longitude)
pt.time = safePoint.time
pt.desc = safePoint.desc
pt.name = safePoint.name
if safePoint.elevation != .greatestFiniteMagnitude {
pt.elevation = safePoint.elevation
}
self.waypoints.append(pt)
}
self.waypointId = waypointResults.last?.waypointId ?? Int64()
// trackpoint request first, followed by waypoint request
// hence, crashFileRecovery method is ran in this.
self.crashFileRecovery()
print("Core Data Helper: async fetches complete.")
}
}
do {
// Executes two requests, one for trackpoint, one for waypoint.
// Note: it appears that the actual object context execution happens after all of this, probably due to its async nature.
try privateManagedObjectContext.execute(asyncRootFetchRequest)
try privateManagedObjectContext.execute(asynchronousTrackPointFetchRequest)
try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest)
} catch let error {
print("NSAsynchronousFetchRequest (fetch request for recovery) error: \(error)")
}
}
// MARK:- Delete from Core Data
/// Deletes all CDRoot entity objects from Core Data.
///
/// CDRoot holds information needed for core data functionalities other than data storage of trackpoints or waypoints, etc.
///
func deleteCDRootFromCoreData() {
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
// Creates a fetch request
let rootFetchRequest = NSFetchRequest<CDRoot>(entityName: "CDRoot")
let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: rootFetchRequest) { asynchronousFetchResult in
print("Core Data Helper: delete last filename from Core Data.")
// Retrieves an array of points from Core Data
guard let results = asynchronousFetchResult.finalResult else { return }
for result in results {
privateManagedObjectContext.delete(result)
}
do {
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context: \(error)")
}
}
}
catch {
print("Failure to save context at child context: \(error)")
}
}
do {
try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest)
} catch let error {
print("NSAsynchronousFetchRequest (while deleting last file name) error: \(error)")
}
}
/// Delete Waypoint from index
///
/// - Parameters:
/// - index: index of the waypoint that is meant to be deleted.
///
func deleteWaypoint(fromCoreDataAt index: Int) {
lastFileName = String()
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
// Creates a fetch request
let wptFetchRequest = NSFetchRequest<CDWaypoint>(entityName: "CDWaypoint")
let asynchronousWaypointFetchRequest = NSAsynchronousFetchRequest(fetchRequest: wptFetchRequest) { asynchronousFetchResult in
print("Core Data Helper: delete waypoint from Core Data at index: \(index)")
// Retrieves an array of points from Core Data
guard let waypointResults = asynchronousFetchResult.finalResult else { return }
privateManagedObjectContext.delete(waypointResults[index])
do {
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context (when deleting waypoint): \(error)")
}
}
}
catch {
print("Failure to save context at child context (when deleting waypoint): \(error)")
}
}
do {
try privateManagedObjectContext.execute(asynchronousWaypointFetchRequest)
} catch let error {
print("NSAsynchronousFetchRequest (for finding deletable waypoint) error: \(error)")
}
}
/// Delete all trackpoints and waypoints in Core Data.
func deleteAllTrackFromCoreData() {
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
print("Core Data Helper: Batch Delete trackpoints from Core Data")
if #available(iOS 10.0, *) {
privateManagedObjectContext.perform {
do {
let trackpointFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CDTrackpoint")
let trackpointDeleteRequest = NSBatchDeleteRequest(fetchRequest: trackpointFetchRequest)
// execute both delete requests.
try privateManagedObjectContext.execute(trackpointDeleteRequest)
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context after delete: \(error)")
}
}
}
catch {
print("Failed to delete all from core data, error: \(error)")
}
}
}
else { // for pre iOS 9 (less efficient, load in memory before removal)
let trackpointFetchRequest = NSFetchRequest<CDTrackpoint>(entityName: "CDTrackpoint")
let trackpointAsynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: trackpointFetchRequest) { asynchronousFetchResult in
guard let results = asynchronousFetchResult.finalResult else { return }
for result in results {
privateManagedObjectContext.delete(result)
}
do {
// Save delete request
try privateManagedObjectContext.save()
}
catch let error {
print("NSAsynchronousFetchRequest (for batch delete <iOS 9) error in saving: \(error)")
}
}
do {
// Executes all delete requests
try privateManagedObjectContext.execute(trackpointAsynchronousFetchRequest)
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context after delete: \(error)")
}
}
} catch let error {
print("NSAsynchronousFetchRequest (for batch delete <iOS 9) error: \(error)")
}
}
}
/// Delete all trackpoints and waypoints in Core Data.
func deleteAllWaypointsFromCoreData() {
let privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.parent = appDelegate.managedObjectContext
print("Core Data Helper: Batch Delete waypoints from Core Data")
if #available(iOS 10.0, *) {
privateManagedObjectContext.perform {
do {
let waypointFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CDWaypoint")
let waypointDeleteRequest = NSBatchDeleteRequest(fetchRequest: waypointFetchRequest)
// execute delete request.
try privateManagedObjectContext.execute(waypointDeleteRequest)
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context after delete: \(error)")
}
}
}
catch {
print("Failed to delete all from core data, error: \(error)")
}
}
}
else { // for pre iOS 9 (less efficient, load in memory before removal)
let waypointFetchRequest = NSFetchRequest<CDWaypoint>(entityName: "CDWaypoint")
waypointFetchRequest.includesPropertyValues = false
let waypointAsynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: waypointFetchRequest) { asynchronousFetchResult in
guard let results = asynchronousFetchResult.finalResult else { return }
//self.resetIds()
for result in results {
let safePoint = privateManagedObjectContext.object(with: result.objectID)
privateManagedObjectContext.delete(safePoint)
}
do {
// Save delete request
try privateManagedObjectContext.save()
}
catch let error {
print("NSAsynchronousFetchRequest (for batch delete <iOS 9) error in saving: \(error)")
}
}
do {
// Executes all delete requests
try privateManagedObjectContext.execute(waypointAsynchronousFetchRequest)
try privateManagedObjectContext.save()
self.appDelegate.managedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try self.appDelegate.managedObjectContext.save()
} catch {
print("Failure to save context after delete: \(error)")
}
}
} catch let error {
print("NSAsynchronousFetchRequest (for batch delete <iOS 9) error: \(error)")
}
}
}
// MARK:- Handles recovered data
/// Prompts user on what to do with recovered data
///
/// Adds all the 'recovered' content retrieved earlier to newly initialized `GPXRoot`.
/// Deletes and clears core data stuff after user decision is made.
///
/// Currently, there are three user decisions allowed:
/// - To continue last session, which loads the recovered data including previous file data (if applicable) on the map.
/// - To save recovered data silently in background and start a fresh new session immediately.
/// - To delete and ignore recovered data, to start a fresh new session instead.
///
func crashFileRecovery() {
DispatchQueue.global().async {
// checks if trackpoint and waypoint are available
if self.currentSegment.trackpoints.count > 0 || self.waypoints.count > 0 {
let root: GPXRoot
let track = GPXTrack()
// will load file if file was resumed before crash
if self.lastFileName != "" {
let gpx = GPXFileManager.URLForFilename(self.lastFileName)
let parsedRoot = GPXParser(withURL: gpx)?.parsedData()
root = parsedRoot ?? GPXRoot(creator: kGPXCreatorString)
}
else {
root = GPXRoot(creator: kGPXCreatorString)
}
// generates a GPXRoot from recovered data
if self.isContinued && self.tracksegments.count >= (self.lastTracksegmentId + 1) {
//Check if there was a tracksegment
if root.tracks.last?.tracksegments.count == 0 {
root.tracks.last?.add(trackSegment: GPXTrackSegment())
}
// if gpx is saved, but further trkpts are added after save, and crashed, trkpt are appended, not adding to new trkseg.
root.tracks.last?.tracksegments[Int(self.lastTracksegmentId)].add(trackpoints: self.tracksegments.first!.trackpoints)
self.tracksegments.remove(at: 0)
}
else {
track.tracksegments = self.tracksegments
root.add(track: track)
//root.waypoints = self.waypoints
//root.add(waypoints: self.waypoints)
}
//root.waypoints = [GPXWaypoint]()
//root.add(waypoints: self.waypoints)
root.waypoints = self.waypoints
// asks user on what to do with recovered data
DispatchQueue.main.sync {
print(root.gpx())
// main action sheet setup
let alertController = UIAlertController(title: NSLocalizedString("CONTINUE_SESSION_TITLE", comment: "no comment"), message: NSLocalizedString("CONTINUE_SESSION_MESSAGE", comment: "no comment"), preferredStyle: .actionSheet)
// option to cancel
let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL", comment: "no comment"), style: .cancel) { (action) in
self.clearAll()
}
// option to continue previous session, which will load it, but not save
let continueAction = UIAlertAction(title: NSLocalizedString("CONTINUE_SESSION", comment: "no comment"), style: .default) { (action) in
NotificationCenter.default.post(name: .loadRecoveredFile, object: nil, userInfo: ["recoveredRoot" : root, "fileName" : self.lastFileName])
}
// option to save silently as file, session remains new
let saveAction = UIAlertAction(title: NSLocalizedString("SAVE_START_NEW", comment: "no comment"), style: .default) { (action) in
self.saveFile(from: root, andIfAvailable: self.lastFileName)
}
alertController.addAction(cancelAction)
alertController.addAction(continueAction)
alertController.addAction(saveAction)
CoreDataAlertView().showActionSheet(alertController)
}
}
else {
// no recovery file will be generated if nothing is recovered (or did not crash).
}
}
}
/// saves recovered data to a gpx file, silently, without loading on map.
func saveFile(from gpx: GPXRoot, andIfAvailable lastfileName: String) {
// date format same as usual.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MMM-yyyy-HHmm"
var fileName = String()
if lastfileName != "" {
fileName = lastfileName
}
else if let lastTrkptDate = gpx.tracks.last?.tracksegments.last?.trackpoints.last?.time {
fileName = dateFormatter.string(from: lastTrkptDate)
}
else {
// File name's date will be as of recovery time, not of crash time.
fileName = dateFormatter.string(from: Date())
}
let recoveredFileName = "recovery-\(fileName)"
let gpxString = gpx.gpx()
// Save the recovered file.
GPXFileManager.save(recoveredFileName, gpxContents: gpxString)
print("File \(recoveredFileName) was recovered from previous session, prior to unexpected crash/exit")
// clear aft save.
self.clearAll()
self.deleteCDRootFromCoreData()
}
// MARK:- Reset & Clear
/// Resets trackpoints and waypoints Id
///
/// the Id is to ensure that when retrieving the entities, the order remains.
/// This is important to ensure that the resulting recovery file has the correct order.
func resetIds() {
self.trackpointId = 0
self.waypointId = 0
self.tracksegmentId = 0
}
/// Clear all arrays and current segment after recovery.
func clearObjects() {
self.tracksegments = []
self.waypoints = []
self.currentSegment = GPXTrackSegment()
}
func clearAllExceptWaypoints() {
// once file recovery is completed, Core Data stored items are deleted.
self.deleteAllTrackFromCoreData()
// once file recovery is completed, arrays are cleared.
self.tracksegments = []
self.currentSegment = GPXTrackSegment()
// current segment should be 'reset' as well
self.currentSegment = GPXTrackSegment()
// reset order sorting ids
self.trackpointId = 0
self.tracksegmentId = 0
}
/// clears all
func clearAll() {
// once file recovery is completed, Core Data stored items are deleted.
self.deleteAllTrackFromCoreData()
self.deleteAllWaypointsFromCoreData()
// once file recovery is completed, arrays are cleared.
self.clearObjects()
// current segment should be 'reset' as well
self.currentSegment = GPXTrackSegment()
// reset order sorting ids
self.resetIds()
}
}