| ... | @@ -629,7 +629,7 @@ private struct Weak<T: AnyObject> { weak var value: T? } | ... | @@ -629,7 +629,7 @@ private struct Weak<T: AnyObject> { weak var value: T? } |
| 629 | | 629 | |
| 630 | /// A section open for editing: pages listed and saved through its replica. | 630 | /// A section open for editing: pages listed and saved through its replica. |
| 631 | final class Section { | 631 | final class Section { |
| 632 | /// Sent on the main thread with `flags` from `sb_section_poll` after each poll. | 632 | /// Sent on the main thread with `flags` from `sb_section_poll`, `listed` once the rows are read again. |
| 633 | static let changed = Notification.Name("SectionChanged") | 633 | static let changed = Notification.Name("SectionChanged") |
| 634 | static let listed: UInt32 = 1 | 634 | static let listed: UInt32 = 1 |
| 635 | static let remote: UInt32 = 2 | 635 | static let remote: UInt32 = 2 |
| ... | @@ -642,17 +642,18 @@ final class Section { | ... | @@ -642,17 +642,18 @@ final class Section { |
| 642 | let tab: Tab | 642 | let tab: Tab |
| 643 | let handle: OpaquePointer | 643 | let handle: OpaquePointer |
| 644 | private let token: Int | 644 | private let token: Int |
| 645 | private(set) var rows: [Row] = [] | 645 | private(set) var rows: [Row] |
| 646 | private(set) var status = SaveStatus.unknown | 646 | private(set) var status = SaveStatus.unknown |
| | 647 | private var listing: (invalidated: Bool, completions: [() -> Void])? |
| 647 | | 648 | |
| 648 | static var all: [Section] { open.values.compactMap(\.value) } | 649 | static var all: [Section] { open.values.compactMap(\.value) } |
| 649 | | 650 | |
| 650 | private init(notebook: Notebook, tab: Tab, handle: OpaquePointer, token: Int) { | 651 | private init(notebook: Notebook, tab: Tab, handle: OpaquePointer, token: Int, rows: [Row]) { |
| 651 | self.notebook = notebook | 652 | self.notebook = notebook |
| 652 | self.tab = tab | 653 | self.tab = tab |
| 653 | self.handle = handle | 654 | self.handle = handle |
| 654 | self.token = token | 655 | self.token = token |
| 655 | reloadRows() | 656 | self.rows = rows |
| 656 | } | 657 | } |
| 657 | | 658 | |
| 658 | deinit { | 659 | deinit { |
| ... | @@ -671,21 +672,53 @@ final class Section { | ... | @@ -671,21 +672,53 @@ final class Section { |
| 671 | let token = tokens | 672 | let token = tokens |
| 672 | let pointer = Int(bitPattern: library) | 673 | let pointer = Int(bitPattern: library) |
| 673 | let author = Author.name ?? "" | 674 | let author = Author.name ?? "" |
| 674 | background({ () -> (Int, String?) in | 675 | background({ () -> (Int, String?, [Row]) in |
| 675 | var error: UnsafeMutablePointer<CChar>? | 676 | var error: UnsafeMutablePointer<CChar>? |
| 676 | let handle = sb_section_open( | 677 | let handle = sb_section_open( |
| 677 | OpaquePointer(bitPattern: pointer), tab.path, author, wakeSection, UInt(token), &error) | 678 | OpaquePointer(bitPattern: pointer), tab.path, author, wakeSection, UInt(token), &error) |
| 678 | return (Int(bitPattern: handle), take(error)) | 679 | let rows = handle.flatMap { decode([Row].self, sb_section_pages($0)) } ?? [] |
| 679 | }) { handle, error in | 680 | return (Int(bitPattern: handle), take(error), rows) |
| | 681 | }) { handle, error, rows in |
| 680 | guard let handle = OpaquePointer(bitPattern: handle) else { return done(nil, error) } | 682 | guard let handle = OpaquePointer(bitPattern: handle) else { return done(nil, error) } |
| 681 | let section = Section(notebook: notebook, tab: tab, handle: handle, token: token) | 683 | let section = Section(notebook: notebook, tab: tab, handle: handle, token: token, rows: rows) |
| 682 | open[token] = Weak(value: section) | 684 | open[token] = Weak(value: section) |
| 683 | done(section, nil) | 685 | done(section, nil) |
| | 686 | // Wakes before `open[token]` was set went unheard. |
| | 687 | section.poll() |
| 684 | } | 688 | } |
| 685 | } | 689 | } |
| 686 | | 690 | |
| | 691 | /// Lists the pages again and tells the views. |
| 687 | func reloadRows() { | 692 | func reloadRows() { |
| | 693 | listing?.invalidated = true |
| 688 | rows = decode([Row].self, sb_section_pages(handle)) ?? rows | 694 | rows = decode([Row].self, sb_section_pages(handle)) ?? rows |
| | 695 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": Self.listed]) |
| | 696 | } |
| | 697 | |
| | 698 | /// Lists the pages again off the main thread, tells the views, then runs `then`. |
| | 699 | private func refreshRows(then: @escaping () -> Void = {}) { |
| | 700 | guard listing == nil else { |
| | 701 | listing?.invalidated = true |
| | 702 | listing?.completions.append(then) |
| | 703 | return |
| | 704 | } |
| | 705 | listing = (false, [then]) |
| | 706 | readRows() |
| | 707 | } |
| | 708 | |
| | 709 | private func readRows() { |
| | 710 | background({ [self] in decode([Row].self, sb_section_pages(handle)) }) { [self] rows in |
| | 711 | // A read begun before a reload or retitle would undo it. |
| | 712 | if listing!.invalidated, rows != nil { |
| | 713 | listing?.invalidated = false |
| | 714 | return readRows() |
| | 715 | } |
| | 716 | self.rows = rows ?? self.rows |
| | 717 | let completions = listing!.completions |
| | 718 | listing = nil |
| | 719 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": Self.listed]) |
| | 720 | for completion in completions { completion() } |
| | 721 | } |
| 689 | } | 722 | } |
| 690 | | 723 | |
| 691 | /// The row listing page or conflict page `id`, and the version when it is one. | 724 | /// The row listing page or conflict page `id`, and the version when it is one. |
| ... | @@ -701,17 +734,19 @@ final class Section { | ... | @@ -701,17 +734,19 @@ final class Section { |
| 701 | var status: UInt8 = 0 | 734 | var status: UInt8 = 0 |
| 702 | let flags = sb_section_poll(handle, &status) | 735 | let flags = sb_section_poll(handle, &status) |
| 703 | let changed = SaveStatus(rawValue: status) ?? .unknown | 736 | let changed = SaveStatus(rawValue: status) ?? .unknown |
| 704 | let unchanged = flags == 0 && changed == self.status | 737 | if flags & Self.listed != 0 { refreshRows() } |
| | 738 | let unlisted = flags & ~Self.listed |
| | 739 | let unchanged = unlisted == 0 && changed == self.status |
| 705 | self.status = changed | 740 | self.status = changed |
| 706 | if flags & Self.listed != 0 { reloadRows() } | | |
| 707 | NotificationCenter.default.post( | 741 | NotificationCenter.default.post( |
| 708 | name: Self.changed, object: self, | 742 | name: Self.changed, object: self, |
| 709 | userInfo: unchanged ? ["flags": flags, "unchanged": true] : ["flags": flags]) | 743 | userInfo: unchanged ? ["flags": unlisted, "unchanged": true] : ["flags": unlisted]) |
| 710 | } | 744 | } |
| 711 | | 745 | |
| 712 | /// A page's title as typed, shown in the list before the section reports it. | 746 | /// A page's title as typed, shown in the list before the section reports it. |
| 713 | func retitle(_ id: String, _ title: String) { | 747 | func retitle(_ id: String, _ title: String) { |
| 714 | guard let index = rows.firstIndex(where: { $0.id == id }), rows[index].title != title else { return } | 748 | guard let index = rows.firstIndex(where: { $0.id == id }), rows[index].title != title else { return } |
| | 749 | listing?.invalidated = true |
| 715 | rows[index].title = title | 750 | rows[index].title = title |
| 716 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": UInt32(0)]) | 751 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": UInt32(0)]) |
| 717 | } | 752 | } |
| ... | @@ -721,7 +756,6 @@ final class Section { | ... | @@ -721,7 +756,6 @@ final class Section { |
| 721 | let (date, time) = titleDate() | 756 | let (date, time) = titleDate() |
| 722 | let id = take(sb_section_new_page(handle, parent, date, time)) | 757 | let id = take(sb_section_new_page(handle, parent, date, time)) |
| 723 | reloadRows() | 758 | reloadRows() |
| 724 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": Self.listed]) | | |
| 725 | return id | 759 | return id |
| 726 | } | 760 | } |
| 727 | | 761 | |
| ... | @@ -735,13 +769,13 @@ final class Section { | ... | @@ -735,13 +769,13 @@ final class Section { |
| 735 | } | 769 | } |
| 736 | return true | 770 | return true |
| 737 | }) { [self] deleted in | 771 | }) { [self] deleted in |
| 738 | release() | 772 | refreshRows { |
| 739 | reloadRows() | 773 | release() |
| 740 | NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": Self.listed]) | 774 | if deleted { done() } |
| 741 | if deleted { done() } | 775 | else { |
| 742 | else { | 776 | let window = UIApplication.shared.connectedScenes.lazy.compactMap { ($0 as? UIWindowScene)?.keyWindow }.first |
| 743 | let window = UIApplication.shared.connectedScenes.lazy.compactMap { ($0 as? UIWindowScene)?.keyWindow }.first | 777 | window?.rootViewController?.alert("Can’t Delete", "Some pages couldn’t be deleted. Try again.") |
| 744 | window?.rootViewController?.alert("Can’t Delete", "Some pages couldn’t be deleted. Try again.") | 778 | } |
| 745 | } | 779 | } |
| 746 | } | 780 | } |
| 747 | } | 781 | } |