Skip to content

Commit

Permalink
- OCCore+ItemList: trigger re-scan of folders restored to their origi…
Browse files Browse the repository at this point in the history
…nal location, to bring the contained items back to life

- OCCore+ItemUpdates: code cleanup; add safeguard against crash in case removedItem.fileID is nil
- OCQuery: remove items from custom query results that are contained in a folder item that has been removed
- OCDatabase+Scans: add code to find dangling items (items without a parent item) and mark them as removed
- OCDatabase+Schemas: add new schema version running the new scan in OCDatabase+Scans
- OCDatabase: efficiently mark all items in removed folders also as removed; avoid dangling items inside a folder that has been removed (and then purged from the database by a policy, leaving a gap and wrong search results)
  • Loading branch information
felix-schwarz committed Jan 26, 2024
1 parent f873361 commit 0198c70
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 10 deletions.
8 changes: 8 additions & 0 deletions ownCloudSDK.xcodeproj/project.pbxproj
Expand Up @@ -869,6 +869,8 @@
DCF575E027956D84003BEBBA /* OCViewProviderContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575DE27956D84003BEBBA /* OCViewProviderContext.m */; };
DCF95AEA25666FBB00806D2A /* OCClassSetting.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF95AE825666FBB00806D2A /* OCClassSetting.h */; settings = {ATTRIBUTES = (Public, ); }; };
DCF95AEB25666FBB00806D2A /* OCClassSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF95AE925666FBB00806D2A /* OCClassSetting.m */; };
DCF962DD2B5A698500509705 /* OCDatabase+Scans.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF962DB2B5A698500509705 /* OCDatabase+Scans.h */; };
DCF962DE2B5A698500509705 /* OCDatabase+Scans.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF962DC2B5A698500509705 /* OCDatabase+Scans.m */; };
DCFA564E2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFA564C2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h */; settings = {ATTRIBUTES = (Public, ); }; };
DCFA564F2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFA564D2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m */; };
DCFBACF121BAA77F00943F76 /* largePropFindResponse1000.xml in Resources */ = {isa = PBXBuildFile; fileRef = DCFBACF021BAA77F00943F76 /* largePropFindResponse1000.xml */; };
Expand Down Expand Up @@ -1905,6 +1907,8 @@
DCF575DE27956D84003BEBBA /* OCViewProviderContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCViewProviderContext.m; sourceTree = "<group>"; };
DCF95AE825666FBB00806D2A /* OCClassSetting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCClassSetting.h; sourceTree = "<group>"; };
DCF95AE925666FBB00806D2A /* OCClassSetting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCClassSetting.m; sourceTree = "<group>"; };
DCF962DB2B5A698500509705 /* OCDatabase+Scans.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCDatabase+Scans.h"; sourceTree = "<group>"; };
DCF962DC2B5A698500509705 /* OCDatabase+Scans.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCDatabase+Scans.m"; sourceTree = "<group>"; };
DCFA564C2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCClassSettingsFlatSourcePostBuild.h; sourceTree = "<group>"; };
DCFA564D2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCClassSettingsFlatSourcePostBuild.m; sourceTree = "<group>"; };
DCFBACF021BAA77F00943F76 /* largePropFindResponse1000.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = largePropFindResponse1000.xml; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3407,6 +3411,8 @@
DCE370922099D18100114981 /* OCDatabaseConsistentOperation.h */,
DCC3700E24D4B3B7008B0DEB /* OCDatabase+Diagnostic.m */,
DCC3700D24D4B3B7008B0DEB /* OCDatabase+Diagnostic.h */,
DCF962DC2B5A698500509705 /* OCDatabase+Scans.m */,
DCF962DB2B5A698500509705 /* OCDatabase+Scans.h */,
DCD3439920592EE100189B9A /* SQLite */,
);
path = Database;
Expand Down Expand Up @@ -4612,6 +4618,7 @@
DCADC0522072DE6600DB8E83 /* OCSQLiteMigration.h in Headers */,
DCEEB2E92046BC2600189B9A /* OCHTTPStatus.h in Headers */,
DC188993218B031600CFB3F9 /* OCLogSource.h in Headers */,
DCF962DD2B5A698500509705 /* OCDatabase+Scans.h in Headers */,
DC510D3227E1469600F2754F /* OCDataSourceSnapshot.h in Headers */,
DC98BDF521E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.h in Headers */,
DCDBEE382049EF3C00189B9A /* NSURL+OCURLNormalization.h in Headers */,
Expand Down Expand Up @@ -5088,6 +5095,7 @@
DC68057E212EB438006C3B1F /* OCExtensionMatch.m in Sources */,
DCC4F40027D75BF700ABF4C9 /* OCDataConverterPipeline.m in Sources */,
DC9D22EB25A8754200CF5675 /* OCHTTPRequest+JSON.m in Sources */,
DCF962DE2B5A698500509705 /* OCDatabase+Scans.m in Sources */,
DC772E9226FC90E2002C0015 /* OCAuthenticationBrowserSessionAWBrowser.m in Sources */,
DC302AEF221EAC55003218C6 /* OCProxyProgress.m in Sources */,
DC9C19F3278EE7230021222E /* OCResourceRequestImage.m in Sources */,
Expand Down
27 changes: 26 additions & 1 deletion ownCloudSDK/Core/ItemList/OCCore+ItemList.m
Expand Up @@ -473,6 +473,8 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task
NSMutableArray <OCItem *> *deletedCacheItems = [NSMutableArray new];
NSMutableArray <OCItem *> *newItems = [NSMutableArray new];

