Skip to content

Commit

Permalink
Merge pull request #4574 from wikimedia/T341104
Browse files Browse the repository at this point in the history
Perform deeper database cleanup upon Settings clear cache
  • Loading branch information
tonisevener authored Aug 2, 2023
2 parents b093ebe + 65f4871 commit 828fdfb
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 18 deletions.
6 changes: 3 additions & 3 deletions WMF Framework/SharedContainerCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ public final class SharedContainerCache<T: Codable>: SharedContainerCacheHouseke
}

/// Persist only the last 50 visited talk pages
@objc public static func deleteStaleCachedItems(in subdirectoryPathComponent: String) {
@objc public static func deleteStaleCachedItems(in subdirectoryPathComponent: String, cleanupLevel: WMFCleanupLevel) {
let folderURL = cacheDirectoryContainerURL.appendingPathComponent(subdirectoryPathComponent)

if let urlArray = try? FileManager.default.contentsOfDirectory(at: folderURL,
includingPropertiesForKeys: [.contentModificationDateKey],
options: .skipsHiddenFiles) {
let maxCacheSize = 50
let maxCacheSize = cleanupLevel == .high ? 0 : 50
if urlArray.count > maxCacheSize {
let sortedArray = urlArray.map { url in
(url, (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast)
Expand All @@ -80,7 +80,7 @@ public final class SharedContainerCache<T: Codable>: SharedContainerCacheHouseke
}

@objc public protocol SharedContainerCacheHousekeepingProtocol: AnyObject {
static func deleteStaleCachedItems(in subdirectoryPathComponent: String)
static func deleteStaleCachedItems(in subdirectoryPathComponent: String, cleanupLevel: WMFCleanupLevel)
}

@objc public class SharedContainerCacheClearFeaturedArticleWrapper: NSObject {
Expand Down
6 changes: 6 additions & 0 deletions WMF Framework/WMFCleanLevel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

@objc public enum WMFCleanupLevel: Int {
case low
case high
}
4 changes: 4 additions & 0 deletions Wikipedia.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@
6782DC182347EE59003FA21B /* DiffListChangeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6782DC162347EE59003FA21B /* DiffListChangeCell.xib */; };
6782DC192347EE59003FA21B /* DiffListChangeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6782DC162347EE59003FA21B /* DiffListChangeCell.xib */; };
6782DC1A2347EE5A003FA21B /* DiffListChangeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6782DC162347EE59003FA21B /* DiffListChangeCell.xib */; };
6785FCC92A66D8DB0078FAF2 /* WMFCleanLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6785FCC82A66D8DB0078FAF2 /* WMFCleanLevel.swift */; };
6785FCCB2A6840880078FAF2 /* WatchlistFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6785FCCA2A6840880078FAF2 /* WatchlistFunnel.swift */; };
6785FCCC2A6840880078FAF2 /* WatchlistFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6785FCCA2A6840880078FAF2 /* WatchlistFunnel.swift */; };
6785FCCD2A6840880078FAF2 /* WatchlistFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6785FCCA2A6840880078FAF2 /* WatchlistFunnel.swift */; };
Expand Down Expand Up @@ -4130,6 +4131,7 @@
6782DBEF23453799003FA21B /* DiffHeaderCompareView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiffHeaderCompareView.xib; sourceTree = "<group>"; };
6782DC102346920B003FA21B /* DiffContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffContainerViewModel.swift; sourceTree = "<group>"; };
6782DC162347EE59003FA21B /* DiffListChangeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DiffListChangeCell.xib; sourceTree = "<group>"; };
6785FCC82A66D8DB0078FAF2 /* WMFCleanLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMFCleanLevel.swift; sourceTree = "<group>"; };
6785FCCA2A6840880078FAF2 /* WatchlistFunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistFunnel.swift; sourceTree = "<group>"; };
678C7C2923BE67F0001AC4D5 /* CacheController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheController.swift; sourceTree = "<group>"; };
678C7C2D23BE705C001AC4D5 /* CacheDBWriting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheDBWriting.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -9345,6 +9347,7 @@
678C7C2823BE6766001AC4D5 /* Cache */,
D8CC94D721789763007293E7 /* Wikipedia */,
D8CC94D9217897FB007293E7 /* NSManagedObject+Extensions.swift */,
6785FCC82A66D8DB0078FAF2 /* WMFCleanLevel.swift */,
);
name = "Core Data";
sourceTree = "<group>";
Expand Down Expand Up @@ -12000,6 +12003,7 @@
D8FA18D11E1BD891009675C3 /* WMFMath.m in Sources */,
678F512B23A7EE6600CE5357 /* ArticleFetcher.swift in Sources */,
0042808925E6E395004945B3 /* MTLModel.m in Sources */,
6785FCC92A66D8DB0078FAF2 /* WMFCleanLevel.swift in Sources */,
0042807B25E6E395004945B3 /* MTLModel+NSCoding.m in Sources */,
D8FA18B41E1BD891009675C3 /* NSCalendar+WMFCommonCalendars.m in Sources */,
B085536C2399E368002100F8 /* UIAccessibility+Grouping.swift in Sources */,
Expand Down
8 changes: 7 additions & 1 deletion Wikipedia/Code/ExploreViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo
NotificationCenter.default.addObserver(self, selector: #selector(articleDeleted(_:)), name: NSNotification.Name.WMFArticleDeleted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(pushNotificationBannerDidDisplayInForeground(_:)), name: .pushNotificationBannerDidDisplayInForeground, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(viewContextDidReset(_:)), name: NSNotification.Name.WMFViewContextDidReset, object: nil)

NotificationCenter.default.addObserver(self, selector: #selector(databaseHousekeeperDidComplete), name: .databaseHousekeeperDidComplete, object: nil)
#if UI_TEST
if UserDefaults.standard.wmf_isFastlaneSnapshotInProgress() {
collectionView.decelerationRate = .fast
Expand Down Expand Up @@ -100,6 +100,12 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo
stopMonitoringReachability()
isGranularUpdatingEnabled = false
}

@objc private func databaseHousekeeperDidComplete() {
DispatchQueue.main.async {
self.refresh()
}
}

@objc func updateNotificationsCenterButton() {
if self.dataStore.authenticationManager.isLoggedIn {
Expand Down
2 changes: 2 additions & 0 deletions Wikipedia/Code/Notification+NotificationsCenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import Foundation

extension Notification.Name {
static let pushNotificationBannerDidDisplayInForeground = Notification.Name("WMFPushNotificationBannerDidDisplayInForeground")
static let databaseHousekeeperDidComplete = Notification.Name("WMFDatabaseHousekeeperDidComplete")
}

@objc extension NSNotification {
public static let pushNotificationBannerDidDisplayInForeground = Notification.Name.pushNotificationBannerDidDisplayInForeground
public static let databaseHousekeeperDidComplete = Notification.Name.databaseHousekeeperDidComplete
}
6 changes: 2 additions & 4 deletions Wikipedia/Code/SharedContainerCacheHousekeeping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import Foundation
import WMF

@objc public class SharedContainerCacheHousekeeping: NSObject, SharedContainerCacheHousekeepingProtocol {
public static func deleteStaleCachedItems(in subdirectoryPathComponent: String) {
SharedContainerCache<TalkPageCache>.deleteStaleCachedItems(in: SharedContainerCacheCommonNames.talkPageCache)
public static func deleteStaleCachedItems(in subdirectoryPathComponent: String, cleanupLevel: WMFCleanupLevel) {
SharedContainerCache<TalkPageCache>.deleteStaleCachedItems(in: subdirectoryPathComponent, cleanupLevel: cleanupLevel)
}


}
4 changes: 2 additions & 2 deletions Wikipedia/Code/WMFAppViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -616,14 +616,14 @@ - (void)performDatabaseHousekeepingWithCompletion:(void (^)(NSError *))completio
WMFDatabaseHousekeeper *housekeeper = [WMFDatabaseHousekeeper new];

NSError *housekeepingError = nil;
[housekeeper performHousekeepingOnManagedObjectContext:self.dataStore.viewContext navigationStateController:self.navigationStateController error:&housekeepingError];
[housekeeper performHousekeepingOnManagedObjectContext:self.dataStore.viewContext navigationStateController:self.navigationStateController cleanupLevel:WMFCleanupLevelLow error:&housekeepingError];
if (housekeepingError) {
DDLogError(@"Error on cleanup: %@", housekeepingError);
housekeepingError = nil;
}

/// Housekeeping for the new talk page cache
[SharedContainerCacheHousekeeping deleteStaleCachedItemsIn:SharedContainerCacheCommonNames.talkPageCache];
[SharedContainerCacheHousekeeping deleteStaleCachedItemsIn:SharedContainerCacheCommonNames.talkPageCache cleanupLevel:WMFCleanupLevelLow];

completion(housekeepingError);
}
Expand Down
27 changes: 20 additions & 7 deletions Wikipedia/Code/WMFDatabaseHousekeeper.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import Foundation
import WMF

@objc class WMFDatabaseHousekeeper : NSObject {

// Returns deleted URLs
@discardableResult @objc func performHousekeepingOnManagedObjectContext(_ moc: NSManagedObjectContext, navigationStateController: NavigationStateController) throws -> [URL] {
// Note: A cleanupLevel of high will attempt to do additional scrubbing in the case of vandalism.
@discardableResult @objc func performHousekeepingOnManagedObjectContext(_ moc: NSManagedObjectContext, navigationStateController: NavigationStateController, cleanupLevel: WMFCleanupLevel) throws -> [URL] {

let urls = try deleteStaleUnreferencedArticles(moc, navigationStateController: navigationStateController)
if cleanupLevel == .high {
moc.navigationState = nil
}

let urls = try deleteStaleUnreferencedArticles(moc, navigationStateController: navigationStateController, cleanupLevel: cleanupLevel)

try deleteStaleAnnouncements(moc)

Expand All @@ -31,16 +37,17 @@ import Foundation
}
}

private func deleteStaleUnreferencedArticles(_ moc: NSManagedObjectContext, navigationStateController: NavigationStateController) throws -> [URL] {
private func deleteStaleUnreferencedArticles(_ moc: NSManagedObjectContext, navigationStateController: NavigationStateController, cleanupLevel: WMFCleanupLevel = .low) throws -> [URL] {

/**
Find `WMFContentGroup`s more than WMFExploreFeedMaximumNumberOfDays days old.
*/

let finalMaxDays = cleanupLevel == .high ? 0 : WMFExploreFeedMaximumNumberOfDays
let today = Date() as NSDate
guard let oldestFeedDateMidnightUTC = today.wmf_midnightUTCDateFromLocalDate(byAddingDays: 0 - WMFExploreFeedMaximumNumberOfDays) else {
guard let oldestFeedDateMidnightUTC = today.wmf_midnightUTCDateFromLocalDate(byAddingDays: 0 - finalMaxDays) else {
assertionFailure("Calculating midnight UTC on the oldest feed date failed")
return []
}
Expand All @@ -51,7 +58,11 @@ import Foundation
var referencedArticleKeys = Set<String>(minimumCapacity: allContentGroups.count * 5 + 1)

for group in allContentGroups {
if group.midnightUTCDate?.compare(oldestFeedDateMidnightUTC) == .orderedAscending {
if cleanupLevel == .high && (group.midnightUTCDate?.compare(oldestFeedDateMidnightUTC) == .orderedAscending ||
group.midnightUTCDate?.compare(oldestFeedDateMidnightUTC) == .orderedSame) {
moc.delete(group)
continue
} else if group.midnightUTCDate?.compare(oldestFeedDateMidnightUTC) == .orderedAscending {
moc.delete(group)
continue
}
Expand Down Expand Up @@ -128,7 +139,7 @@ import Foundation

let articlesToDeleteFetchRequest = WMFArticle.fetchRequest()
// savedDate == NULL && isDownloaded == YES will be picked up by SavedArticlesFetcher for deletion
let articlesToDeletePredicate = NSPredicate(format: "viewedDate == NULL && savedDate == NULL && isDownloaded == NO && placesSortOrder == 0 && isExcludedFromFeed == NO")
let articlesToDeletePredicate = cleanupLevel == .high ? NSPredicate(format: "savedDate == NULL && isDownloaded == NO && placesSortOrder == 0 && isExcludedFromFeed == NO") : NSPredicate(format: "viewedDate == NULL && savedDate == NULL && isDownloaded == NO && placesSortOrder == 0 && isExcludedFromFeed == NO")

if let preservedArticleKeys = navigationStateController.allPreservedArticleKeys(in: moc) {
referencedArticleKeys.formUnion(preservedArticleKeys)
Expand All @@ -141,7 +152,7 @@ import Foundation

var urls: [URL] = []
for obj in articlesToDelete {
guard obj.isFault else { // only delete articles that are faults. prevents deletion of articles that are being actively viewed. repro steps: open disambiguation pages view -> exit app -> re-enter app
guard cleanupLevel != .high && obj.isFault else { // only delete articles that are faults. prevents deletion of articles that are being actively viewed. repro steps: open disambiguation pages view -> exit app -> re-enter app
continue
}

Expand All @@ -160,6 +171,8 @@ import Foundation
try moc.save()
}

NotificationCenter.default.post(name: .databaseHousekeeperDidComplete, object: nil)

return urls
}
}
34 changes: 33 additions & 1 deletion Wikipedia/Code/WMFSettingsViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -382,16 +382,48 @@ - (void)showClearCacheActionSheet {
message = [NSString localizedStringWithFormat:message, bytesString];

UIAlertController *sheet = [UIAlertController alertControllerWithTitle:WMFLocalizedStringWithDefaultValue(@"settings-clear-cache-are-you-sure-title", nil, nil, @"Clear cached data?", @"Title for the confirmation presented to the user to verify they are sure they want to clear clear cached data.") message:message preferredStyle:UIAlertControllerStyleAlert];
typeof(self) __weak weakSelf = self;
[sheet addAction:[UIAlertAction actionWithTitle:WMFLocalizedStringWithDefaultValue(@"settings-clear-cache-ok", nil, nil, @"Clear cache", @"Confirm action to clear cached data")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[self.dataStore clearTemporaryCache];
[weakSelf clearCache];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:WMFLocalizedStringWithDefaultValue(@"settings-clear-cache-cancel", nil, nil, @"Cancel", @"Cancel action to clear cached data {{Identical|Cancel}}") style:UIAlertActionStyleCancel handler:NULL]];

[self presentViewController:sheet animated:YES completion:NULL];
}

- (void)clearCache {

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showClearCacheInProgressBanner) object:nil];
[self performSelector:@selector(showClearCacheInProgressBanner) withObject:nil afterDelay:1.0];

[self.dataStore clearTemporaryCache];

WMFDatabaseHousekeeper *databaseHousekeeper = [WMFDatabaseHousekeeper new];
WMFNavigationStateController *navigationStateController = [[WMFNavigationStateController alloc] initWithDataStore:self.dataStore];

[self.dataStore performBackgroundCoreDataOperationOnATemporaryContext:^(NSManagedObjectContext * _Nonnull moc) {
NSError *housekeepingError = nil;
[databaseHousekeeper performHousekeepingOnManagedObjectContext:moc navigationStateController:navigationStateController cleanupLevel:WMFCleanupLevelHigh error:&housekeepingError];
if (housekeepingError) {
DDLogError(@"Error on cleanup: %@", housekeepingError);
housekeepingError = nil;
}

dispatch_async(dispatch_get_main_queue(), ^{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showClearCacheInProgressBanner) object:nil];
[[WMFAlertManager sharedInstance] showAlert:WMFLocalizedStringWithDefaultValue(@"clearing-cache-complete", nil, nil, @"Clearing cache complete.", @"Title of banner that appears after clearing cache completes. Clearing cache is a button triggered by the user in Settings.") sticky:NO dismissPreviousAlerts:YES tapCallBack:nil];
});
}];

[SharedContainerCacheHousekeeping deleteStaleCachedItemsIn:SharedContainerCacheCommonNames.talkPageCache cleanupLevel:WMFCleanupLevelHigh];
}

- (void)showClearCacheInProgressBanner {
[[WMFAlertManager sharedInstance] showAlert:WMFLocalizedStringWithDefaultValue(@"clearing-cache-in-progress", nil, nil, @"Clearing cache in progress.", @"Title of banner that appears when a user taps clear cache button in Settings. Informs the user that clearing of cache is in progress.") sticky:NO dismissPreviousAlerts:YES tapCallBack:nil];
}

- (void)logout {
[self wmf_showKeepSavedArticlesOnDevicePanelIfNeededTriggeredBy:KeepSavedArticlesTriggerLogout
theme:self.theme
Expand Down
2 changes: 2 additions & 0 deletions Wikipedia/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@
"chinese-variants-alert-body" = "The Wikipedia app now supports the following Chinese variants as primary or secondary languages within the app, making it easier to read, search and edit in your preferred variants:\n\n简体 Chinese, Simplified (zh-hans)\n香港繁體 Hong Kong Traditional (zh-hk)\n澳門繁體 Macau Traditional (zh-mo)\n大马简体 Malaysia Simplified (zh-my)\n新加坡简体 Singapore Simplified (zh-sg)\n臺灣正體 Taiwanese Traditional (zh-tw)";
"chinese-variants-alert-title" = "Updates to Chinese variant support";
"clear-title-accessibility-label" = "Clear";
"clearing-cache-complete" = "Clearing cache complete.";
"clearing-cache-in-progress" = "Clearing cache in progress.";
"close-button-accessibility-label" = "Close";
"compass-direction" = "at $1 o'clock";
"continue-reading-empty-description" = "Explore Wikipedia for more articles to read";
Expand Down
2 changes: 2 additions & 0 deletions Wikipedia/Localizations/qqq.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@
"chinese-variants-alert-body" = "Body text of alert used to inform users about Chinese variant support. Please do not translate the newlines (\n) or Chinese characters (简体, 繁體, etc.).";
"chinese-variants-alert-title" = "Title of alert used to inform users about Chinese variant support.";
"clear-title-accessibility-label" = "Accessibility label title for action that clears text";
"clearing-cache-complete" = "Title of banner that appears after clearing cache completes. Clearing cache is a button triggered by the user in Settings.";
"clearing-cache-in-progress" = "Title of banner that appears when a user taps clear cache button in Settings. Informs the user that clearing of cache is in progress.";
"close-button-accessibility-label" = "Accessibility label for a button that closes a dialog. {{Identical|Close}}";
"compass-direction" = "Spoken description of compass direction, e.g. \"at 3 o'clock\" means \"to the right\", \"at 11 o'clock\" means \"slightly to the left\", etc. $1 is the hour.";
"continue-reading-empty-description" = "Explore Wikipedia for more articles to read";
Expand Down

0 comments on commit 828fdfb

Please sign in to comment.