From 8dfb50e3565c4421129b4623ce4b26e7623d6b11 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Thu, 21 Apr 2022 11:17:23 +0200 Subject: [PATCH] - OCItemVersionSeed: new value to allow quick detection of changes in items - OCItem: add new versionSeed property and associated methods to update the seed - OCCore: add support for updating versionSeed when changes occur - OCItem+OCDataItem: make OCItem comply to OCDataItem and OCDataItemVersion (the latter using the new version seed) - OCHTTPResponse: extend support for encoding-sensitive conversion of body data to NSStrings, with explicit fallback encoding - OCResourceSourceDriveItems: make text resource encoding sensitive - OCResourceText: make compliant to OCDataItem and OCDataItemVersion - OCDrive: include readme and image in significant update detection, add support for subtitles/descriptions - GAGraphData+Decoder: add support for unescaped URLs (https://github.com/owncloud/ocis/issues/3538) - OCLocation: add .lastPathComponent utility property - OCViewProviderContext: add initializer and context key for content mode - OCQuery: add .queryResultsDataSource property providing an OCDataSource tracking the query's results (leaving room for future performance improvements) --- ownCloudSDK.xcodeproj/project.pbxproj | 8 ++ ownCloudSDK/Core/ItemList/OCCore+ItemList.m | 6 ++ ownCloudSDK/Core/OCCore+ItemUpdates.m | 4 + ownCloudSDK/Core/OCCore.m | 2 +- .../DriveItems/OCResourceSourceDriveItems.m | 2 +- ownCloudSDK/Core/Resources/OCResourceTypes.h | 2 +- .../Core/Resources/Resource/OCResourceText.h | 5 +- .../Core/Resources/Resource/OCResourceText.m | 17 ++++ ownCloudSDK/Data Sources/OCDataTypes.h | 3 +- ownCloudSDK/Data Sources/OCDataTypes.m | 3 +- ownCloudSDK/Drive/OCDrive.m | 11 ++- .../Parser Support/GAGraphData+Decoder.m | 8 +- ownCloudSDK/HTTP/Response/OCHTTPResponse.h | 4 +- ownCloudSDK/HTTP/Response/OCHTTPResponse.m | 22 +++-- ownCloudSDK/Item/OCItem+OCDataItem.h | 29 +++++++ ownCloudSDK/Item/OCItem+OCDataItem.m | 87 +++++++++++++++++++ ownCloudSDK/Item/OCItem.h | 11 +++ ownCloudSDK/Item/OCItem.m | 27 ++++++ ownCloudSDK/Location/OCLocation.h | 1 + ownCloudSDK/Location/OCLocation.m | 5 ++ ownCloudSDK/Protocols/OCViewProviderContext.h | 6 +- ownCloudSDK/Protocols/OCViewProviderContext.m | 22 +++++ ownCloudSDK/Query/OCQuery+Internal.m | 14 +++ ownCloudSDK/Query/OCQuery.h | 8 +- ownCloudSDK/Query/OCQuery.m | 17 ++++ ownCloudSDK/ownCloudSDK.h | 1 + 26 files changed, 303 insertions(+), 22 deletions(-) create mode 100644 ownCloudSDK/Item/OCItem+OCDataItem.h create mode 100644 ownCloudSDK/Item/OCItem+OCDataItem.m diff --git a/ownCloudSDK.xcodeproj/project.pbxproj b/ownCloudSDK.xcodeproj/project.pbxproj index fc1ea872..38f1e7e1 100644 --- a/ownCloudSDK.xcodeproj/project.pbxproj +++ b/ownCloudSDK.xcodeproj/project.pbxproj @@ -212,6 +212,8 @@ DC39DC4E2041B53000189B9A /* AuthenticationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC39DC4D2041B53000189B9A /* AuthenticationTests.m */; }; DC39DC59204215A800189B9A /* NSProgress+OCEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = DC39DC57204215A800189B9A /* NSProgress+OCEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC39DC5A204215A800189B9A /* NSProgress+OCEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DC39DC58204215A800189B9A /* NSProgress+OCEvent.m */; }; + DC3AB1912808B3C400789435 /* OCItem+OCDataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3AB18F2808B3C400789435 /* OCItem+OCDataItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC3AB1922808B3C400789435 /* OCItem+OCDataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3AB1902808B3C400789435 /* OCItem+OCDataItem.m */; }; DC3C7FE121A6EDE00064D193 /* NSError+OCHTTPStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3C7FDF21A6EDE00064D193 /* NSError+OCHTTPStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3C7FE221A6EDE00064D193 /* NSError+OCHTTPStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3C7FE021A6EDE00064D193 /* NSError+OCHTTPStatus.m */; }; DC3CE03F2429FAA200AB8B88 /* OCMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3CE03B2429FAA200AB8B88 /* OCMessage.m */; }; @@ -1141,6 +1143,8 @@ DC39DC4D2041B53000189B9A /* AuthenticationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AuthenticationTests.m; sourceTree = ""; }; DC39DC57204215A800189B9A /* NSProgress+OCEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSProgress+OCEvent.h"; sourceTree = ""; }; DC39DC58204215A800189B9A /* NSProgress+OCEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSProgress+OCEvent.m"; sourceTree = ""; }; + DC3AB18F2808B3C400789435 /* OCItem+OCDataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCDataItem.h"; sourceTree = ""; }; + DC3AB1902808B3C400789435 /* OCItem+OCDataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItem+OCDataItem.m"; sourceTree = ""; }; DC3C7FDF21A6EDE00064D193 /* NSError+OCHTTPStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+OCHTTPStatus.h"; sourceTree = ""; }; DC3C7FE021A6EDE00064D193 /* NSError+OCHTTPStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+OCHTTPStatus.m"; sourceTree = ""; }; DC3CE03B2429FAA200AB8B88 /* OCMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMessage.m; sourceTree = ""; }; @@ -3094,6 +3098,8 @@ DCFF1AAE21655C8800ABE40A /* OCItem+OCFileURLMetadata.h */, DC85571B2050196000189B9A /* OCItem+OCXMLObjectCreation.m */, DC85571A2050196000189B9A /* OCItem+OCXMLObjectCreation.h */, + DC3AB1902808B3C400789435 /* OCItem+OCDataItem.m */, + DC3AB18F2808B3C400789435 /* OCItem+OCDataItem.h */, DC4E0A5720927048007EB05F /* OCItemVersionIdentifier.m */, DC4E0A5620927048007EB05F /* OCItemVersionIdentifier.h */, DC0283652090A8EE005B6334 /* Images */, @@ -4025,6 +4031,7 @@ DC8EB30423952084009148F9 /* OCAuthenticationBrowserSessionUIWebView.h in Headers */, DC708CE0214135D100FE43CA /* OCSyncActionDelete.h in Headers */, DCC8FA33202B443D00EB6701 /* OCEventTarget.h in Headers */, + DC3AB1912808B3C400789435 /* OCItem+OCDataItem.h in Headers */, DC8913642092088600028999 /* NSString+OCVersionCompare.h in Headers */, DCF00BF527E28A77001F2AFC /* OCDataSourceSubscription+Internal.h in Headers */, DCC8F9E62028556500EB6701 /* OCConnection.h in Headers */, @@ -4655,6 +4662,7 @@ DCE26621211348B00001FB2C /* OCCore+CommandLocalImport.m in Sources */, DCF1C6732631BFD5004D8B0F /* OCMeasurement.m in Sources */, DC2565F62260C86A00828AA5 /* OCCertificateRuleChecker.m in Sources */, + DC3AB1922808B3C400789435 /* OCItem+OCDataItem.m in Sources */, DC35969722403E5B00C4D6E6 /* OCQueryCondition+SQLBuilder.m in Sources */, DCDBEE302048A71200189B9A /* OCConnection+Tools.m in Sources */, DCFE3B8927A16AE800939415 /* OCConnection+GraphAPI.m in Sources */, diff --git a/ownCloudSDK/Core/ItemList/OCCore+ItemList.m b/ownCloudSDK/Core/ItemList/OCCore+ItemList.m index 324dd839..99f554a5 100644 --- a/ownCloudSDK/Core/ItemList/OCCore+ItemList.m +++ b/ownCloudSDK/Core/ItemList/OCCore+ItemList.m @@ -550,6 +550,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task [queryResults addObject:cacheItem]; // Update cache + [cacheItem updateSeedFrom:retrievedItem.versionSeed]; [changedCacheItems addObject:cacheItem]; } else @@ -573,6 +574,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task (retrievedItem.isFavorite != cacheItem.isFavorite)) // Favorite mismatch { // Update item in the cache if the server has a different version + [retrievedItem updateSeedFrom:cacheItem.versionSeed]; [changedCacheItems addObject:retrievedItem]; } } @@ -592,6 +594,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task // Result: new file's item still points to the local copy it downloaded, but which has been removed by vacuuming of the OLD file -> viewing and other actions requiring the local copy fail unexpectedly // Remove cacheItem (with different fileID) + [cacheItem updateSeed]; [deletedCacheItems addObject:cacheItem]; // Add retrievedItem (with different fileID + different localID) @@ -634,6 +637,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task else { // Remove item + [cacheItem updateSeed]; [deletedCacheItems addObject:cacheItem]; } } @@ -745,6 +749,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task } // Add to updatedItems + [newItem updateSeedFrom:knownItem.versionSeed]; [changedCacheItems addObject:newItem]; } @@ -843,6 +848,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task containedItem.removed = NO; } + [containedItem updateSeedFrom:item.versionSeed]; [movedItems addObject:containedItem]; } else diff --git a/ownCloudSDK/Core/OCCore+ItemUpdates.m b/ownCloudSDK/Core/OCCore+ItemUpdates.m index 1c9a5e37..2af46a5b 100644 --- a/ownCloudSDK/Core/OCCore+ItemUpdates.m +++ b/ownCloudSDK/Core/OCCore+ItemUpdates.m @@ -70,6 +70,10 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems return; } + // Update version seeds for updated and removed items + [updatedItems makeObjectsPerformSelector:@selector(updateSeed)]; + [removedItems makeObjectsPerformSelector:@selector(updateSeed)]; + // Update metaData table and queries if ((addedItems.count > 0) || (removedItems.count > 0) || (updatedItems.count > 0) || (beforeQueryUpdatesAction!=nil)) { diff --git a/ownCloudSDK/Core/OCCore.m b/ownCloudSDK/Core/OCCore.m index 85e6cc7e..464e1899 100644 --- a/ownCloudSDK/Core/OCCore.m +++ b/ownCloudSDK/Core/OCCore.m @@ -2132,7 +2132,7 @@ - (void)updateWithDrives:(NSArray *)drives initialize:(BOOL)doInitial return (NO); }] mutableCopy]; - [hierarchicDrivesTopLevelItems addObject:_hierarchicDrivesLogicalProjectsFolderPresentable]; + // [hierarchicDrivesTopLevelItems addObject:_hierarchicDrivesLogicalProjectsFolderPresentable]; [_hierarchicDrivesDataSource setVersionedItems:hierarchicDrivesTopLevelItems]; diff --git a/ownCloudSDK/Core/Resources/DriveItems/OCResourceSourceDriveItems.m b/ownCloudSDK/Core/Resources/DriveItems/OCResourceSourceDriveItems.m index 2ec7b041..afa12fd9 100644 --- a/ownCloudSDK/Core/Resources/DriveItems/OCResourceSourceDriveItems.m +++ b/ownCloudSDK/Core/Resources/DriveItems/OCResourceSourceDriveItems.m @@ -110,7 +110,7 @@ - (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OC // Text OCResourceText *resource = [[OCResourceText alloc] initWithRequest:driveItemRequest]; - resource.text = response.bodyAsString; // takes encoding passed in Content-Type into account + resource.text = [response bodyAsStringWithFallbackEncoding:NSUTF8StringEncoding]; // takes encoding passed in Content-Type into account, defaults to UTF-8 returnResource = resource; } diff --git a/ownCloudSDK/Core/Resources/OCResourceTypes.h b/ownCloudSDK/Core/Resources/OCResourceTypes.h index 7629f65f..c02a496e 100644 --- a/ownCloudSDK/Core/Resources/OCResourceTypes.h +++ b/ownCloudSDK/Core/Resources/OCResourceTypes.h @@ -44,4 +44,4 @@ typedef NSString* OCResourceVersion; //!< A string that can be used to distingui typedef NSString* OCResourceStructureDescription; //!< A string describing the structure properties of the resource that can affect resource generation or return, such as f.ex. the MIME type (which can change after a rename, without causing ID or version to change) typedef NSString* OCResourceMetadata; //!< A resource-specific string with metadata on the resource's data -typedef NSString* OCResourceMIMEType NS_TYPED_ENUM; +typedef NSString* OCResourceMIMEType; diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceText.h b/ownCloudSDK/Core/Resources/Resource/OCResourceText.h index ce8c0c93..5f7e1484 100644 --- a/ownCloudSDK/Core/Resources/Resource/OCResourceText.h +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceText.h @@ -17,12 +17,13 @@ */ #import "OCResource.h" +#import "OCDataTypes.h" NS_ASSUME_NONNULL_BEGIN -@interface OCResourceText : OCResource +@interface OCResourceText : OCResource -@property(strong) NSString *text; +@property(strong,nullable) NSString *text; @end diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceText.m b/ownCloudSDK/Core/Resources/Resource/OCResourceText.m index 409dd490..bf03738c 100644 --- a/ownCloudSDK/Core/Resources/Resource/OCResourceText.m +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceText.m @@ -17,6 +17,7 @@ */ #import "OCResourceText.h" +#import "OCDataTypes.h" @implementation OCResourceText @@ -43,4 +44,20 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder return (self); } +#pragma mark - Data Item +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeTextResource); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.identifier); +} + +- (OCDataItemVersion)dataItemVersion +{ + return (self.version); +} + @end diff --git a/ownCloudSDK/Data Sources/OCDataTypes.h b/ownCloudSDK/Data Sources/OCDataTypes.h index fdfd709e..29c69511 100644 --- a/ownCloudSDK/Data Sources/OCDataTypes.h +++ b/ownCloudSDK/Data Sources/OCDataTypes.h @@ -59,9 +59,10 @@ typedef NSComparisonResult(^OCDataSourceItemComparator)(OCDataSource *source1, O typedef BOOL(^OCDataSourceItemRecordFilter)(OCDataItemRecord * _Nullable itemRecord); typedef NSComparisonResult(^OCDataSourceItemRecordComparator)(OCDataItemRecord * _Nullable itemRecord1, OCDataItemRecord * _Nullable itemRecord2); +extern OCDataItemType OCDataItemTypeItem; //!< Item of type OCItem extern OCDataItemType OCDataItemTypeDrive; //!< Item of type OCDrive extern OCDataItemType OCDataItemTypePresentable; //!< Item of type OCDataItemPresentable -extern OCDataItemType OCDataItemTypeListContentConfiguration; //!< Item of type UIListContentConfiguration +extern OCDataItemType OCDataItemTypeTextResource; //!< Item of type OCResourceText extern OCDataViewOption OCDataViewOptionCore; //!< OCCore instance extern OCDataViewOption OCDataViewOptionListContentConfiguration; //!< UIListContentConfiguration instance to fill diff --git a/ownCloudSDK/Data Sources/OCDataTypes.m b/ownCloudSDK/Data Sources/OCDataTypes.m index ca8b1707..5133a070 100644 --- a/ownCloudSDK/Data Sources/OCDataTypes.m +++ b/ownCloudSDK/Data Sources/OCDataTypes.m @@ -18,9 +18,10 @@ #import "OCDataTypes.h" +OCDataItemType OCDataItemTypeItem = @"item"; OCDataItemType OCDataItemTypeDrive = @"drive"; OCDataItemType OCDataItemTypePresentable = @"presentable"; -OCDataItemType OCDataItemTypeListContentConfiguration = @"listContentConfiguration"; +OCDataItemType OCDataItemTypeTextResource = @"textResource"; OCDataViewOption OCDataViewOptionCore = @"core"; OCDataViewOption OCDataViewOptionListContentConfiguration = @"listContentConfiguration"; diff --git a/ownCloudSDK/Drive/OCDrive.m b/ownCloudSDK/Drive/OCDrive.m index 5c3c655b..4096645b 100644 --- a/ownCloudSDK/Drive/OCDrive.m +++ b/ownCloudSDK/Drive/OCDrive.m @@ -63,12 +63,14 @@ + (instancetype)personalDrive return(nil); } +#pragma mark - OCDataConverter for OCDrives + (void)load { OCDataConverter *driveToPresentableConverter; driveToPresentableConverter = [[OCDataConverter alloc] initWithInputType:OCDataItemTypeDrive outputType:OCDataItemTypePresentable conversion:^id _Nullable(OCDataConverter * _Nonnull converter, OCDrive * _Nullable inDrive, OCDataRenderer * _Nullable renderer, NSError * _Nullable __autoreleasing * _Nullable outError, OCDataViewOptions _Nullable options) { OCDataItemPresentable *presentable = nil; + __weak OCCore *weakCore = options[OCDataViewOptionCore]; if (inDrive != nil) { @@ -77,7 +79,7 @@ + (void)load presentable = [[OCDataItemPresentable alloc] initWithItem:inDrive]; presentable.title = inDrive.name; - presentable.subtitle = inDrive.type; + presentable.subtitle = (inDrive.desc.length > 0) ? inDrive.desc : inDrive.type; presentable.availableResources = (imageDriveItem != nil) ? ((readmeDriveItem != nil) ? @[OCDataItemPresentableResourceCoverImage, OCDataItemPresentableResourceCoverDescription] : @@ -99,6 +101,7 @@ + (void)load } resourceRequest.lifetime = OCResourceRequestLifetimeSingleRun; + resourceRequest.core = weakCore; return (resourceRequest); }; @@ -112,6 +115,7 @@ + (void)load ]]; } +#pragma mark - Comparison - (BOOL)isSubstantiallyDifferentFrom:(OCDrive *)drive { return (![drive.identifier isEqual:_identifier] || @@ -119,9 +123,12 @@ - (BOOL)isSubstantiallyDifferentFrom:(OCDrive *)drive ![drive.name isEqual:_name] || ![drive.desc isEqual:_desc] || ![drive.davRootURL isEqual:_davRootURL] || + OCNANotEqual([drive.gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag) || + OCNANotEqual([drive.gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag) || (![drive.rootETag isEqual:self.rootETag] && (drive.rootETag != self.rootETag))); } +#pragma mark - Utility accessors - (OCLocation *)rootLocation { return ([[OCLocation alloc] initWithDriveID:_identifier path:@"/"]); @@ -193,7 +200,7 @@ - (OCDataItemReference)dataItemReference - (OCDataItemVersion)dataItemVersion { - return ([NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@", _identifier, _type, _name, _desc, _davRootURL, _gaDrive.eTag]); + return ([NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@:%@:%@", _identifier, _type, _name, _desc, _davRootURL, _gaDrive.eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag]); } #pragma mark - Comparison diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m index 944cb4fe..c05c6ad3 100644 --- a/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m @@ -103,7 +103,13 @@ + (nullable id)object:(id)inObject key:(NSString *)key ofClass:(Class)class inCo { // Convert string to URL NSURL *url; - if ((url = [NSURL URLWithString:(NSString *)object]) != nil) + if ((url = [NSURL URLWithString:(NSString *)object]) == nil) + { + // Implement fallback in case of unescaped URLs (https://github.com/owncloud/ocis/issues/3538) + url = [NSURL URLWithString:[(NSString *)object stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]]; + } + + if (url != nil) { // Block file URLs if (url.isFileURL) diff --git a/ownCloudSDK/HTTP/Response/OCHTTPResponse.h b/ownCloudSDK/HTTP/Response/OCHTTPResponse.h index b8b9b968..ebb8799d 100644 --- a/ownCloudSDK/HTTP/Response/OCHTTPResponse.h +++ b/ownCloudSDK/HTTP/Response/OCHTTPResponse.h @@ -68,9 +68,11 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)contentType; //!< Content-Type (stripped of charset and other parameters) #pragma mark - Convenience body conversions -- (NSStringEncoding)bodyStringEncoding; //!< Returns the body's string encoding +- (NSStringEncoding)bodyStringEncoding; //!< Returns the body's string encoding - or ISO-8859-1 (Latin1) if none was found +- (NSStringEncoding)bodyStringEncodingWithFallback:(NSStringEncoding)fallbackEncoding; //!< Returns the body's string encoding - or fallbackEncoding if none was found - (nullable NSString *)bodyAsString; //!< Returns the response body as a string formatted using the text encoding provided by the server. If no text encoding is provided, ISO-8859-1 is used. +- (nullable NSString *)bodyAsStringWithFallbackEncoding:(NSStringEncoding)fallbackEncoding; //!< Returns the response body as a string formatted using the text encoding provided by the server. If no text encoding is provided, fallbackEncoding is used. - (nullable NSDictionary *)bodyConvertedDictionaryFromJSONWithError:(NSError * _Nullable *)outError; //!< Returns the response body as dictionary as converted by the JSON deserializer - (nullable NSArray *)bodyConvertedArrayFromJSONWithError:(NSError * _Nullable *)error; //!< Returns the response body as array as converted by the JSON deserializer diff --git a/ownCloudSDK/HTTP/Response/OCHTTPResponse.m b/ownCloudSDK/HTTP/Response/OCHTTPResponse.m index eae8b62d..71295f93 100644 --- a/ownCloudSDK/HTTP/Response/OCHTTPResponse.m +++ b/ownCloudSDK/HTTP/Response/OCHTTPResponse.m @@ -126,36 +126,42 @@ - (nullable NSString *)contentType } #pragma mark - Convenience body conversions -- (NSStringEncoding)bodyStringEncoding +- (NSStringEncoding)bodyStringEncodingWithFallback:(NSStringEncoding)fallbackEncoding; //!< Returns the body's string encoding - or fallbackEncoding if none was found { NSString *textEncodingName; - NSStringEncoding stringEncoding; + NSStringEncoding stringEncoding = fallbackEncoding; if ((textEncodingName = self.httpURLResponse.textEncodingName) != nil) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)textEncodingName)); } - else - { - stringEncoding = NSISOLatin1StringEncoding; // ISO-8859-1 - } return(stringEncoding); } -- (NSString *)bodyAsString +- (NSStringEncoding)bodyStringEncoding +{ + return ([self bodyStringEncodingWithFallback:NSISOLatin1StringEncoding]); +} + +- (NSString *)bodyAsStringWithFallbackEncoding:(NSStringEncoding)fallbackEncoding { NSString *responseBodyAsString = nil; NSData *responseBodyData; if ((responseBodyData = self.bodyData) != nil) { - responseBodyAsString = [[NSString alloc] initWithData:responseBodyData encoding:self.bodyStringEncoding]; + responseBodyAsString = [[NSString alloc] initWithData:responseBodyData encoding:[self bodyStringEncodingWithFallback:fallbackEncoding]]; } return (responseBodyAsString); } +- (NSString *)bodyAsString +{ + return ([self bodyAsStringWithFallbackEncoding:NSISOLatin1StringEncoding]); +} + - (NSDictionary *)bodyConvertedDictionaryFromJSONWithError:(NSError **)outError { id jsonObject; diff --git a/ownCloudSDK/Item/OCItem+OCDataItem.h b/ownCloudSDK/Item/OCItem+OCDataItem.h new file mode 100644 index 00000000..e0cde0b2 --- /dev/null +++ b/ownCloudSDK/Item/OCItem+OCDataItem.h @@ -0,0 +1,29 @@ +// +// OCItem+OCDataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 14.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, 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 . + * + */ + +#import +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCItem (DataItem) + + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Item/OCItem+OCDataItem.m b/ownCloudSDK/Item/OCItem+OCDataItem.m new file mode 100644 index 00000000..26222d0e --- /dev/null +++ b/ownCloudSDK/Item/OCItem+OCDataItem.m @@ -0,0 +1,87 @@ +// +// OCItem+OCDataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 14.04.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2022, 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 . + * + */ + +#import "OCItem+OCDataItem.h" + +@implementation OCItem (DataItem) + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeItem); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.localID); +} + +- (OCDataItemVersion)dataItemVersion +{ + return (@(_versionSeed)); +} + +#pragma mark - OCDataConverter for OCDrives ++ (void)load +{ + OCDataConverter *itemToPresentableConverter; + + itemToPresentableConverter = [[OCDataConverter alloc] initWithInputType:OCDataItemTypeItem outputType:OCDataItemTypePresentable conversion:^id _Nullable(OCDataConverter * _Nonnull converter, OCItem * _Nullable inItem, OCDataRenderer * _Nullable renderer, NSError * _Nullable __autoreleasing * _Nullable outError, OCDataViewOptions _Nullable options) { + OCDataItemPresentable *presentable = nil; +// __weak OCCore *weakCore = options[OCDataViewOptionCore]; + + if (inItem != nil) + { + presentable = [[OCDataItemPresentable alloc] initWithItem:inItem]; + presentable.title = inItem.name; + presentable.subtitle = (inItem.type == OCItemTypeCollection) ? @"folder" : [NSString stringWithFormat:@"file (%ld bytes)", (long)inItem.size]; + +// presentable.availableResources = (imageDriveItem != nil) ? +// ((readmeDriveItem != nil) ? @[OCDataItemPresentableResourceCoverImage, OCDataItemPresentableResourceCoverDescription] : +// @[OCDataItemPresentableResourceCoverImage]) : +// ((readmeDriveItem != nil) ? @[OCDataItemPresentableResourceCoverDescription] : +// nil); +// +// presentable.resourceRequestProvider = ^OCResourceRequest * _Nullable(OCDataItemPresentable * _Nonnull presentable, OCDataItemPresentableResource _Nonnull presentableResource, OCDataViewOptions _Nullable options, NSError * _Nullable __autoreleasing * _Nullable outError) { +// OCResourceRequestDriveItem *resourceRequest = nil; +// +// if ([presentableResource isEqual:OCDataItemPresentableResourceCoverImage] && (imageDriveItem != nil)) +// { +// resourceRequest = [OCResourceRequestDriveItem requestDriveItem:imageDriveItem waitForConnectivity:YES changeHandler:nil]; +// } +// +// if ([presentableResource isEqual:OCDataItemPresentableResourceCoverDescription] && (readmeDriveItem != nil)) +// { +// resourceRequest = [OCResourceRequestDriveItem requestDriveItem:readmeDriveItem waitForConnectivity:YES changeHandler:nil]; +// } +// +// resourceRequest.lifetime = OCResourceRequestLifetimeSingleRun; +// resourceRequest.core = weakCore; +// +// return (resourceRequest); +// }; + } + + return (presentable); + }]; + + [OCDataRenderer.defaultRenderer addConverters:@[ + itemToPresentableConverter + ]]; +} + +@end diff --git a/ownCloudSDK/Item/OCItem.h b/ownCloudSDK/Item/OCItem.h index 0588b484..d4af9f17 100644 --- a/ownCloudSDK/Item/OCItem.h +++ b/ownCloudSDK/Item/OCItem.h @@ -76,6 +76,8 @@ typedef NS_ENUM(NSInteger, OCItemCloudStatus) OCItemCloudStatusLocalOnly //!< Item only exists locally. There's no remote copy. }; +typedef NSInteger OCItemVersionSeed; //!< Version seed (opaque format) that changes whenever an item changes + #import "OCShare.h" NS_ASSUME_NONNULL_BEGIN @@ -90,6 +92,8 @@ NS_ASSUME_NONNULL_BEGIN NSTimeInterval _localAttributesLastModified; NSString *_creationHistory; + + OCItemVersionSeed _versionSeed; } @property(assign) OCItemType type; //!< The type of the item (e.g. file, collection, ..) @@ -171,6 +175,8 @@ NS_ASSUME_NONNULL_BEGIN @property(readonly,nonatomic) BOOL compactingAllowed; //!< YES if the local copy may be removed during compacting. +@property(assign) OCItemVersionSeed versionSeed; //!< Version seed that changes whenever the item is updated + + (OCLocalID)generateNewLocalID; //!< Generates a new, unique OCLocalID + (OCFileID)generatePlaceholderFileID; //!< Generates a new, unique placeholder OCFileID @@ -197,6 +203,11 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - File tools - (nullable OCFile *)fileWithCore:(OCCore *)core; //!< OCFile instance generated from the data in the OCItem. Returns nil if the item doesn't reference a local file. To test local availability of a file, use -[OCCore localCopyOfItem:] instead of this method. +#pragma mark - Version seed +- (void)regenerateSeed; //!< Regenerates the seed from scratch. +- (void)updateSeed; //!< Update the seed based on its own seed. +- (void)updateSeedFrom:(OCItemVersionSeed)previousVersionSeed; //!< Updates the item's .versionSeed from another item's .versionSeed + #pragma mark - Serialization tools + (nullable instancetype)itemFromSerializedData:(NSData *)serializedData; - (nullable NSData *)serializedData; diff --git a/ownCloudSDK/Item/OCItem.m b/ownCloudSDK/Item/OCItem.m index 2d5c65be..7f88f7dd 100644 --- a/ownCloudSDK/Item/OCItem.m +++ b/ownCloudSDK/Item/OCItem.m @@ -53,6 +53,8 @@ - (instancetype)init _thumbnailAvailability = OCItemThumbnailAvailabilityInternal; _localID = [OCItem generateNewLocalID]; + + [self regenerateSeed]; } return (self); @@ -138,6 +140,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_quotaBytesRemaining forKey:@"quotaBytesRemaining"]; [coder encodeObject:_quotaBytesUsed forKey:@"quotaBytesUsed"]; + [coder encodeInteger:_versionSeed forKey:@"versionSeed"]; + } - (instancetype)initWithCoder:(NSCoder *)decoder @@ -200,6 +204,8 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _quotaBytesRemaining = [decoder decodeObjectOfClass:NSNumber.class forKey:@"quotaBytesRemaining"]; _quotaBytesUsed = [decoder decodeObjectOfClass:NSNumber.class forKey:@"quotaBytesUsed"]; + + _versionSeed = [decoder decodeIntegerForKey:@"versionSeed"]; } return (self); @@ -670,6 +676,27 @@ - (OCFile *)fileWithCore:(OCCore *)core return (file); } +#pragma mark - Version seed ++ (OCItemVersionSeed)randomSeed +{ + return ((NSInteger)NSDate.timeIntervalSinceReferenceDate) ^ ((NSInteger)getpid()); +} + +- (void)updateSeedFrom:(OCItemVersionSeed)previousVersionSeed +{ + _versionSeed = (previousVersionSeed + 1) ^ OCItem.randomSeed; +} + +- (void)updateSeed +{ + [self regenerateSeed]; +} + +- (void)regenerateSeed +{ + _versionSeed = (NSInteger)_localID.hash ^ OCItem.randomSeed; +} + #pragma mark - Description - (NSString *)_shareTypesDescription { diff --git a/ownCloudSDK/Location/OCLocation.h b/ownCloudSDK/Location/OCLocation.h index baf65ce1..4d947b14 100644 --- a/ownCloudSDK/Location/OCLocation.h +++ b/ownCloudSDK/Location/OCLocation.h @@ -40,6 +40,7 @@ typedef NSString* OCLocationString; //!< DriveID + path encoded into a single st @property(strong,readonly,nonatomic) OCLocation *parentLocation; @property(strong,nullable,readonly,nonatomic) OCLocation *normalizedDirectoryPathLocation; @property(strong,nullable,readonly,nonatomic) OCLocation *normalizedFilePathLocation; +@property(strong,nullable,readonly,nonatomic) NSString *lastPathComponent; @property(readonly,nonatomic) BOOL isRoot; diff --git a/ownCloudSDK/Location/OCLocation.m b/ownCloudSDK/Location/OCLocation.m index b849b974..922557f4 100644 --- a/ownCloudSDK/Location/OCLocation.m +++ b/ownCloudSDK/Location/OCLocation.m @@ -108,6 +108,11 @@ - (OCLocation *)normalizedFilePathLocation return (_normalizedFilePathLocation); } +- (NSString *)lastPathComponent +{ + return (_path.lastPathComponent); +} + - (BOOL)isRoot { return (_path.isRootPath); diff --git a/ownCloudSDK/Protocols/OCViewProviderContext.h b/ownCloudSDK/Protocols/OCViewProviderContext.h index a992cf9d..9c2622e4 100644 --- a/ownCloudSDK/Protocols/OCViewProviderContext.h +++ b/ownCloudSDK/Protocols/OCViewProviderContext.h @@ -25,12 +25,16 @@ NS_ASSUME_NONNULL_BEGIN -typedef NSString* OCViewProviderContextKey; +typedef NSString* OCViewProviderContextKey NS_TYPED_ENUM; @interface OCViewProviderContext : NSObject @property(strong,nullable) NSDictionary *attributes; +- (instancetype)initWithAttributes:(NSDictionary *)attributes; + @end +extern OCViewProviderContextKey OCViewProviderContextKeyContentMode; //!< UIViewContentMode for content arrangement + NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Protocols/OCViewProviderContext.m b/ownCloudSDK/Protocols/OCViewProviderContext.m index dd4d0c02..10126612 100644 --- a/ownCloudSDK/Protocols/OCViewProviderContext.m +++ b/ownCloudSDK/Protocols/OCViewProviderContext.m @@ -6,8 +6,30 @@ // Copyright © 2022 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2022, 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 . + * + */ + #import "OCViewProviderContext.h" @implementation OCViewProviderContext +- (instancetype)initWithAttributes:(NSDictionary *)attributes +{ + if ((self = [super init]) != nil) + { + _attributes = attributes; + } + + return (self); +} + @end + +OCViewProviderContextKey OCViewProviderContextKeyContentMode = @"contentMode"; diff --git a/ownCloudSDK/Query/OCQuery+Internal.m b/ownCloudSDK/Query/OCQuery+Internal.m index a0b13c71..a0a487df 100644 --- a/ownCloudSDK/Query/OCQuery+Internal.m +++ b/ownCloudSDK/Query/OCQuery+Internal.m @@ -154,6 +154,8 @@ - (void)updateProcessedResultsIfNeeded:(BOOL)ifNeeded // We just recomputed _processedQueryResults = newProcessedResults; _needsRecomputation = NO; + + [_queryResultsDataSource setVersionedItems:newProcessedResults]; } } } @@ -161,10 +163,22 @@ - (void)updateProcessedResultsIfNeeded:(BOOL)ifNeeded #pragma mark - Needs recomputation - (void)setNeedsRecomputation { + BOOL updateProcessedResults = NO; + @synchronized(self) { _needsRecomputation = YES; self.hasChangesAvailable = YES; + + if (_queryResultsDataSource != nil) + { + updateProcessedResults = YES; + } + } + + if (updateProcessedResults) + { + [self updateProcessedResultsIfNeeded:YES]; } } diff --git a/ownCloudSDK/Query/OCQuery.h b/ownCloudSDK/Query/OCQuery.h index a13def35..57424cd9 100644 --- a/ownCloudSDK/Query/OCQuery.h +++ b/ownCloudSDK/Query/OCQuery.h @@ -25,6 +25,7 @@ #import "OCCoreItemList.h" #import "OCQueryCondition.h" #import "OCCancelAction.h" +#import "OCDataSourceArray.h" #pragma mark - Types typedef NS_ENUM(NSUInteger, OCQueryState) @@ -68,6 +69,7 @@ typedef void(^OCQueryCustomSource)(OCCore *core, OCQuery *query, OCQueryCustomRe NSMutableArray *_fullQueryResults; // All items matching the query, before applying filters and sorting. NSMutableArray *_processedQueryResults; // Like full query results, but after applying sorting and filtering. + OCDataSourceArray *_queryResultsDataSource; OCCoreItemList *_fullQueryResultsItemList; // Cached item list of _fullQueryResults used in the default @@ -137,10 +139,12 @@ typedef void(^OCQueryCustomSource)(OCCore *core, OCQuery *query, OCQueryCustomRe - (void)removeFilter:(id)filter; //!< Remove a filter #pragma mark - Query results -@property(nullable, strong,nonatomic) NSArray *queryResults; //!< Returns an array of OCItems representing the latest results after sorting and filtering. The contents is identical to that of _processedQueryResults at the time of calling. It does not affect the contents of _lastQueryResults. -@property(nullable, strong) OCItem *rootItem; //!< The rootItem is the item at the root of the query - representing the item at .queryPath/.queryItem.path. +@property(nullable,strong,nonatomic) NSArray *queryResults; //!< Returns an array of OCItems representing the latest results after sorting and filtering. The contents is identical to that of _processedQueryResults at the time of calling. It does not affect the contents of _lastQueryResults. +@property(nullable,strong) OCItem *rootItem; //!< The rootItem is the item at the root of the query - representing the item at .queryPath/.queryItem.path. @property(assign,nonatomic) BOOL includeRootItem; //!< If YES, the rootItem is included in the queryResults and change sets. If NO, it's only exposed via .rootItem. +@property(nullable,strong,readonly,nonatomic) OCDataSource *queryResultsDataSource; //!< Returns a data source providing the OCItems in .queryResults. The data source is only created on demand. + #pragma mark - Change Sets @property(assign,nonatomic) BOOL hasChangesAvailable; //!< Indicates that query result changes are available for retrieval @property(nullable, weak) id delegate; //!< Query Delegate that's informed about the availability of changes (optional) diff --git a/ownCloudSDK/Query/OCQuery.m b/ownCloudSDK/Query/OCQuery.m index 9077109d..39a722c0 100644 --- a/ownCloudSDK/Query/OCQuery.m +++ b/ownCloudSDK/Query/OCQuery.m @@ -21,6 +21,7 @@ #import "OCQuery+Internal.h" #import "OCLogger.h" #import "OCQueryCondition+Item.h" +#import "OCItem+OCDataItem.h" #import "NSError+OCError.h" #import @@ -352,6 +353,22 @@ - (void)removeFilter:(id)filter return (queryResults); } +#pragma mark - Data sources +- (OCDataSource *)queryResultsDataSource +{ + @synchronized(self) + { + if (_queryResultsDataSource == nil) + { + _queryResultsDataSource = [[OCDataSourceArray alloc] init]; + + [_queryResultsDataSource setVersionedItems:_processedQueryResults]; + } + } + + return (_queryResultsDataSource); +} + #pragma mark - Change Sets - (void)setHasChangesAvailable:(BOOL)hasChangesAvailable { diff --git a/ownCloudSDK/ownCloudSDK.h b/ownCloudSDK/ownCloudSDK.h index e6483f03..72402f05 100644 --- a/ownCloudSDK/ownCloudSDK.h +++ b/ownCloudSDK/ownCloudSDK.h @@ -207,6 +207,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import #import #import