__block NSMutableArray<OCLocation *> *restoredFolderLocations = nil;

__block NSError *cacheUpdateError = nil;

queryResults = [NSMutableArray new];
Expand Down Expand Up @@ -726,6 +728,23 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task
newItem.removed = knownItemRemoved; // carry over the removed status
[queryResults removeObject:newItem]; // remove from query results
}

// Trigger re-scan of folders restored in the same location
if (knownItemRemoved && (newItem.type == OCItemTypeCollection))
{
OCLocation *folderLocation;

if ((folderLocation = newItem.location) != nil)
{
if (restoredFolderLocations == nil)
{
restoredFolderLocations = [NSMutableArray new];
}
[restoredFolderLocations addObject:folderLocation];

// OCLogDebug(@"Restored folder: %@", folderLocation);
}
}
}

// Remove from deletedCacheItems
Expand Down Expand Up @@ -796,6 +815,12 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task
OCDriveID taskLocationDriveID = task.location.driveID;
OCPath taskLocationPath = task.location.path;

// Add restored folders to refreshLocations
if (restoredFolderLocations != nil)
{
[refreshLocations addObjectsFromArray:restoredFolderLocations];
}

// Determine refreshPaths if automatic item list updates are enabled
for (OCItem *item in newItems)
{
Expand Down Expand Up @@ -888,7 +913,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task

if (movedItems.count > 0)
{
OCLogDebug(@"Moved items: %@", OCLogPrivate(movedItems));
// OCLogDebug(@"Moved items: %@", OCLogPrivate(movedItems));
[changedCacheItems addObjectsFromArray:movedItems];
}

Expand Down
10 changes: 4 additions & 6 deletions ownCloudSDK/Core/OCCore+ItemUpdates.m
Expand Up @@ -47,11 +47,6 @@ - (void)performUpdatesForAddedItems:(nullable NSArray<OCItem *> *)addedItems
return;
}

// Determine driveID
// if (driveID == nil) { driveID = addedItems.firstObject.driveID; }
// if (driveID == nil) { driveID = removedItems.firstObject.driveID; }
// if (driveID == nil) { driveID = updatedItems.firstObject.driveID; }

// Begin
[self beginActivity:@"Perform item and query updates"];

Expand Down Expand Up @@ -135,7 +130,10 @@ - (void)performUpdatesForAddedItems:(nullable NSArray<OCItem *> *)addedItems
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
for (OCItem *removeItem in removedItems)
{
[self->_thumbnailCache removeObjectForKey:removeItem.fileID];
if (removeItem.fileID != nil)
{
[self->_thumbnailCache removeObjectForKey:removeItem.fileID];
}
}

OCWaitDidFinishTask(cacheUpdatesGroup);
Expand Down
30 changes: 30 additions & 0 deletions ownCloudSDK/Query/OCQuery.m
Expand Up @@ -264,11 +264,41 @@ - (void)updateWithAddedItems:(nullable OCCoreItemList *)addedItems updatedItems:
{
OCItem *removeItem;

// Remove removed item itself
if ((removeItem = fullQueryResultsItemList.itemsByLocalID[removedItem.localID]) != nil)
{
[fullQueryResults removeObject:removeItem];
madeChanges = YES;
}

// In case of a removed folder, remove all items in the folder and its subfolders, too
if (removedItem.type == OCItemTypeCollection)
{
NSMutableArray<OCItem *> *containedItems = nil;

// Find all contained items
for (OCItem *item in fullQueryResults)
{
if (OCNAIsEqual(item.driveID, removeItem.driveID) && // item in same drive
[item.path hasPrefix:removedItem.path]) // item below removed folder
{
if (containedItems == nil) {
containedItems = [NSMutableArray new];
}

[containedItems addObject:item];
}
}

// Remove contained items
if (containedItems != nil)
{
[fullQueryResults removeObjectsInArray:containedItems];
madeChanges = YES;

// OCLogDebug(@"Recursive removal of %@ removed: %@", removedItem.location, containedItems);
}
}
}
}

Expand Down
29 changes: 29 additions & 0 deletions ownCloudSDK/Vaults/Database/OCDatabase+Scans.h
@@ -0,0 +1,29 @@
//
// OCDatabase+Scans.h
// ownCloudSDK
//
// Created by Felix Schwarz on 19.01.24.
// Copyright © 2024 ownCloud GmbH. All rights reserved.
//

/*
* Copyright (C) 2024, ownCloud GmbH.
*
* This code is covered by the GNU Public License Version 3.
*
* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/
* You should have received a copy of this license along with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

#import <ownCloudSDK/ownCloudSDK.h>

NS_ASSUME_NONNULL_BEGIN

@interface OCDatabase (Scans)

+ (nullable NSError *)scanForAndMarkAsRemovedDanglingMetadataInDatabase:(OCSQLiteDB *)sqlDB;

@end

NS_ASSUME_NONNULL_END
143 changes: 143 additions & 0 deletions ownCloudSDK/Vaults/Database/OCDatabase+Scans.m
@@ -0,0 +1,143 @@
//
// OCDatabase+Scans.m
// ownCloudSDK
//
// Created by Felix Schwarz on 19.01.24.
// Copyright © 2024 ownCloud GmbH. All rights reserved.
//

/*
* Copyright (C) 2024, ownCloud GmbH.
*
* This code is covered by the GNU Public License Version 3.
*
* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/
* You should have received a copy of this license along with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

#import "OCDatabase+Scans.h"

@implementation OCDatabase (Scans)

+ (NSError *)scanForAndMarkAsRemovedDanglingMetadataInDatabase:(OCSQLiteDB *)sqlDB
{
NSMutableArray<OCDriveID> *driveIDs = [NSMutableArray new];
__block NSError *resultError = nil;

[sqlDB executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError * _Nullable(OCSQLiteDB * _Nonnull db, OCSQLiteTransaction * _Nonnull transaction) {
__block NSError *transactionError = nil;

// Determine used drive IDs
[sqlDB executeQuery:[OCSQLiteQuery query:@"SELECT driveID FROM metaData GROUP BY driveID" resultHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error, OCSQLiteTransaction * _Nullable transaction, OCSQLiteResultSet * _Nullable resultSet) {
if (error != nil) {
transactionError = error;
return;
}

[resultSet iterateUsing:^(OCSQLiteResultSet * _Nonnull resultSet, NSUInteger line, OCSQLiteRowDictionary _Nonnull rowDictionary, BOOL * _Nonnull stop) {
OCDriveID driveID;

if ((driveID = OCDriveIDWrap(rowDictionary[@"driveID"])) != nil)
{
[driveIDs addObject:driveID];
}
} error:&error];
}]];

if (transactionError != nil) { return (transactionError); }

// Iterate over drives
for (OCDriveID driveID in driveIDs)
{
// Determine dangling folders
NSMutableSet<NSString *> *validPaths = [NSMutableSet new];
NSMutableSet<NSString *> *danglingFolderPaths = [NSMutableSet new];

NSString *driveIDComparator = (OCDriveIDUnwrap(driveID) == nil) ? @"IS" : @"=";

@autoreleasepool {
OCSQLiteQueryString queryString = [NSString stringWithFormat:@"SELECT path, parentPath FROM metaData WHERE type=1 AND removed=0 AND driveID %@ ? GROUP BY path, removed ORDER BY path", driveIDComparator];

[sqlDB executeQuery:[OCSQLiteQuery query:queryString withParameters:@[ driveID ] resultHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error, OCSQLiteTransaction * _Nullable transaction, OCSQLiteResultSet * _Nullable resultSet) {
if (error != nil) {
transactionError = error;
return;
}

OCSQLiteRowDictionary rowDict = nil;
NSError *rowError = nil;

do {
NSUInteger i=0;

// Limit memory consumption by processing 500 paths at a time, then releasing autoreleasepool memory
@autoreleasepool {
do {
i++;
if ((rowDict = [resultSet nextRowDictionaryWithError:&rowError]) != nil)
{
OCPath path = (NSString *)rowDict[@"path"];
OCPath parentPath = (NSString *)rowDict[@"parentPath"];

if (parentPath.isRootPath || [validPaths containsObject:parentPath])
{
// In root or a known existing path
[validPaths addObject:path];
}
else
{
// Not in root and not in a known existing path
[danglingFolderPaths addObject:path];
}
}
} while ((rowDict != nil) && (i<500) && (rowError==nil));
}
} while ((rowDict != nil) && (rowError==nil));
}]];
}

OCLog(@"Valid paths in drive %@: %@", driveID, validPaths);
OCLog(@"Dangling folder paths in drive %@: %@", driveID, danglingFolderPaths);

if (transactionError != nil) { return (transactionError); }

// Mark all dangling folders and all items below their paths as removed
@autoreleasepool {
for (OCPath danglingPath in danglingFolderPaths)
{
OCSQLiteQueryString queryString = [NSString stringWithFormat:@"UPDATE metaData SET removed=1 WHERE path LIKE ? AND driveID %@ ? AND removed=0", driveIDComparator];

[sqlDB executeQuery:[OCSQLiteQuery query:queryString withParameters:@[ [danglingPath stringByAppendingString:@"%"], driveID ] resultHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error, OCSQLiteTransaction * _Nullable transaction, OCSQLiteResultSet * _Nullable resultSet) {
if (error != nil) {
transactionError = error;
return;
}
}]];
}
}

if (transactionError != nil) { return (transactionError); }

// Mark all items without non-removed parent paths as removed
OCSQLiteQueryString queryString = [NSString stringWithFormat:@"UPDATE metaData SET removed=1 WHERE removed=0 AND driveID %@ ? AND parentPath NOT IN (SELECT path FROM metaData WHERE removed=0 AND type=1 AND driveID %@ ?)", driveIDComparator, driveIDComparator];

[sqlDB executeQuery:[OCSQLiteQuery query:queryString withParameters:@[ driveID, driveID ] resultHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error, OCSQLiteTransaction * _Nullable transaction, OCSQLiteResultSet * _Nullable resultSet) {
if (error != nil) {
transactionError = error;
return;
}
}]];

if (transactionError != nil) { return (transactionError); }
}

return (transactionError);
} type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB * _Nonnull db, OCSQLiteTransaction * _Nonnull transaction, NSError * _Nullable error) {
resultError = error;
}]];

return (resultError);
}

@end

0 comments on commit 0198c70

Please sign in to comment.