From 254cbacc369d7678ddce401d2db19150a4b39f24 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Wed, 21 Jun 2023 10:10:24 +0200 Subject: [PATCH] V12.0 Release (data sources, drives API, ocis support and more) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - reopen develop PR * - OCHostSimulator: add auth-race-condition host simulator, to test handling of race conditions in Authorization * - OCCoreNetworkMonitorSignalProvider: add logging - OCNetworkMonitor: add logging - OCAuthenticationMethodOAuth2/OIDC: no longer treat network errors during token refresh as permanently failed refresh * [feature/openssl-1.1.1] Migration to OpenSSL 1.1.1 (#95) - migrated code to new OpenSSL 1.1.1 API - using OpenSSL package via SwiftPM * - remove OpenSSL 1.0.x includes, build scripts and binaries * - NSError+OCNetworkFailure: add .isNetworkTimeoutError convenience property - NSError+OCError: add new error for request timeouts - OCConnection - make better use of the error parameter in HTTP response handling to actions - increase timeout for COPY requests to 10 minutes - OCSyncActionCopyMove: improve error handling for HTTP timeouts - OCHostSimulator+Builtin: add new "action-timeout-simulator" that responds to action requests with timeout errors * - OCSyncActionCopyMove/Localizable.strings: add missing localizations * - change to keep the PR open * - replace calls of malloc with calloc * - move core.add-accept-language-header class setting from "Privacy" to "Connection" * - OCExtensionManager: - add class settings support including dynamic documentation - allow blocking addition and querying of OCExtensions by identifier * V12.0: Datasources, Spaces / Drives / Graph API support (#92) * - update CONFIGURATION.json with latest changes and additions * - OCClassSettings+Metadata: fix sorting of possible values * - OCHTTP: - clarify and extend redirection policies: - "forbidden" becomes "handle locally" - "validate connection" - triggering the connection validator - becomes the new default - extended redirection support in OCHTTPRequest: - new maximumRedirectionDepth property to indicate maximum number of redirections to follow - new redirectionHistory property keeping track of NSURLs queried as part of redirection handling - OCConnection: - new status validation method +validateStatus:; replace all instances of checks for maintenance mode with it - add Connection Validator that kicks in if a HTTP Request with "ValidateConnection" redirection policy receives a redirection response - demote OCConnectionTransparentTemporaryRedirect option from "advanced" to "debug", turn off by default - OCHostSimulator - move OCHostSimulator+CookieRedirect from ownCloudMocking to ownCloudSDK/OCHostSimulator+BuiltIn - add new "simple-apm" cookie redirection simulating APM Host Simulator Extension and rebuild documentation - OCCore: - enable cookie support by default * - OCAuthenticationMethod: - switch from detection URLs to detection requests - use Detection ID to consolidate requests prior to performing them - authentication methods now perform a PROPFIND rather than a GET request on the WebDAV endpoint - when contacting the bare WebDAV endpoint, the URL is now slash-terminated - Connection Validator - new error code for failed validation - preparations for second stage of validation if first stage fails (but commented out for now as it's not clear that's needed and would complicate the implementation by multiplying the possible outcomes) - code comment describing how the Connection Validator works - handle failed validation like maintenance mode, but with custom error in status line - add "recovering-apm" Host Simulator that makes bogus redirects for the first 30 seconds, then becomes a "simple-apm" that wants to set cookies * - OCConnection: make Connection Validator more thread-safe, add rate limiter to avoid excessive retries * - OCBookmark: - allow setting the internally tracked _lastUsername with new method - OCAuthenticationMethodOAuth2 + OCAuthenticationMethodOpenIDConnect: - add headers to prefill user name where available and supported by the server - comment out support for OAuth2 due to crashes in the web view in Simulator and on device * - OCConnection: - implement Connection Validator II - following the latest flow diagram - update code comments explaining the structure - factor out the Connection Validator to its own method * - OCConnection: Connection Validator cleanup * - OCConnection: Connection Validator comment clarifications * - OCConnection + OCHTTPPipeline: add debug output for cancellation of non-critical requests - OCProcessManager: add additional observation and debug output for tracking app/extension state * - OCClassSettings: add "categoryTag" generation support * - OCHTTP improvements - centralize management of X-Request-ID header in OCHTTPRequest - replace all header field strings with OCHTTPHeaderFieldNames - extended and more uniform log messages to make following a request through the OCHTTPPipeline much easier - move the majority of debug messages to the verbose level - log old and new X-Request-ID when recreating it, to allow connecting the dots - avoid requesting the full header dictionary when just interested in a single one - log type (Bearer, Basic, …) from Authorization header and only replace the actually confidential part with "[redacted]" - add debugging description to OCHTTPRequest - OCHTTP bugfixes - ensure that only one representation (object or data) is kept in an OCHTTPPipelineTask at a time, to avoid inconsistencies - drop request data if requestID is changed - OCLogger - migrate privacy mask and log level to OCClassSetting + observation - fix bug where log level changes didn't propagate across processes - OCClassSettings - fix bugs related to change notifications timing - implement cross-process notifications for user settings - General - move frequently logged debug messages that are very specific and aren't needed 98% of the time to the verbose logging level * - Turn off HTTP request and response logging toggle by default * - OCConnection: - limit connection validation to same host (fixes conflict with redirects / multi-tenancy) - increase security of "allow same host" redirect policy by also requiring same HTTP scheme - OCCore: -trackItemAtPath:trackingHandler: no longer makes multiple nil calls if an item is not found - OCLogger: avoid log.log-enabled-components validation error during logger startup - OCUser: also archive/unarchive _forceIsRemote - project: update env vars to take lack of http request/response logging into account - unit tests: - fix failing tests - tag remaining tests that still fail * - OCLogger: fix recursive possible values assignment for OCClassSettingsMetadataKeyPossibleValues - OCHTTPPipeline: check signal requirements for final requests only if there are any signals actually required by the request - or the handler for the partition the request belongs to is around - OCHostSimulator+BuiltIn: fix incorrect block passing resulting in requestWithCookiesHandler never being called - unit tests: - fix remaining failing tests - comment out deactivated tests * - additional unit test fixes / adaptions to Bitrise performance * OAuth2: - add new method -postProcessAuthenticationDataDict: to condense the stored token response to the needed essentials, saving space and steering clear of issues if the token response contains null values that make secret serialization to property list format fail - factor out access to clientID and clientSecret into methods - make -sendTokenRequestToConnection:… subclass able - improved error handling if property list serialization fails OIDC: - add support for OpenID Connect Dynamic Client Registration - on by default for servers offering the endpoint - including support for expiration, preservation and caching - add additional error code for client registration failure HTTP Pipeline: - factor out User-Agent template composition method to make it available - extend OCHTTPRequest with JSON-specific method to easily instantiate POST requests with JSON payload * Update CONFIGURATION.json with latest OIDC additions * - Authentication: add new OCAuthenticationMethodRequiredUsernameKey option - OAuth2 / OIDC: add support for new OCAuthenticationMethodRequiredUsernameKey option * - OCClassSettings: fix crash that could occur if overrideSettings was mutated while being used * Class Settings cleanup: - remove "connection-" and "log-" in front of class setting names for OCConnection and OCLogger to avoid duplication like "connection.connection-.." - update schemes to use the new names - update CONFIGURATION.json with the changes - replace configuration documentation in CONFIGURATION.md with a reference to the full documentation in the ios-app repository, as reference in CONFIGURATION.md is no longer updated * - OCConnection: add dedicated handling for "403 Forbidden" responses to file download requests (enterprise#4338) - NSError+OCError: add a new OCErrorWithDescriptionFromError macro that allows creating an OCError based on another error with custom description * Add support for certificate comparisons: - OCCertificate+OpenSSL: - add nullability annotations - extend API to compare certificates - OCCertificateDetailsViewNode: - add nullability annotations - add support for different types of changes, supporting old and new values, old and new certificates (for certificate chains) - OCCertificateViewController - add support for certificate comparisons, with a "+/- Show/Hide" toolbar button baked right in - highlight changes with colors and circled +/- signs * OCCertificateViewController: only add "Done" navigation button if root view controller * - provide clearer issue title for certificate changes * OCHTTPPipeline: fix "UNKNOWN TASK" error for auto-resumed downloads/transfers - resumable downloads occur when backgrounding the app - resumable downloads would be rescheduled automatically to complete the download - by rescheduling, the request got a new X-Request-ID, but that one was never used by iOS - when iOS returned the response for the resumed download, it came back with the OLD X-Request-ID, resulting in the Pipeline not being able to recognize the request - result was hung downloads at the pipeline level - fixed: now avoids recreating the X-Request-ID for resumed requests * - OCBookmark: add additional diagnostic action that overwrites the certificate with an empty data blob, prompting a certificate warning dialog on next connect * OpenID Connect: for token refresh, use the client_id and client_secret used at the time the token was registered in the Authorization header, while using the latest registered client_id / client_secret as parameters * ownCloudUI: show "Show ±" only for certificate diffs with actual differences * [feature/mdm+branding] SDK version for MDM+Branding app branch (#78) * - OCConnection+Setup: - remove support for auto-detection of OC installations in "owncloud" subfolder - no longer alert user of root redirects if they happen on the same host with the same scheme (http/https) - OCClassSettings: - add new DictionaryArray and URLString types - add validation support for new types * OCClassSettings+Metadata: - also include registered metadata when determining available keys for a settings identifier * - OIDC Dynamic Client Registration: add support for non-expiring client_id/client_secret pairs via convention * Add 11.5 section to CHANGELOG.md * - OCConnection+Setup: rewrite redirect handling during setup with full duplicate control and permanent removal of support for subfolder installs in `/owncloud/`. * OCConnection: - Connection Validator: infinite loops detection: - increments a counter for the URL whose response triggered a connection validation - decrements the counter when receiving a response from the same URL that no longer would trigger a connection validation (bringing it back to 0, ideally) - if the response to the URL triggered validation more than 3 times, the response is delivered instead of triggering validation - Connection Setup: - count how often an URL was the target of a redirect during setup - if a URL was already targeted more than 0 times (adjustable), declare detection of the server root URL as failed - HTTP Pipeline: - turn certificate missing log message from error into warning, since it is not handled as an error internally, either and can be confusing when debugging * - OCConnection+Setup: only accept redirects with status 301 to other root folders, fail for all others * - OCAuthenticationMethod: add additional userInfo key to redirection errors to indicate from where a redirection started - OCAuthenticationMethodOpenIDConnect: - add redirection detection for .well-known/openid-configuration - improved error handling - OCBookmark+Diagnostics: add "Use Origin URL as URL" action to reset multi-tenancy redirection info in bookmarks - OCConnection: - fix recovery from answered redirection warning issues - add support for redirections to other hosts (previously a hard error) - OCCore: - pick up certificate rejection errors from OCHTTPPolicyBookmark and present them to the user - remove unused attemptConnect method variant - OCHTTPPolicyBookmark: improved description of errors where a certificate was changed but could not be auto-accepted - OCCertificateDetailsViewNode: fix a crasher caused by unconverted object types stored in .previousValue * - Ocean: adapt to SDK API changes, including unit tests - OCConnection: add fallback option to extractBaseURLFromRedirectionTargetURL:… to return redirectionTargetURL in case no common base path could be found - OCAuthenticationMethod: replace local fallback with above fallback * - AuthenticationTests: additional test cases for redirection base discovery * - OCConnection: support for cleaning up additional redirection target format (+ additional unit tests) * Add OCLock set of APIs for expiring, coordinated cross-process locks (+ unit tests) * - OCLock / OCLockManager: make keepAlive more opportunistic to optimize resource usage - OCAuthenticationMethodOAuth2: use OCLock to secure token refreshs and avoid issue (3) reported in ios-app#886 * - OCHTTPStatus: new .name property to quickly get the name of a status - OCHTTPPipeline: new HTSum-tagged brief one-line summary of HTTP requests and responses (to be extended) - OCConnection+Setup: add base URL extraction call to redirection handling for status requests - OCConnection: Connection Validator now also checks for the same port before it starts - NSURL+OCURLNormalization: standardized URL port and scheme/host/port comparison additions, nullability annotations * OCConnection and OCConnection+Setup: change from local comparison of host and scheme to NSURL+OCURLNormalization utility method. * - OCLogger: fix migration of log level + privacy masking (broken following shortening of the identifiers earlier) * - OCLogger: add migration path for user setting "log.log-level" to "log.level" * - improve OCLogger migration code structure * - NSDictionary+OCExpand - NSDictionary category to expand flat keys into hierarchies - implemented unit test in MiscTests - OCClassSettingsFlatSourceManagedConfiguration - add detection of changes pushed via MDM and posting OCClassSettingsManagedSettingsChanged notification - utilize NSDictionary+OCExpand to support simplified notations of MDM configurations * - OCBookmarkManager+ItemResolution: add new method to find the OCBookmark that contains a provided OCLocalID - OCCoreManager+ItemResolution: add new method to request OCCore and OCItem for a provided OCLocalID (build upon OCBookmarkManager+ItemResolution) * - OCCoreManager+ItemResolution: fix nullability annotation * Add unit test for ItemRetrieval method(s). * - OCConnection+Avatars: clarify the method name as it returns data, not the avatar itself - OCResource - initial draft of a universal resource retrieval, caching and version management subsystem - design target is to also support local resource generation (like QuickLook thumbnails) and a unified memory + DB cache across all resource types - start README.md on this subsystem to create documentation about the concepts along the way * - OCResource: add concept of a structure description (lesson from thumbnail caching) * - change to reopen PR * - Class Settings: add additional implementation check for settings snapshot - OCLogger: use all OCClassSettings implementing classes when putting together summary * - NSArray+ObjCRuntime: support additional ways to filter class list - OCClassSettings: add new, optional includeInLogSnapshot method to opt into the log snapshot (part of log intro) - OCClassSettings+Documentation: returning all classes that opted into snapshots via snapshotClasses - OCLogger: use OCClassSettings.snapshotClasses as basis - other classes: use opt-in macro * - OCConnection: update OCConnectionRenewedCertificateAcceptanceRule to allow root certificate change from DST Root CA X3 / R3 to ISRG Root X1 / R3 - add unit tests for LetsEncrypt root certificate change * - OCKeyValueStorage: add APIs for new semantics for shared instances based on URL + identifier and owner - OCVault: uses new shared semantics to create its KVS, to avoid rapid recreation in contexts where OCVault is only used temporarily, to gain access to its KVS * - extend changelog with changes for 11.6.1 * - fix crash that occurred on devices if classes were checked for protocol conformance using a regular method call, which triggered class initialization, which - especially for WebKit classes - led to a crash. - update CONFIGURATION.json with latest changes * - Change to keep PR open * OCCore: add class setting to allow configuration of time interval between end of one scan for changes to the next scan for changes * - OCDAVRawResponse: container for capturing raw WebDAV PROPFIND responses - add update scan timing logging - add support for update scan interval (and disable it for now) - add support for infinite PROPFIND requests, introduce OCPropfindDepth - OCSQLiteDB: add method to queue blocks on the DB thread - OCXMLParser: add support for live processing of parsing results and add further abstraction for error and parse result handling - OCBookmark, OCVault: add methods to prepopulate account databases from infinite PROPFIND responses - various smaller improvements * - OCCore+ItemList: scan for changes no longer uses the background URL session to avoid APM redirect issues * Update CHANGELOG.md for 11.7 * - OCHTTP: add support for streaming responses as NSInputStream (+ prepare for more variants) - OCConnection: add support for streaming PROPFIND responses - OCVault+Prepopulation: add methods for streaming metadata and parsing the stream at the same time - OCBookmark+Prepopulation: add method for streaming prepopulation * - OCCapabilities: prepare support for new "supportsInfinitePropfind" capability - OCHTTPStatus: correct spelling of Method Not Allowedc status code * - add localizable strings for account prepopulation progress messages * - OCClassSettings improvements - extend -keysForClass from documentation to --keysForClass:options: for wider use - settingsSnapshotForClasses now uses -keysForClass:options: instead of trying to compile a definitive list of keys itself - adapt calling code accordingly * - fix DAVRawResponseTests comment error * - support for custom poll interval for changes: - through capabilities: mirroring changes from https://github.com/owncloud/client/pull/8777 - through MDM via `core.scan-for-changes-interval` - in milliseconds - defaulting to 10 seconds - enforcing (and logging a warning) for intervals below a minimum of 5 seconds - logging a warning for intervals greater than 60 seconds * - improved configurable scan for changes poll interval - determine effective poll interval only once / initialized core - limit logging of warnings and errors to once / initialized core - improve wording and verbosity of log messages - clean up code * - rename OCResourceCache to OCResourceStore and add retrieve/store method definiton - fix build errors in OCConnection+Avatars - add OCResourceMetadata type - extend OCResource definition in preparation for storage/retrieval in OCResourceStore - add resources table definition to OCDatabase - outlook: plan is to limit resource subsystem to avatars at first, then eventually migrate thumbnails over * - Browser Session Class: add AWBrowser to simplify configuration for AirWatch browser * - no longer output "computed: ''" entries for class settings in the LogIntro if it is the only entry for that MDM parameter * - OCAuthenticationMethodOAuth2: use UUIDString instead of UUID description for lock name - OCCore+FileProvider: add handling for edge case when the database is not available or not open, preventing a hang - OCCore+ItemList: implement coordinated scan for changes - synchronizes scans for changes across processes - prioritizes scans, giving the app highest and the fileprovider second highest priority - consolidate related log messages under ScanChanges tag (including PollForChanges and UpdateScan) - OCLock: add support for trying to acquire a lock and immediately returning with the result, with a new OCErrorLockInvalidated error code in case the lock couldn't be acquired - OCDatabase: add .isOpened property - OCSQLiteDB: disable statement caching in minimum memory configuration * - change "refresh_token" to "refresh token" in English Localizable.strings. * - OCLocale: modular localization system replacing direct system localization calls - OCLocaleFilter: layers that strings run through during localization to allow transformations - ClassSettings: allows overriding individual strings with replacements provided through a dictionary from class settings - Variables - allows replacing {{variables}} with values, or dynamic content from "variable sources" - shared instance allows addition of sources and changing variables at runtime - provides generally useful variables like app.name - change OCLocalized() set of macros to call OCLocale, not NSLocalizedString/NSBundle directly * - OCLocale: - fix bug when replacing placeholders with sources - add support for passing additional variables via options (+ OCLocalizedFormat macro) * - OCLocaleFilterVariables: add missing "{{" and "}}" in search term for replacements * - OCCapabilities: add support for dav > propfind > depth_infinity capability * - OCItemPolicyProcessorVacuum: fix wrong metadata type for OCClassSettingsKeyItemPolicyVacuumSyncAnchorTTL (bool -> integer) * - update CONFIGURATION.json - OCAuthenticationMethodOAuth2: add support for passing username to OAuth2 page - OCBookmark: add .serverLocationUserName property to aid with server location functionality, including re-authentication - OCClassSettingsFlatSourceEnvironment: add support for JSON dictionary values - OCServerLocator: - extensible layer that allows to locate the target server URL from a username - OCServerLocatorWebFinger: server location using web finger and custom relation http://webfinger.owncloud/rel/server-instance - OCServerLocatorLookupTable: server location using a lookup table and patterns - OCHostSimulator: - add new "web-finger" simulator simulating a webfinger server providing server instance relation information - OCConnection: - add OCConnectionSetupOptionKey type - allow to pass a username to -prepareForSetupWithOptions: via OCConnectionSetupOptionUserName - setup: add support for / integration with OCServerLocator - OCHTTPRequest: - log application/jrd+json bodys in clear text * - OCSQL: - add collation support via new OCSQLiteCollation class, making it as simple as possible to encapsulate and add collations, avoiding string conversions where possible - add collation OCSQLiteCollationLocalized (OCLOCALIZED) for "Finder-like" sorting - OCDatabase+Schema: upgrade schema for metadata to use OCLOCALIZED for item name - OCLogger: fix duplicate use of tags in _OC_RLOG macro * - added authentication-oauth2.oidc-fallback-on-client-registration-failure, defaulting to true, to allow the automatic fallback to default client_id / client_secret if OpenID Connect Dynamic Client Registration fails with any error * OAuth2: - store token expiration timespan - if stored token expiration timespan < (safetyMargin + 20) seconds, no longer preemptively refreshes the token within the safety margin * - reset OCCapabilities to latest version * - add NSError+OCErrorTools with new method to determine if an error is an authentication error - OCConnection+Compatibility: improve error handling, adding handling for "Unauthorised" errors - OCConnection: remove authenticated WebDAV request asking root WebDAV endpoint for D:supported-method-set, instead rely on capabilities to respond with an authentication error if auth credentials are not valid. - AuthenticationTests: adjust for latest changes in OAuth2 and OCConnection connect * - OCBookmark: add first-level support for access to the user.displayName with new property userDisplayName - OCConnection: update .bookmark.userDisplayName (and save it unless it is a working copy with in-memory credentials storage) on every connect, if it was changed - OCBookmarkManager: fix possible deadlock * - Snapshot * - snapshot * - remove unused OCJobID from code * OCCore+CommandLocalModification: no longer handle failure of -startAccessingSecurityScopedResource as an error, as that may indicate the inputFileURL is not actually security scoped, not that the file can't be accessed. Fixes enterprise#4934. * - !! Snapshot !! - overhaul of Resources concept - implementation of OCResourceManager, addition of OCResourceManagerJob - support for automatic (by deallocation) and manual removal and cancellation of OCResourceRequests - add serialization/deserialization support to OCResource - OCHTTP: extend documentation, fix sorting bug, add missing status code description * - milestone: first functioning thumbnail implementation * - Resources: database storage & retrieval of retrieved resources now working - OCBookmarkManager deadlock bugfix * - add OCPlatform - add OCViewProvider + OCViewProviderContext - add OCBookmark.avatar property serialized/deserializing to/from data, utilizing OCViewProvider for maximum flexibility, but ensuring OCBookmark can always be serialized/deserialized even if avatar can't be deserialized - make OCAvatar a subclass of OCImage - cleanup designed OCAvatar usages - making progress, thumbnails and avatars now functional through OCResourceManager * - OCResources - add new OCResourceSourceAvatarPlaceholders - add new OCResourceTextPlaceholder - add per-session and expiry triggers for avatars - add new error code OCErrorResourceDoesNotExist that forces clearing the cache and restart a resource manager job - add missing parts - OCUser: add localizedInitials accessor and class method - OCBookmark: - add .user property with a full copy of the OCUser instance from OCConnection.loggedInUser - provide fallback OCResourceTextPlaceholder for .avatar if none is set - OCBookmarkManager: preserve existing OCBookmark instances on reload whenever possible - OCConnection: keep bookmark.user up-to-date * - OCBookmark: add more details to documentation regarding OCBookmark.userName - OCConnection: build WebDAV root URL using loggedInUser, bookmark.user, only then bookmark.userName, addressing https://github.com/owncloud/enterprise/issues/4924 * - add OCBookmark.user property, storing the last retrieved version of OCConnection.loggedInUser - use OCBookmark.user to compose WebDAV endpoint path (fixing https://github.com/owncloud/enterprise/issues/4924 ) * - OCCoreItemListTask: stop processing if core has shut down in the meantime * - OCDatabase - Schemas: delete existing thumbnails from old thumbnails table - Versions: bump database version number to block repopulation of thumbnails table when using old versions - remove old thumbnails API - OCCore: remove old thumbnails code * - remove unused OCResourceSourceItemIcons * - remove OCResourceSourceItemIcons.h from SDK header * - OCCore: remove thumbnail APIs - OCDatabase: remove thumbnail API - OCItem: remove unused .thumbnail property - OCResourceSourceAvatarPlaceholders: upgrade priority from local fallback to instant - OCResourceManager: detect duplicate resource source additions - OCResourceSourceItemThumbnails: utilize item.thumbnailAvailability information to avoid unnecessary requests * - OCConnection+GraphAPI: start of graph API support - OCConnection: add OCConnectionEndpointIDGraph - OCGraph: - OCGraphContext: context to pass around during graph parsing - OCGraphData+Decoder: decodes graph JSON to graph model objects - OCGraphObject: protocol for graph model classes to follow - ocapigen: YAML parser + code generator with advanced capabilities - no external dependencies - modular design allows implementation of different generators - initial implementation for Objective-C / ownCloudSDK - divides target files into segments, with support for: - locking segments - customizing code generation on a per-segment basis - GraphAPI/GeneratedTypes: initial batch of model headers and implementations, auto-generated by ocapigen * - use more distinct "namespace" prefix for Graph API model classes, switching from OCG to GA * - fix typo in header * - add OCDrive, OCQuota classes - add OCDrive, OCDriveType and OCSeed types - OCItem: add driveID - OCDatabase: - add support for storing, updating, retrieving and deleting OCDrives into a new drives table - add driveID column to metaData / item table - OCConnection+GraphAPI: fully implement drive list retrieval, based on GADrive and OCDrive - OCRecipient: refactor as OCIdentity - NSError+OCError: add new OCErrorGraphError code for packaging graph errors - GAGraphData+Decoder: add NSString -> NSURL conversion, block file URLs - code generator: also create NSSecureCoding en-/decoding code as well as a debug description implementation - OCMacros: add new OCExpandVar macro to make meaningful debug descriptions easier to write * - OCCapabilities: add support for spaces: enabled + version - OCConnection: - turn server version + edition methods into properties - add new options OCConnectionEndpointURLOptionDriveID and OCConnectionOptionDriveID to pass driveID to endpoint and retrieveItems APIs - adopt new driveID options in almost all WebDAV-related methods - DirectURL: adopt new driveID option - Compatibility: add central drive switch .useDriveAPI - Tools: add support for DriveID-dependant WebDAV endpoint URL - OData: - new generalized support for requesting OData objects (and later to also create, update and delete them) - add stubs for a filter string builder for OCQueryCondition - GraphAPI: add methods to retrieve drive list and (cached) drives, use new OData APIs - GraphAPI: fix issue decoding dates without fractional seconds - OCHTTPDAVRequest: add driveID option to parsing - OCLocation: new type that encapsulates bookmarkUUID, driveID and path - OCItem: add .location property - OCMacros: generalized macros to wrap/unwrap nil values into NSNull objects - OCDatabase: add API to remove items for a particular driveID - OCVault: - add drive related methods to erase local copies for a particular drive - add method to retrieve the root URL for files of a particular driveID - adapt existing methods to take an item's .driveID into account * - add OCLocation comparison support + building tools - change APIs and internal structures to use OCLocations instead of OCPaths - adopt OCDriveID and OCLocation throughout core SDK types like OCShare, OCItem and more - fix comparison with NULL values in generated SQLite WHERE clauses - extend OCCoreItemList with drive selection * - use GADrive instead of GDrive in OCDrive API name - update types from latest yaml, add some "locked" parts in the headers that would otherwise be overwritten - ocapigen: alphabetically sort forward declared types and includes so they don't change order and cause unnecessary changes * - OCItem: add driveID to debug description - OCDrive: - add isSubstantiallyDifferentFrom method to quickly determine if the drive description itself has changed in a substantial way (f.ex. id, type, name, davRootURL) - add rootLocation property returning the root folder (/) location - OCDatabase: - add drive "seed" column - OCCore: - replace polling of webDAV root for eTag changes with polling of drive list (+ eTag comparison) - OCCore+CommandLocalImport: include driveID into placeholderItem - OCSyncActionCopyMove: include driveID into placeholderItem - OCSyncActionCreateFolder: include driveID into placeholderItem - OCBookmark: add bookmark-capabilities support, feature flags that can be set, queried and removed - OCConnection+GraphAPI: update .drives list every time a new, valid list is retrieved via -retrieveDriveListWithCompletionHandler: - OCConnection+Compatibility: use bookmark capability OCBookmarkCapabilityDrives to determine .useDriveAPI property value - OCConnection: set drives capability in bookmark during connect - GADrive: change eTag from NSString to OCFileETag * - update GA classes from latest libre-graph * - OCDatabase: fix duplicate/multiple drive entries in OCDatabase - OCDrive: add .rootETag property to return root eTag (both for flexibility and convenience) - OCCore: use .rootETag from drives to trigger PROPFINDs on root folders of the spaces, including database-persistance (!) - OCSQLiteQuery: add support for INSERT OR REPLACE query construction - GADriveItem: use OCFileETag for .eTag rather than NSString directly * - initial Data Sources implementation, including initial set of unit tests - OCCore+ItemList: do not request updates from drives that do not have an ETag (assume a temporary malfunction) - fix unit tests compilation errors: convert from OCPath-based APIs to OCLocation * - NSArray+OCFiltering: utility category to filter arrays efficiently using a block - Data Sources progress - CONCEPT.md: gives an overview over the thinking and structure behind data sources - OCCore+DataSources: APIs for core-provided data sources - OCDataItemPresentable: new container class storing an intermediate, presentable representation of an item - OCDataRenderer: - added "path finder" for auto-assembly of converters from one object type to another, using the smallest number of steps - added unit tests for path finding algorithm - OCDataSourceArray: implementation - OCDataSource: refine subscription API - OCDataSourceSubscription: add setNeedsUpdateHandling change aggregation pattern, allowing users to provide a dispatch queue to perform the change notification on - OCDrive: - add OCDataItem and OCDataItemVersion suopport - add converter to OCDataItemPresentable * - OCDataItemCellConfiguration: helper class that allows to temporarily attach additional information alongside OCDataItemReferences, where it can't be passed otherwise - OCDataSource: add UUID property * - OCDataItemCellConfiguration: add hostViewController property * - remove OCDataCellConfiguration (replaced by CollectionViewCellConfiguration in ownCloudAppShared) * - NSArray+OCFiltering: helper methods to quickly filter and find objects in arrays - OCDataTypes: add types for filter and sort comparators on OCDataSourceItemReferences and OCDataItemRecords - OCDataSourceArray: use same object for @synchronized internal state modification as OCDataSource - OCDataSourceComposition: - new data source type that composes its content from one or more other OCDataSources - supports filtering and sorting - of every contributing data source individually - of the item set composed from all data sources - GraphAPI type updates * - OCResource - new request OCResourceRequestDriveItem and source OCResourceSourceDriveItems for DriveItems (from spaces) - new resource type: OCResourceTypeDriveItem - new resource: OCResourceText to store textual resources - OCData - OCDataItemPresentable: add intermediate resources retrieval support (subject to change) - OCDrive: - add support for providing OCDataItemPresentable resources for cover image and readme - add support for space description ("subtitle" in web UI) - add type for drive aliases - Graph API - GADrive: add method to retrieve special items - GARemoteItem: use eTag type - GASpecialFolder: add GASpecialFolderName type and add GASpecialFolderNameReadme and GASpecialFolderNameImage * - OCConnection+Avatars: use parsed content type - OCConnection: remove .supportsPreviewAPI and support for thumbnail API - OCCore+DataSources: clarify the data sources are read-only - OCDataItemPresentable: change API to return requests to resources, rather than the resource themselves - OCDrive: adapt to OCDataItemPresentable changes * - 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) * - OCConnection: only accept images in return to thumbnail requests (working around https://github.com/owncloud/ocis/issues/3558) - OCCore: include virtual drives in hierarchicDrivesTopLevelItems - OCResource subsystem: - fix bug that prevented retrieval of resources other than item thumbnail and avatars from cache - fix bug that could mix resources of different types that shared the same ID * - OCConnection: - add virtual preview API endpoint - remove legacy thumbnail endpoint support - OCImage: fix logical error that led to wrong/inferior resolutions being picked - GAGraphContext: comment out designed methods for now, to avoid warnings * - add enum annotations for Swift 5 * - additional Swift 5 enum adaptions - OCConnection: return new OCErrorMissingDriveID + log a warning when a command without drive ID is attempted on a drive-based account * - new class: OCVaultLocation - provides an abstract description of an item's location in a vault - can en- and decode OCVFSItemIDs - OCVault - add support to map OCVaultLocations to NSURLs - and back - add additional class methods to retrieve specific container root URLs - add filesystem layout overview to header file - OCVFS (WIP) - virtual file system implementation - supports virtual folders and mapping OCQuerys and OCLocations via OCVFSNodes - returns OCVFSContent to describe contents of a folder * - OCItem: add .customIdentifier1 and .customIdentifier2 to temporarily store two references/IDs that aren't serialized - OCVault: fix URL <-> OCVFSItemID conversion bugs - OCVFS: make advances in implementation to drive a first File Provider implementation * - OCVault: - add APIs to store, retrieve and track drives, including in a new detached state (for drives that were removed on the server) - add support for OCVFS ID conversion hooks - change File Provider signaling to adopt OCVFSItemID - add isVirtual flag to OCVaultLocation to allow referencing a drive root mapping to an OCVFSNode - OCVFS - refine ID addressing scheme and implement support for it across OCVaultLocation and OCVFS - specify the OCVFSItem protocol and implement it for OCItem and OCVFSNode - differentiate auto-updating and self-updating OCVFSContent via .isSnapshot property - add methods to OCVFSCore to replace all nodes and request a drive root based on its OCLocation - OCDrive: add properties for detached state tracking, make value comparisons in isSubstantiallyDifferentFrom nil-aware - OCItem: replace customIdentifier1/2 with non-serialized .bookmarkUUID property - OCDatabase: remove support and schema for drives table- NSArray+OCMapping: utility category that adds methods to transform arrays into sets and dictionaries - OCCore+ItemList: switch to vault as source for drives to scan periodically - OCCore: clean up drive APIs, make them pass through to OCVault - OCConnection+Compatibility: add workaround for broken legacy version in ocis beta/nightly - OCDataSourceKVO: new data source class that is updated via KVO observation of another object, with immediate filtering/conversion capabilities. Supports both regular data items and versioned data items. * - ocapigen: - add support for primitive item types (=> i.e. strings) to resolve warnings and properly dearchive all properties - fix occurrences of "NSArray.class.class" - update GA types with fixes by running the generator against the latest yaml file - OCVault.h: add info on subfolder used by TUS uploads * - OCItem: add OCItemPropertyNameDriveID to allow custom queries targeting specific drives - OCDatabase: add mapping for OCItemPropertyNameDriveID to driveID database column - OCCore: add support for drive IDs to -[OCCore trackItemAtLocation:…] - OCDrive: - turn OCDriveType into a string enum - add .isDeactivated property - GADeleted: add type GADeletedState for .state - OCHTTPRequest: add size of URL-based request body to debug output * - OCAction: simple encapsulation class for actions that can originate from anywhere (SDK, app, …) - OCDataTypes: add types for Message (TBD) and Action - OCDataSource: add OCDataSourceState type as a simple way to convey the status of the underlying mechanism of a data source - OCQuery: update .queryResultsDataSource.state from OCQuery.state * - OCAction: extend with identifier, version and type (regular/destructive) properties for a richer representation and more flexibility to control updates - OCAuthenticationMethod: add convenience class method localizedNameForAuthenticationMethodIdentifier to return the name of an authentication method - OCAuthenticationMethodOpenIDConnect: add "select_account" to prompt to ensure the account switcher is shown for users that are already logged in - Data sources: - rename OCDataItemVersion protocol to OCDataItemVersioning since Swift can't tell the protocol and the type apart otherwise - add new OCDataItemTypeView for views - add ordering detection to OCDataSourceSubscription and report it as change as well * - OCLocation: add simple en-/decoding to/from data and UTI data type ID - OCQuery: fix typo in .rootItem description * - OCDataSourceComposition - new methods to add, insert and remove sources dynamically - new method to include/exclude added sources * - OCDataSourceComposition: efficiency change: only update composition if include status of a source actually changed * - NSArray+OCMapping: add method -arrayUsingMapper: - NSDate+OCDateParser: add method -localizedStringWithTemplate:locale: for flexible localized date representations - OCQueryCondition: add userInfo property to support adding additional meta data (such as by the ownCloudApp framework for UI purposes) * Add app provider support - OCAppProvider: encapsulates URLs, retrieved raw app lists, parsed apps and file types - OCAppProviderApp: encapsulates apps and provides metadata, including the icon as URLItem resource - OCAppProviderFileType: encapsulates file types and provides metadata, including the icon as URLItem resource - OCCapabilities: support for on-demand parsing of app_providers into OCAppProvider instances, convenience access to latest supported app provider version - OCConnection: - support for the new app provider endpoints in the endpoint API - new methods to retrieve app list, create new documents and open documents in web apps - OCCore: new KVO-observable, managed .appProvider property for convenience access to the latest supported app provider New URLItem resource type: - intended for caching remote, URL-based resources with variable caching intervals Miscellaneous improvements: - new OCMIMEType and OCFileExtension types - fix DataSourceTests - add ocis target to OCTestTarget and stub app provider endpoint test (needs server support to fully implement) * - OCHTTPDAVRequest: implement support for "Prefer: return=minimal" to take advantage of https://github.com/cs3org/reva/pull/3222 - OCItem+OCTypeAlias: add support for (MIME) type aliases directly into the SDK - new OCTypeAlias type - new methods for conversion from OCMIMEType to OCTypeAlias - new OCItemPropertyNameTypeAlias and support in the SQL Builder - add new typeAlias column to metadata, implement v16 of the metaData table schema and an efficient migration with progress reporting - OCSQLiteDB: add -dropTableSchemas method to release table schemas when they are no longer needed, allowing to free memory held by migration blocks in the schemas, take advantage in OCDatabase * - OCConnection: add support for "open" app providers endpoint - OCAppProviderApp: add type to support app provider view mode (view, read, write) - OCConnection+AppProviders: add support for "open" endpoint, with mode support and returning a ready-to-use URL request - NSError+OCISError: add support for parsing `{ code: "…", message: "…" }`-style ocis errors into NSError objects - NSError+OCError: add new error codes to support NSError+OCISError - NSDictionary+OCFormEncoding: factor out code to encode a dictionary of parameters as a PUT/POST body data * - OCSyncActionDelete: fix potential crash bug * - add new OCDataItemTypeSavedSearch data type * - OCCore - add new data source .subscribedDrivesDataSource for subscribed drives - add support for deactivated drives - do not return a drive as subscribed when it was deactivated on the server - automatically reload custom queries if the list of subscribed drives changes - mark items of detached drives as deleted, change their detachedStatus from .new to .itemsRemoved - OCDrive: better documentation of OCDriveDetachedStates and additional state OCDriveDetachedStateItemsRemoved - OCMacros: add OCWaitOnCompletion to execute a block on completion of the OCWait - OCDatabase: add methods to remove (mark as deleted) or purge (actually remove) the cache items for a whole drive - OCVault: - add two new internal notifications for when the subscribed and detached drive lists change respectively - add new wipe mechanism - has wipeContainer locations inside .rootURL and .filesRootURL - fs items (files/folders) to be wiped are moved into these folders, then removed - all items inside wipeContainers are removed eventually (f.ex. on opening a bookmark, the app, etc.) - new method to erase a drive (both files + from cache in DB) * - OCVault: improve error handling and fileop debug logging * - fix wrong type warning in implementation * - update GraphAPI to libre-grap-api v0.17.0 * - OCDrive: no longer pass through space type as presentable subtitle, only actual drive descriptions - OCLogger: add new "replace-newline" option that replaces newlines with "\n" by default, disable (then unneeded) "single-lined" by default * - OCHTTP: add support for 425 status - OCItem: - add parsing support for 425 status multi-responses - add OCItemState signalling 425 status as .serverSideProcessing * - OCHTTPStatus: add textual representation for 425 status * - OCConnection+AppProviders: fix error pass through from HTTP to completion handler * - OCCore+ItemList: add missing error handling when retrieving drive list; drives now no longer vanish when connections fail or are note possible * - OCCore+ItemList: recognize OCItem.state changes as relevant change for updating the item and its versionSeed * - add all missing German translations in Localizable.strings * - OCConnection+Download: add error code + error message for files returning 425 on download * - add missing localization of string "Retry" * - OCCore+SyncEngine: avoid publishing the same sync activity twice * - OCCore+ItemList: - FIX: add .previousPath to renamed items where that info was previously missing - FIX: ensure that recomposed paths after a move end in a "/" for folders - OCCore+ItemUpdates: update OCQuery.queryLocation when the observed location is moved * - OCVFSCore: guard nodes against enumeration during mutation (which could cause crashes) * - OCCapabilities: add convenience accessor .federatedSharingSupported with temporary workaround for https://github.com/owncloud/ocis/issues/4788 - OCCore+Sharing: update share queries also in case of errors - NSError+OCHTTPStatus: add method to quickly check if an error is a HTTP status code - OCImage: use QOS_CLASS_DEFAULT instead of QOS_CLASS_USER_INITIATED * - fix Xcode 14 warnings * - OCShareRole: encapsulates information on sharing roles for display - OCCore: add new APIs to retrieve available sharing roles for a location, as well as identify the one matching an OCShare - OCSymbol: OCSymbolName type and class for accessing SF Symbols, adding an abstraction layer that simplifies access and allows centralized customization down the road - OCLocation: new OCLocationType, also usable as mask, for file, folder, drive and account - OCShare: - migrate from OCItemType to OCLocationType - add conversion for OCShareType<->OCShareTypesMask - XML parsing support for extracting drive ID information to form a complete OCLocation * - OCStatistic: - new class to provide structured statistical data from the SDK to clients like the app - implements OCDataItem and OCDataItemVersioning, so could also be used in data sources as items in the future - OCDataSource: - change implementation of .specialItemReferences to .specialItems, allowing to directly pass OCDataItems from source to subscribers - add new OCDataSourceSpecialItemFolderStatistics - OCDrive: change type of .quota to GAQuota - OCQuota: add missing OCQuotaState labels - OCQuery: add .queryResultsDataSourceIncludesStatistics to request that statistics be provided through the query's data source * - OCDrive: implement .name override for "Personal" and "Shares" spaces * - OCSyncActionCopyMove: prevent copying and moving of items into themselves * - OCConnection+AppProviders: add OCConnection.staticHeaderFields to the header fields used in the initial request when opening a web app in-app (typically adding "Accept-Language" to that request) - OCConnection + OCHTTPRequest: use header field data type rather than different NSDictionary types * - OCDrive: - add new property "specialType" to easily determine if the drive is the personal or shares jail space - or a regular project space - detect shares jail not by "virtual" type but by UUID - adapt .name to use .specialType to return hardcoded names * - OCLocale+SystemLanguage: new category providing the "Accept-Language" string as well as the primary language of the user - OCCore: replace local code with OCLocale+SystemLanguage to fill Accept Language static header field - OCConnection+AppProviders: add "lang=" parameter if the primaryUserLanguage can be determined. Fixes the issue that the UI of web apps was always presented in English, irrespective of the user's system language. * - OCConnection+AppProviders: return tailored error message and OCError code for 425 responses to open-web and open endpoint requests * - NSError+OCISError: add support for code TOO_EARLY - OCConnection+AppProvider: hardcode to always use the SDK's own 425 error rather than the error that may be returned from the server as JSON (=> which would not be localized) * - Data Sources: - Types: add new item types for OCBookmark and OCLocation - Presentable: add .image property for easier rendering of small images (f.ex. for a toolbar) - OCDataSourceArray: provide convenience initializer method to pass content at initialization - OCDataSourceMapped: new kind of data sources that allows to map an input data source's items to any kind of items that are dynamically created, updated and removed to match the contents of the source datasource - OCAuthenticationBrowserSessionCustomScheme: move workaround to open URLs from extensions to class-level method to make it generally available - OCLocation: adopt OCDataItem + OCDataItemVersioning - OCBookmark: adopt OCDataItem + OCDataItemVersioning - OCBookmarkManager: provide a data source with the bookmarks - OCAsyncSequentialQueue: add convenience initializer allowing to pass a dispatch queue to create an executor from * - OCDataSource: - make OCDataSourceKVO available outside the SDK - call -terminateSubscription for all active subscriptions on deallocation - OCQuery: change signal for setting data source state to Loading or Idle so empty folders no longer indicate Loading forever - replace calls to [UIImage systemImageNamed:] with OCSymbol calls * - OCAction: add support and a property for ActionProperties, clarify OCActionOption as OCActionRunOption - OCDataSource: add method to signal updated items by reference efficiently * - GAGroup: update to libre-graph-api spec v1.0.0 * - OCAction: new type .warning and closed extensibility attribute - OCResourceManager: add support for status 425 (log and return error) - OCDataSourceMapped: support changing source and, by setting it to nil, calling the destroyer on all mapped items * - OCConnection: add support for cross-space COPYs - OCCore: - add new async API to retrieve the cached item for an OCLoation - enrich OCLocation with .bookmarkUUID in more places - OCUser: crash fix and fallback algorithm if the OS does not return initials for a name - OCItem: return bookmarkUUID as part of the .location - OCLocation: relax equal comparison: if the driveID is identical, but only one of the OCLocations has a bookmarkUUID, assume it is the same - OCCertificateStore: store for certificates, preparing for future usage by OCBookmark * - OCCertificateStore: complete implementation - OCBookmark: - replace plain certificate property with OCCertificateStore and build migration path from existing bookmarks - add diagnostics support for multiple certificates - add new .primaryCertificate property that returns the certificate matching the bookmark's hostname - or - the only certificate in the store if there is only one - OCConnection: adapt bookmark building to use OCCertificateStore - OCHTTPPolicyBookmark: - add support for handling certificates for multiple hostnames using a bookmark's Certificate Store - implement option connection.associated-certificates-tracking-rule to opt-in certificates other than the server's main certificate into change tracking/recognition - OCHTTPRequest: add property .hostname for quick access to .effectiveURL/.url hostname * - OCDataSourceMapped: fix subscription leak on deallocation - OCDataSource: add new subscription observer API allowing to dynamically start/stop updating content when there is or isn't demand * - OCCore+DataSources: - make names of existing data sources clearer - add new data sources for: favorite, available offline item policies, available offline files - implement dynamic polling logic (currently used for favorites) - prepare for additional data sources for different sharing aspects - OCItemPolicy: - add UUID with fallbacks - implement OCDataItem + OCDataItemVersioning support, including a small converter to OCDataItemTypePresentable - use .locationString instead of .path in conditions for collections - OCItemPolicyProcessorAvailableOffline: add support for drives/spaces - OCDataTypes: add ItemPolicy type - OCItem: add .locationString property - OCDatabase: add new locationString column to metadata database, updating the schema * - add missing headerfile OCItemPolicy+OCDataItem.h to target * - OCBookmark: add Favorites capability - OCCapabilities: add support for favorites support capability - OCConnection: update bookmarks if its Favorites capability changed - OCCore+DataSources: - clean up naming - add additional on-demand data sources for "sharedWithMe" OCShares - OCShare: - rename OCShareStateRejected to OCShareStateDeclined to match wording used elsewhere - add OCDataItem + OCDataItemVersioning support - add support for composing FileID in Share Jail from ShareID - OCDataTypes: add Share data type - OCDataSource/Mapped/Composition: add content synchronization support via dispatch groups and different subscriptions types - OCDrive: add MountPoint drive type - libre-graph-api: add new classes and update existing classes via code generation * - OCDataSourceArray: add internal tracking of OCDataItem versions to allow detecting changes to identical objects - OCCore+OCDataSource: take advantage of OCDataSourceArray.trackItemVersions to detect changes to OCShares - OCShare: include cloud share .accepted state into data item version * - address Xcode 14 thread priority inversion warnings at runtime * - OCConnection+Sharing: - add support for drives - add context in which an OCShare was received from the server (shared with me/shared by me) - OCShare: - add .category property and OCShareCategory type - add support for grouping shares - further enhance Drive ID / File ID logic, describe difficulties with the current approach in https://github.com/owncloud/ocis/issues/5355 - OCCore+DataSources: - use the contents of the Shares Jail for "Accepted Shares" instead of the OCShares - implement on-demand data sources for sharedByMeDataSource and sharedByLinkDataSource - return grouped and sorted shares from all OCShare data sources - OCDrive: add root ETag to debug output * - OCCore+DataSources: add .shareJailQueryCustomizer to allow applying filtering and sorting options to the underlying OCQuery for shareWithMeAcceptedDataSource * - upgrade project version to Xcode 14.2 * - add OCClassSettingsFlatSourcePostBuild to allow injection of post-build class settings * - rename OCClassSettingsKeyPostBuildAllowedFlatIdentifiers to OCClassSettingsKeyPostBuildAllowedSettings * - OCDataSourceMapped: fix curiosity crash bug where a weak var is initialized with nil during deallocation * - OCDataSource: - fix auto-completion typo/error that lead to initial updates on subscriptions being ignored and the value of trackDifferences being used instead - fix performIntialUpdate -> performInitialUpdate typo * - fix issue where OC10 accounts showed up empty in the File Provider * - address static analyzer findings (potential crashes and memory leaks) * - OCDrive: change label from Shares jail from "Shares" to "Shared with me" * - OCClassSettingsFlatSourcePostBuild : fix type error leading to a crash * - make OCResourceSourceURLItems and OCResourceRequestURLItem public API * - OCLocation: also transfer .bookmarkUUID when creating a copy of an instance * - OCBookmarkManager: reply on OCBookmark.dataItemVersion to trigger updates to bookmarksDatasource * - OCAuthenticationMethod - broaden API to allow passing options in more methods - add additional option keys to - pass along a Web Finger Account Lookup URL - skip WebDAV-endpoint WWW-Authenticate based tests for authentication method availability (OCAuthenticationMethodSkipWWWAuthenticateChecksKey) - limit which authentication methods are allowed - BasicAuth + OAuth2: add support for OCAuthenticationMethodSkipWWWAuthenticateChecksKey - OIDC:implement alternative logic when OCAuthenticationMethodSkipWWWAuthenticateChecksKey is passed as option - OCConnection - add support for option OCAuthenticationMethodAllowedMethods - add support for WebFinger as status.php alternative during account setup - add support for retrieving available OCServerInstances after authentication via authenticated WebFinger - add support passing back options in the -prepareForSetupWithOptions completionHandler - OCServerInstance - new class to encapsulate server-instance specific information when creating new bookmarks / during account setup - convenience accessor for localized server title based on localized title dictionary retrieved from the server - OCBookmark+ServerInstance: convenience method to apply a server instance's content to an OCBookmark - adapt tests to past bookmark.certificate API changes and recent authentication API changes * - upgrade project from Xcode 13.3 to 14.2 - bring OCConnection+Setup webfinger + authenticated webfinger code in line with enterprise#5579 - send hosts root URL as relation to webfinger in first request - send acct:me@{host} as relation to webfinger in second request - add "Location:" header with URL of server pointing to IdP when requesting /.well-known/openid-configuration - extend and clean up NSURL+OCURLQueryParameterExtensions * - OCConnection+AppProviders: ensure ISO-639-1 (uses only 2 characters) by cutting off any differentiators (f.ex. "en-GB" becomes "en") * - OCBookmarkManager: ensure _bookmarksDatasource is updated after loading bookmarks * - OCExtensionContext: in convenience initializer, create new instance from called class, not OCExtensionContext * - OCConnection: Accept-Language now correctly formats language codes with region/country variant additions, addressing https://github.com/owncloud/ios-app/issues/1193 * - OCConnection: - add and implement new option OCAuthenticationMethodWebFingerAlternativeIDPKey to store and re-use information on an alternative IDP address - use OCAuthenticationMethodWebFingerAlternativeIDPKey with response returned from WebFinger - extend APIs where necessary to properly support OCAuthenticationMethodWebFingerAlternativeIDPKey - OCAuthentictionMethod: - extend APIs where necessary to properly support OCAuthenticationMethodWebFingerAlternativeIDPKey - tolerate lack of user_id in token responses in context of WebFinger account lookup, as user_id may depend on the respective instance the user chooses - OCBookmark: use _lastUsername as a fallback for auth method + auth data derived user_name * - OCConnection+Setup: prevent erroneously triggering connection validation if a server doesn't support webfinger lookup * - OCLocation: add new isDriveRoot property to determine if a location is a drive's root Address https://github.com/owncloud/ios-app/issues/1188 with the following changes: - OCSyncRecord: - add new .removed property to use instead of .recordID when removing a sync record - OCDatabase: change to cache sync records ONLY within the Sync Engine, to avoid any possible concurrency issues - SyncEngine: - make use of OCSyncRecord.removed property - handle exceptions during processing - log sync action and exception in detail - send OCIssue to the UI informing the user - terminate and remove the action from the queue, so they can't disrupt the client further * - OCCore: use vault to resolve identifiers to drives, remove internal _drivesByID variable, which was initialized from the vault, but then not kept up-to-date and caused issues * - OCAuthenticationMethodOpenIDConnect: allow changes to "prompt" via class settings * - NSDate+OCDateParser: add shared ISO8601 date formatters to parse/compose dates issued by/accepted by oCIS sharing - OCConnection+Sharing: adapt date formatting for oCIS - OCCore+DataSources: make OCShares shared by the user available as flat data source and as a data source of grouped OCShares - OCCore+Sharing: fix bug preventing OCShareRoles from being setup - OCRecipientSearchController: make recipients available as data source - OCDataTypes: add item types for OCShareRole and OCIdentity - OCIdentity: make OCDataItem + OCDataVersioning compatible - OCShareRole: make OCDataItem + OCDataVersioning compatible * - OCAppIdentity: add support for FileProviderUIExtension app component - OCCore+DataSources: sort shared-by-me shares by location + recipient * - OCItem: add .parentPath property - OCDatabase: add SQLBuilder support for .parentPath * - OCConnection+Sharing: return a descriptive error when a share create/modify/delete request fails due to a missing internet connection * - OCConnection+Sharing: categorize item, item with reshares and subitems as "Shard By Me" - OCCore+DataSources: add .sharesSortComparator for consistent OCShare sorting - OCCore: remove unneeded item from matchingShareRoleFor: method - OCShareQuery: add dataSource property to allow tracking OCShareQuery results via an OCDataSource * - change wording from "internet connection" to "active connection" * - OCCore+DataSources: neuter sharedWithMeAcceptedDataSource to return only share objects, to allow management in the face of https://github.com/owncloud/ocis/issues/5355 ; preserve working code for another timeline * - OCShare: add "Internal" permission - OCCore+Sharing, OCShareRole: add "Invited persons" role for drive-based servers * - Xcode project: add GitTags and GitBranch info to Info.plist - OCItem.h: add comment for experimental Deniable permission * - OCConnection+Setup: add debug logging and avoid premature deallocation of instance * - OCCore: - add .personalDrive property - add personalDrive.driveID to legacy root locations in OCQuerys when started on a drive-based account - OCCore+Sharing: trigger update polls for accepted and pending cloud shares when accepting a share - OCCore+DataSources: - convert sharedWithMe data source from array to composed data sources - add accepted and pending remote/federated shares to sharedWithMe and derived data sources - OCShare: add .effectiveState to simplify accept/pending/declined handling across federated and local shares - OCShareQuery: make .dataSource track item versions * - OCMacros: add OCLocaliz… --- CHANGELOG.md | 1 + ownCloudSDK.xcodeproj/project.pbxproj | 1349 ++++++++++++++++- .../xcshareddata/xcschemes/Ocean.xcscheme | 2 +- .../xcschemes/ownCloudSDK.xcscheme | 2 +- ownCloudSDK/Actions/OCAction.h | 63 + ownCloudSDK/Actions/OCAction.m | 86 ++ ownCloudSDK/Activity/OCActivityUpdate.h | 2 +- ownCloudSDK/App Identity/OCAppIdentity.h | 1 + ownCloudSDK/App Identity/OCAppIdentity.m | 15 +- ownCloudSDK/App Providers/OCAppProvider.h | 62 + ownCloudSDK/App Providers/OCAppProvider.m | 163 ++ ownCloudSDK/App Providers/OCAppProviderApp.h | 52 + ownCloudSDK/App Providers/OCAppProviderApp.m | 103 ++ .../App Providers/OCAppProviderFileType.h | 50 + .../App Providers/OCAppProviderFileType.m | 60 + ...AuthenticationBrowserSessionCustomScheme.h | 1 + ...AuthenticationBrowserSessionCustomScheme.m | 17 + .../OCAuthenticationMethod+OCTools.h | 2 + .../OCAuthenticationMethod+OCTools.m | 5 + .../Authentication/OCAuthenticationMethod.h | 11 +- .../Authentication/OCAuthenticationMethod.m | 7 +- .../OCAuthenticationMethodBasicAuth.m | 18 +- .../OCAuthenticationMethodOAuth2.h | 6 +- .../OCAuthenticationMethodOAuth2.m | 85 +- .../OCAuthenticationMethodOpenIDConnect.h | 1 + .../OCAuthenticationMethodOpenIDConnect.m | 163 +- ownCloudSDK/Bookmark/OCBookmark+DataItem.h | 28 + ownCloudSDK/Bookmark/OCBookmark+DataItem.m | 44 + ownCloudSDK/Bookmark/OCBookmark+Diagnostics.m | 20 +- ownCloudSDK/Bookmark/OCBookmark.h | 21 +- ownCloudSDK/Bookmark/OCBookmark.m | 177 ++- .../Foundation/NSArray+OCFiltering.h | 31 + .../Foundation/NSArray+OCFiltering.m | 50 + .../Categories/Foundation/NSArray+OCMapping.h | 34 + .../Categories/Foundation/NSArray+OCMapping.m | 74 + .../Foundation/NSArray+ObjCRuntime.m | 2 +- .../Foundation/NSDate+OCDateParser.h | 3 + .../Foundation/NSDate+OCDateParser.m | 86 +- .../NSURL+OCURLQueryParameterExtensions.h | 14 +- .../NSURL+OCURLQueryParameterExtensions.m | 15 + .../Connection/Capabilities/OCCapabilities.h | 12 + .../Connection/Capabilities/OCCapabilities.m | 100 ++ .../GraphAPI/OCConnection+GraphAPI.h | 42 + .../GraphAPI/OCConnection+GraphAPI.m | 121 ++ ownCloudSDK/Connection/NSError+OCISError.h | 31 + ownCloudSDK/Connection/NSError+OCISError.m | 81 + .../Connection/OCConnection+AppProviders.m | 424 ++++++ .../Connection/OCConnection+Authentication.m | 15 +- ownCloudSDK/Connection/OCConnection+Avatars.m | 95 ++ .../Connection/OCConnection+Compatibility.m | 16 +- .../Connection/OCConnection+Recipients.m | 12 +- ownCloudSDK/Connection/OCConnection+Setup.m | 293 +++- ownCloudSDK/Connection/OCConnection+Sharing.m | 119 +- ownCloudSDK/Connection/OCConnection+Tools.m | 102 +- ownCloudSDK/Connection/OCConnection+Upload.m | 19 +- ownCloudSDK/Connection/OCConnection.h | 72 +- ownCloudSDK/Connection/OCConnection.m | 345 +++-- .../Connection/OData/OCConnection+OData.h | 36 + .../Connection/OData/OCConnection+OData.m | 108 ++ ownCloudSDK/Connection/OData/OCODataTypes.h | 27 + .../OData/OCQueryCondition+ODataBuilder.h | 30 + .../OData/OCQueryCondition+ODataBuilder.m | 29 + .../OCCore+AvailableOffline.m | 28 +- ownCloudSDK/Core/Claims/OCCore+Claims.m | 2 +- .../Core/Data Sources/OCCore+DataSources.h | 56 + .../Core/Data Sources/OCCore+DataSources.m | 1084 +++++++++++++ ownCloudSDK/Core/DirectURL/OCCore+DirectURL.m | 3 +- ownCloudSDK/Core/Favorites/OCCore+Favorites.m | 2 +- ownCloudSDK/Core/ItemList/OCCore+ItemList.h | 4 +- ownCloudSDK/Core/ItemList/OCCore+ItemList.m | 246 ++- .../Core/ItemList/OCCoreDirectoryUpdateJob.h | 5 +- .../Core/ItemList/OCCoreDirectoryUpdateJob.m | 6 +- ownCloudSDK/Core/ItemList/OCCoreItemList.h | 4 + ownCloudSDK/Core/ItemList/OCCoreItemList.m | 48 + .../Core/ItemList/OCCoreItemListTask.h | 4 +- .../Core/ItemList/OCCoreItemListTask.m | 52 +- .../Core/ItemPolicies/OCCore+ItemPolicies.m | 2 +- .../ItemPolicies/OCItemPolicy+OCDataItem.h | 28 + .../ItemPolicies/OCItemPolicy+OCDataItem.m | 67 + ownCloudSDK/Core/ItemPolicies/OCItemPolicy.h | 7 +- ownCloudSDK/Core/ItemPolicies/OCItemPolicy.m | 56 +- .../OCItemPolicyProcessorAvailableOffline.m | 54 +- .../OCItemPolicyProcessorDownloadExpiration.m | 2 +- .../OCItemPolicyProcessorVersionUpdates.m | 2 +- .../Core/NameConflicts/OCCore+NameConflicts.h | 2 +- .../Core/NameConflicts/OCCore+NameConflicts.m | 8 +- ownCloudSDK/Core/OCCore+Internal.h | 11 + ownCloudSDK/Core/OCCore+ItemUpdates.h | 2 +- ownCloudSDK/Core/OCCore+ItemUpdates.m | 112 +- ownCloudSDK/Core/OCCore.h | 96 +- ownCloudSDK/Core/OCCore.m | 415 ++++- .../Avatars/OCResourceRequestAvatar.h | 29 + .../Avatars/OCResourceRequestAvatar.m | 45 + .../OCResourceSourceAvatarPlaceholders.h | 29 + .../OCResourceSourceAvatarPlaceholders.m | 72 + .../Avatars/OCResourceSourceAvatars.h | 29 + .../Avatars/OCResourceSourceAvatars.m | 166 ++ .../Resources/Manager/OCResourceManager.h | 62 + .../Resources/Manager/OCResourceManager.m | 565 +++++++ .../Resources/Manager/OCResourceManagerJob.h | 71 + .../Resources/Manager/OCResourceManagerJob.m | 205 +++ ownCloudSDK/Core/Resources/OCResourceTypes.h | 48 + ownCloudSDK/Core/Resources/README.md | 45 + .../Resources/Request/OCResourceRequest.h | 92 ++ .../Resources/Request/OCResourceRequest.m | 148 ++ .../Request/OCResourceRequestImage.h | 31 + .../Request/OCResourceRequestImage.m | 47 + .../Core/Resources/Resource/OCResource.h | 58 + .../Core/Resources/Resource/OCResource.m | 113 ++ .../Core/Resources/Resource/OCResourceImage.h | 42 + .../Core/Resources/Resource/OCResourceImage.m | 142 ++ .../Core/Resources/Resource/OCResourceText.h | 30 + .../Core/Resources/Resource/OCResourceText.m | 63 + .../Resource/OCResourceTextPlaceholder.h | 29 + .../Resource/OCResourceTextPlaceholder.m | 46 + .../Sources/OCDatabase+ResourceStorage.h | 28 + .../Sources/OCDatabase+ResourceStorage.m | 207 +++ .../Core/Resources/Sources/OCResourceSource.h | 68 + .../Core/Resources/Sources/OCResourceSource.m | 106 ++ .../Sources/OCResourceSourceStorage.h | 29 + .../Sources/OCResourceSourceStorage.m | 88 ++ .../OCResourceRequestItemThumbnail.h | 31 + .../OCResourceRequestItemThumbnail.m | 55 + .../OCResourceSourceItemLocalThumbnails.h | 29 + .../OCResourceSourceItemLocalThumbnails.m | 144 ++ .../OCResourceSourceItemThumbnails.h | 29 + .../OCResourceSourceItemThumbnails.m | 126 ++ .../DriveItems/OCResourceRequestDriveItem.h | 33 + .../DriveItems/OCResourceRequestDriveItem.m | 46 + .../DriveItems/OCResourceSourceDriveItems.h | 29 + .../DriveItems/OCResourceSourceDriveItems.m | 73 + .../Resources/URL-based/OCResourceSourceURL.h | 30 + .../Resources/URL-based/OCResourceSourceURL.m | 132 ++ .../URLItems/OCResourceRequestURLItem.h | 36 + .../URLItems/OCResourceRequestURLItem.m | 73 + .../URLItems/OCResourceSourceURLItems.h | 29 + .../URLItems/OCResourceSourceURLItems.m | 64 + ownCloudSDK/Core/Sharing/OCCore+Sharing.m | 310 +++- .../Sharing/OCRecipientSearchController.h | 5 +- .../Sharing/OCRecipientSearchController.m | 34 +- ownCloudSDK/Core/Sharing/OCShareQuery.h | 3 + ownCloudSDK/Core/Sharing/OCShareQuery.m | 36 +- .../Actions/CopyMove/OCSyncActionCopyMove.m | 22 +- .../CreateFolder/OCSyncActionCreateFolder.m | 1 + .../Sync/Actions/Delete/OCSyncActionDelete.m | 15 +- .../Actions/Download/OCSyncActionDownload.m | 12 +- .../Upload/OCCore+CommandLocalImport.m | 3 +- .../Sync/Actions/Upload/OCSyncActionUpload.m | 8 +- ownCloudSDK/Core/Sync/Context/OCSyncContext.h | 8 +- ownCloudSDK/Core/Sync/OCCore+SyncEngine.m | 95 +- ownCloudSDK/Core/Sync/Record/OCSyncRecord.h | 2 + .../Core/Thumbnails/OCCore+Thumbnails.h | 6 - .../Core/Thumbnails/OCCore+Thumbnails.m | 269 +--- ownCloudSDK/Data Sources/CONCEPT.md | 110 ++ .../Data Sources/Converters/OCDataConverter.h | 43 + .../Data Sources/Converters/OCDataConverter.m | 51 + .../Converters/OCDataConverterPipeline.h | 33 + .../Converters/OCDataConverterPipeline.m | 77 + ownCloudSDK/Data Sources/OCDataTypes.h | 81 + ownCloudSDK/Data Sources/OCDataTypes.m | 38 + .../Presentable/OCDataItemPresentable.h | 65 + .../Presentable/OCDataItemPresentable.m | 128 ++ .../Data Sources/Renderer/OCDataRenderer.h | 43 + .../Data Sources/Renderer/OCDataRenderer.m | 215 +++ .../Sources/Array Backed/OCDataSourceArray.h | 40 + .../Sources/Array Backed/OCDataSourceArray.m | 171 +++ .../Composition/OCDataSourceComposition.h | 53 + .../Composition/OCDataSourceComposition.m | 605 ++++++++ .../Sources/Item Records/OCDataItemRecord.h | 45 + .../Sources/Item Records/OCDataItemRecord.m | 67 + .../Sources/KVO Backed/OCDataSourceKVO.h | 34 + .../Sources/KVO Backed/OCDataSourceKVO.m | 136 ++ .../Sources/Mapped/OCDataSourceMapped.h | 39 + .../Sources/Mapped/OCDataSourceMapped.m | 188 +++ .../Data Sources/Sources/OCDataSource.h | 99 ++ .../Data Sources/Sources/OCDataSource.m | 258 ++++ .../Sources/Snapshots/OCDataSourceSnapshot.h | 38 + .../Sources/Snapshots/OCDataSourceSnapshot.m | 23 + .../OCDataSourceSubscription+Internal.h | 33 + .../OCDataSourceSubscription+Internal.m | 231 +++ .../Subscriptions/OCDataSourceSubscription.h | 60 + .../Subscriptions/OCDataSourceSubscription.m | 110 ++ ownCloudSDK/Diagnostics/OCDiagnosticNode.h | 2 +- ownCloudSDK/Drive/OCDrive.h | 99 ++ ownCloudSDK/Drive/OCDrive.m | 300 ++++ ownCloudSDK/Drive/OCQuota.h | 28 + ownCloudSDK/Drive/OCQuota.m | 24 + ownCloudSDK/Errors/NSError+OCError.h | 19 +- ownCloudSDK/Errors/NSError+OCError.m | 44 +- ownCloudSDK/Events/OCEvent.h | 9 +- ownCloudSDK/Events/OCEvent.m | 15 +- ownCloudSDK/Extensions/OCExtensionContext.m | 2 +- ownCloudSDK/Extensions/OCExtensionManager.h | 6 +- ownCloudSDK/Extensions/OCExtensionManager.m | 79 + .../ISRunLoopThread/OCRunLoopThread.m | 1 + .../Checksums/OCChecksumAlgorithmSHA1.m | 2 +- ownCloudSDK/GraphAPI/GAGraph.h | 25 + .../GraphAPI/GeneratedTypes/GAAppRole.h | 37 + .../GraphAPI/GeneratedTypes/GAAppRole.m | 75 + .../GeneratedTypes/GAAppRoleAssignment.h | 42 + .../GeneratedTypes/GAAppRoleAssignment.m | 90 ++ .../GraphAPI/GeneratedTypes/GAApplication.h | 39 + .../GraphAPI/GeneratedTypes/GAApplication.m | 73 + .../GraphAPI/GeneratedTypes/GADeleted.h | 46 + .../GraphAPI/GeneratedTypes/GADeleted.m | 72 + .../GeneratedTypes/GADirectoryObject.h | 35 + .../GeneratedTypes/GADirectoryObject.m | 69 + ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.h | 61 + ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.m | 159 ++ .../GraphAPI/GeneratedTypes/GADriveItem.h | 77 + .../GraphAPI/GeneratedTypes/GADriveItem.m | 158 ++ .../GeneratedTypes/GAEducationClass.h | 45 + .../GeneratedTypes/GAEducationClass.m | 91 ++ .../GeneratedTypes/GAEducationOrganization.h | 35 + .../GeneratedTypes/GAEducationOrganization.m | 69 + .../GeneratedTypes/GAEducationSchool.h | 36 + .../GeneratedTypes/GAEducationSchool.m | 72 + .../GraphAPI/GeneratedTypes/GAEducationUser.h | 52 + .../GraphAPI/GeneratedTypes/GAEducationUser.m | 106 ++ .../GraphAPI/GeneratedTypes/GAEntity.h | 34 + .../GraphAPI/GeneratedTypes/GAEntity.m | 66 + .../GeneratedTypes/GAFileSystemInfo.h | 36 + .../GeneratedTypes/GAFileSystemInfo.m | 72 + .../GraphAPI/GeneratedTypes/GAFolder.h | 38 + .../GraphAPI/GeneratedTypes/GAFolder.m | 70 + .../GraphAPI/GeneratedTypes/GAFolderView.h | 36 + .../GraphAPI/GeneratedTypes/GAFolderView.m | 72 + ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.h | 42 + ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.m | 82 + .../GraphAPI/GeneratedTypes/GAHashes.h | 37 + .../GraphAPI/GeneratedTypes/GAHashes.m | 75 + .../GraphAPI/GeneratedTypes/GAIdentity.h | 35 + .../GraphAPI/GeneratedTypes/GAIdentity.m | 69 + .../GraphAPI/GeneratedTypes/GAIdentitySet.h | 40 + .../GraphAPI/GeneratedTypes/GAIdentitySet.m | 76 + ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.h | 35 + ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.m | 69 + .../GraphAPI/GeneratedTypes/GAItemReference.h | 39 + .../GraphAPI/GeneratedTypes/GAItemReference.m | 81 + .../GraphAPI/GeneratedTypes/GAODataError.h | 37 + .../GraphAPI/GeneratedTypes/GAODataError.m | 74 + .../GeneratedTypes/GAODataErrorDetail.h | 36 + .../GeneratedTypes/GAODataErrorDetail.m | 72 + .../GeneratedTypes/GAODataErrorMain.h | 41 + .../GeneratedTypes/GAODataErrorMain.m | 89 ++ .../GeneratedTypes/GAObjectIdentity.h | 35 + .../GeneratedTypes/GAObjectIdentity.m | 69 + .../GraphAPI/GeneratedTypes/GAOpenGraphFile.h | 39 + .../GraphAPI/GeneratedTypes/GAOpenGraphFile.m | 73 + .../GeneratedTypes/GAPasswordProfile.h | 35 + .../GeneratedTypes/GAPasswordProfile.m | 69 + .../GraphAPI/GeneratedTypes/GAPermission.h | 38 + .../GraphAPI/GeneratedTypes/GAPermission.m | 70 + ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.h | 41 + ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.m | 78 + .../GraphAPI/GeneratedTypes/GARemoteItem.h | 62 + .../GraphAPI/GeneratedTypes/GARemoteItem.m | 125 ++ ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.h | 31 + ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.m | 63 + .../GraphAPI/GeneratedTypes/GAShared.h | 40 + .../GraphAPI/GeneratedTypes/GAShared.m | 76 + .../GraphAPI/GeneratedTypes/GASpecialFolder.h | 66 + .../GraphAPI/GeneratedTypes/GASpecialFolder.m | 90 ++ .../GraphAPI/GeneratedTypes/GATagAssignment.h | 35 + .../GraphAPI/GeneratedTypes/GATagAssignment.m | 69 + .../GeneratedTypes/GATagUnassignment.h | 35 + .../GeneratedTypes/GATagUnassignment.m | 69 + ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.h | 38 + ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.m | 70 + ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.h | 51 + ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.m | 103 ++ ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.h | 48 + ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.m | 13 + .../GraphAPI/ManualTypes/OCGIdentity.h | 23 + .../GraphAPI/ManualTypes/OCGIdentity.m | 23 + .../GraphAPI/ManualTypes/OCGIdentitySet.h | 23 + .../GraphAPI/ManualTypes/OCGIdentitySet.m | 24 + .../GraphAPI/ManualTypes/OCGItemReference.h | 17 + .../GraphAPI/ManualTypes/OCGItemReference.m | 13 + ownCloudSDK/GraphAPI/ManualTypes/OCGObject.h | 33 + ownCloudSDK/GraphAPI/ManualTypes/OCGObject.m | 23 + .../GraphAPI/Parser Support/GAGraphContext.h | 36 + .../GraphAPI/Parser Support/GAGraphContext.m | 23 + .../Parser Support/GAGraphData+Decoder.h | 41 + .../Parser Support/GAGraphData+Decoder.m | 157 ++ .../GraphAPI/Parser Support/GAGraphObject.h | 38 + ownCloudSDK/HTTP/OCHTTPTypes.h | 2 +- ownCloudSDK/HTTP/Pipeline/OCHTTPPipeline.m | 11 +- .../HTTP/Policy/OCHTTPPolicyBookmark.m | 64 +- .../Request/NSDictionary+OCFormEncoding.h | 29 + .../Request/NSDictionary+OCFormEncoding.m | 40 + ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.h | 3 +- ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.m | 11 +- ownCloudSDK/HTTP/Request/OCHTTPRequest.h | 7 +- ownCloudSDK/HTTP/Request/OCHTTPRequest.m | 41 +- ownCloudSDK/HTTP/Response/OCHTTPResponse.h | 4 +- ownCloudSDK/HTTP/Response/OCHTTPResponse.m | 22 +- .../HTTP/Status/NSError+OCHTTPStatus.h | 1 + .../HTTP/Status/NSError+OCHTTPStatus.m | 10 + ownCloudSDK/HTTP/Status/OCHTTPStatus.h | 2 + ownCloudSDK/HTTP/Status/OCHTTPStatus.m | 8 + ownCloudSDK/{Share => Identity}/OCGroup.h | 0 ownCloudSDK/{Share => Identity}/OCGroup.m | 0 ownCloudSDK/Identity/OCIdentity+DataItem.h | 28 + ownCloudSDK/Identity/OCIdentity+DataItem.m | 38 + .../OCRecipient.h => Identity/OCIdentity.h} | 14 +- .../OCRecipient.m => Identity/OCIdentity.m} | 22 +- ownCloudSDK/{Share => Identity}/OCUser.h | 7 +- ownCloudSDK/{Share => Identity}/OCUser.m | 86 +- ownCloudSDK/Image Types/Avatars/OCAvatar.h | 37 + ownCloudSDK/Image Types/Avatars/OCAvatar.m | 55 + ownCloudSDK/Image Types/Symbols/OCSymbol.h | 31 + ownCloudSDK/Image Types/Symbols/OCSymbol.m | 29 + ownCloudSDK/Issues/OCIssue.h | 6 +- ownCloudSDK/Issues/OCIssueChoice.h | 2 +- ownCloudSDK/Item/Images/OCImage.h | 31 +- ownCloudSDK/Item/Images/OCImage.m | 145 +- ownCloudSDK/Item/Images/OCItemThumbnail.h | 20 +- ownCloudSDK/Item/Images/OCItemThumbnail.m | 112 -- ownCloudSDK/Item/OCItem+OCDataItem.h | 29 + ownCloudSDK/Item/OCItem+OCDataItem.m | 87 ++ ownCloudSDK/Item/OCItem+OCFileURLMetadata.h | 10 + ownCloudSDK/Item/OCItem+OCFileURLMetadata.m | 10 + ownCloudSDK/Item/OCItem+OCTypeAlias.h | 37 + ownCloudSDK/Item/OCItem+OCTypeAlias.m | 167 ++ ownCloudSDK/Item/OCItem+OCXMLObjectCreation.m | 11 +- ownCloudSDK/Item/OCItem.h | 52 +- ownCloudSDK/Item/OCItem.m | 158 +- ownCloudSDK/Locale/OCLocale+SystemLanguage.h | 30 + ownCloudSDK/Locale/OCLocale+SystemLanguage.m | 60 + ownCloudSDK/Locale/OCLocale.h | 1 + ownCloudSDK/Locale/OCLocale.m | 5 + ownCloudSDK/Location/OCLocation.h | 79 + ownCloudSDK/Location/OCLocation.m | 424 ++++++ ownCloudSDK/Logging/OCLogger.h | 1 + ownCloudSDK/Logging/OCLogger.m | 35 +- ownCloudSDK/OCMacros.h | 10 + ownCloudSDK/OCTypes.h | 8 + ownCloudSDK/Platforms/OCPlatform.h | 43 + ownCloudSDK/Platforms/OCPlatform.m | 23 + ownCloudSDK/Protocols/OCViewProvider.h | 31 + ownCloudSDK/Protocols/OCViewProviderContext.h | 41 + ownCloudSDK/Protocols/OCViewProviderContext.m | 36 + .../Query/Condition/OCQueryCondition.h | 4 + .../Query/Condition/OCQueryCondition.m | 4 + ownCloudSDK/Query/OCQuery+Internal.h | 5 + ownCloudSDK/Query/OCQuery+Internal.m | 83 + ownCloudSDK/Query/OCQuery.h | 22 +- ownCloudSDK/Query/OCQuery.m | 30 +- .../Resource Management/OCBookmarkManager.h | 4 + .../Resource Management/OCBookmarkManager.m | 107 +- .../Resources/de.lproj/Localizable.strings | Bin 36912 -> 46022 bytes .../Resources/en.lproj/Localizable.strings | 82 +- ownCloudSDK/Security/OCCertificate.h | 2 +- ownCloudSDK/Security/OCCertificateStore.h | 43 + ownCloudSDK/Security/OCCertificateStore.m | 148 ++ .../Security/OCCertificateStoreRecord.h | 35 + .../Security/OCCertificateStoreRecord.m | 75 + .../Settings/OCClassSettings+Validation.m | 2 +- ownCloudSDK/Settings/OCClassSettings.m | 2 + .../Settings/Sources/NSDictionary+OCExpand.m | 4 +- .../OCClassSettingsFlatSourcePostBuild.h | 39 + .../OCClassSettingsFlatSourcePostBuild.m | 161 ++ .../OCClassSettingsUserPreferences.m | 8 +- ownCloudSDK/Setup/OCBookmark+ServerInstance.h | 33 + ownCloudSDK/Setup/OCBookmark+ServerInstance.m | 29 + ownCloudSDK/Setup/OCServerInstance.h | 33 + ownCloudSDK/Setup/OCServerInstance.m | 51 + ownCloudSDK/Share/OCShare+OCDataItem.h | 28 + ownCloudSDK/Share/OCShare+OCDataItem.m | 75 + .../Share/OCShare+OCXMLObjectCreation.m | 77 +- ownCloudSDK/Share/OCShare.h | 38 +- ownCloudSDK/Share/OCShare.m | 132 +- .../Share/Roles/OCShareRole+OCDataItem.h | 28 + .../Share/Roles/OCShareRole+OCDataItem.m | 38 + ownCloudSDK/Share/Roles/OCShareRole.h | 54 + ownCloudSDK/Share/Roles/OCShareRole.m | 51 + ownCloudSDK/Statistics/OCStatistic.h | 42 + ownCloudSDK/Statistics/OCStatistic.m | 58 + ownCloudSDK/TUS/OCTUSJob.h | 3 + ownCloudSDK/TUS/OCTUSJob.m | 2 + ownCloudSDK/Toolkit/OCAsyncSequentialQueue.h | 2 + ownCloudSDK/Toolkit/OCAsyncSequentialQueue.m | 7 +- ownCloudSDK/VFS/CONCEPT.md | 38 + ownCloudSDK/VFS/OCItem+OCVFSItem.h | 27 + ownCloudSDK/VFS/OCItem+OCVFSItem.m | 49 + ownCloudSDK/VFS/OCVFSContent.h | 41 + ownCloudSDK/VFS/OCVFSContent.m | 32 + ownCloudSDK/VFS/OCVFSCore.h | 71 + ownCloudSDK/VFS/OCVFSCore.m | 465 ++++++ ownCloudSDK/VFS/OCVFSNode.h | 55 + ownCloudSDK/VFS/OCVFSNode.m | 179 +++ ownCloudSDK/VFS/OCVFSTypes.h | 60 + .../Vaults/Database/OCDatabase+Schemas.h | 1 + .../Vaults/Database/OCDatabase+Schemas.m | 421 ++++- .../Vaults/Database/OCDatabase+Versions.h | 13 +- ownCloudSDK/Vaults/Database/OCDatabase.h | 23 +- ownCloudSDK/Vaults/Database/OCDatabase.m | 319 ++-- .../Collations/OCSQLiteCollationLocalized.h | 2 +- .../Vaults/Database/SQLite/OCSQLiteDB.h | 1 + .../Vaults/Database/SQLite/OCSQLiteDB.m | 11 +- .../Database/SQLite/Queries/OCSQLiteQuery.h | 1 + .../Database/SQLite/Queries/OCSQLiteQuery.m | 44 +- ownCloudSDK/Vaults/OCVault+Internal.h | 9 +- ownCloudSDK/Vaults/OCVault+Internal.m | 169 ++- ownCloudSDK/Vaults/OCVault.h | 136 +- ownCloudSDK/Vaults/OCVault.m | 891 ++++++++++- ownCloudSDK/Vaults/OCVaultDriveList.h | 33 + ownCloudSDK/Vaults/OCVaultDriveList.m | 61 + ownCloudSDK/Vaults/OCVaultLocation.h | 43 + ownCloudSDK/Vaults/OCVaultLocation.m | 108 ++ .../Prepopulation/OCVault+Prepopulation.m | 6 +- .../OCWaitCondition+Diagnostic.m | 3 +- .../Wait Condition/OCWaitConditionIssue.m | 5 +- .../OCWaitConditionMetaDataRefresh.h | 5 +- .../OCWaitConditionMetaDataRefresh.m | 42 +- ownCloudSDK/ownCloudSDK.h | 88 +- ownCloudSDKTests/AuthenticationTests.m | 2 +- ownCloudSDKTests/ConnectionTests.m | 59 +- ownCloudSDKTests/CoreRedirectTests.m | 5 +- ownCloudSDKTests/CoreSharingTests.m | 34 +- ownCloudSDKTests/CoreSyncTests.m | 150 +- ownCloudSDKTests/CoreTests.m | 148 +- ownCloudSDKTests/DataSourceTests.m | 341 +++++ ownCloudSDKTests/HostSimulatorTests.m | 2 +- ownCloudSDKTests/ItemPolicyTests.m | 4 +- ownCloudSDKTests/MiscTests.m | 2 - ownCloudSDKTests/OCTestTarget.h | 4 + ownCloudSDKTests/OCTestTarget.m | 10 + ownCloudSDKTests/SharingTests.m | 82 +- .../Code Generation/Generator/OCCodeFile.h | 43 + .../Code Generation/Generator/OCCodeFile.m | 129 ++ .../Generator/OCCodeFileSegment.h | 77 + .../Generator/OCCodeFileSegment.m | 175 +++ .../Generator/OCCodeGenerator.h | 80 + .../Generator/OCCodeGenerator.m | 343 +++++ .../Generator/ObjC/OCCodeGeneratorObjC.h | 28 + .../Generator/ObjC/OCCodeGeneratorObjC.m | 372 +++++ .../Code Generation/Schema/OCSchema.h | 38 + .../Code Generation/Schema/OCSchema.m | 73 + .../Schema/OCSchemaConstraint.h | 27 + .../Schema/OCSchemaConstraint.m | 23 + .../Code Generation/Schema/OCSchemaProperty.h | 51 + .../Code Generation/Schema/OCSchemaProperty.m | 28 + tools/ocapigen/YAML Parser/OCYAMLNode.h | 46 + tools/ocapigen/YAML Parser/OCYAMLNode.m | 61 + tools/ocapigen/YAML Parser/OCYAMLParser.h | 35 + tools/ocapigen/YAML Parser/OCYAMLParser.m | 214 +++ .../ocapigen.xcodeproj/project.pbxproj | 379 +++++ .../contents.xcworkspacedata | 7 + tools/ocapigen/ocapigen/main.m | 115 ++ 451 files changed, 29597 insertions(+), 1839 deletions(-) create mode 100644 ownCloudSDK/Actions/OCAction.h create mode 100644 ownCloudSDK/Actions/OCAction.m create mode 100644 ownCloudSDK/App Providers/OCAppProvider.h create mode 100644 ownCloudSDK/App Providers/OCAppProvider.m create mode 100644 ownCloudSDK/App Providers/OCAppProviderApp.h create mode 100644 ownCloudSDK/App Providers/OCAppProviderApp.m create mode 100644 ownCloudSDK/App Providers/OCAppProviderFileType.h create mode 100644 ownCloudSDK/App Providers/OCAppProviderFileType.m create mode 100644 ownCloudSDK/Bookmark/OCBookmark+DataItem.h create mode 100644 ownCloudSDK/Bookmark/OCBookmark+DataItem.m create mode 100644 ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.h create mode 100644 ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.m create mode 100644 ownCloudSDK/Categories/Foundation/NSArray+OCMapping.h create mode 100644 ownCloudSDK/Categories/Foundation/NSArray+OCMapping.m create mode 100644 ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.h create mode 100644 ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.m create mode 100644 ownCloudSDK/Connection/NSError+OCISError.h create mode 100644 ownCloudSDK/Connection/NSError+OCISError.m create mode 100644 ownCloudSDK/Connection/OCConnection+AppProviders.m create mode 100644 ownCloudSDK/Connection/OCConnection+Avatars.m create mode 100644 ownCloudSDK/Connection/OData/OCConnection+OData.h create mode 100644 ownCloudSDK/Connection/OData/OCConnection+OData.m create mode 100644 ownCloudSDK/Connection/OData/OCODataTypes.h create mode 100644 ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.h create mode 100644 ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.m create mode 100644 ownCloudSDK/Core/Data Sources/OCCore+DataSources.h create mode 100644 ownCloudSDK/Core/Data Sources/OCCore+DataSources.m create mode 100644 ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.h create mode 100644 ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.m create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.h create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.m create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.h create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.m create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.h create mode 100644 ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.m create mode 100644 ownCloudSDK/Core/Resources/Manager/OCResourceManager.h create mode 100644 ownCloudSDK/Core/Resources/Manager/OCResourceManager.m create mode 100644 ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.h create mode 100644 ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.m create mode 100644 ownCloudSDK/Core/Resources/OCResourceTypes.h create mode 100644 ownCloudSDK/Core/Resources/README.md create mode 100644 ownCloudSDK/Core/Resources/Request/OCResourceRequest.h create mode 100644 ownCloudSDK/Core/Resources/Request/OCResourceRequest.m create mode 100644 ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.h create mode 100644 ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.m create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResource.h create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResource.m create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceImage.h create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceImage.m create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceText.h create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceText.m create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.h create mode 100644 ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.m create mode 100644 ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.h create mode 100644 ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.m create mode 100644 ownCloudSDK/Core/Resources/Sources/OCResourceSource.h create mode 100644 ownCloudSDK/Core/Resources/Sources/OCResourceSource.m create mode 100644 ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.h create mode 100644 ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.m create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.h create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.m create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.h create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.m create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.h create mode 100644 ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.m create mode 100644 ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.h create mode 100644 ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.m create mode 100644 ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.h create mode 100644 ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.m create mode 100644 ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.h create mode 100644 ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.m create mode 100644 ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.h create mode 100644 ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.m create mode 100644 ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.h create mode 100644 ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.m create mode 100644 ownCloudSDK/Data Sources/CONCEPT.md create mode 100644 ownCloudSDK/Data Sources/Converters/OCDataConverter.h create mode 100644 ownCloudSDK/Data Sources/Converters/OCDataConverter.m create mode 100644 ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.h create mode 100644 ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.m create mode 100644 ownCloudSDK/Data Sources/OCDataTypes.h create mode 100644 ownCloudSDK/Data Sources/OCDataTypes.m create mode 100644 ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.h create mode 100644 ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.m create mode 100644 ownCloudSDK/Data Sources/Renderer/OCDataRenderer.h create mode 100644 ownCloudSDK/Data Sources/Renderer/OCDataRenderer.m create mode 100644 ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.h create mode 100644 ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.m create mode 100644 ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.h create mode 100644 ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.m create mode 100644 ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.h create mode 100644 ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.m create mode 100644 ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.h create mode 100644 ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.m create mode 100644 ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.h create mode 100644 ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.m create mode 100644 ownCloudSDK/Data Sources/Sources/OCDataSource.h create mode 100644 ownCloudSDK/Data Sources/Sources/OCDataSource.m create mode 100644 ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.h create mode 100644 ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.m create mode 100644 ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.h create mode 100644 ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.m create mode 100644 ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.h create mode 100644 ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.m create mode 100644 ownCloudSDK/Drive/OCDrive.h create mode 100644 ownCloudSDK/Drive/OCDrive.m create mode 100644 ownCloudSDK/Drive/OCQuota.h create mode 100644 ownCloudSDK/Drive/OCQuota.m create mode 100644 ownCloudSDK/GraphAPI/GAGraph.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.m create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.h create mode 100644 ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.m create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.h create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.m create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.h create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.m create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.h create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.m create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.h create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.m create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGObject.h create mode 100644 ownCloudSDK/GraphAPI/ManualTypes/OCGObject.m create mode 100644 ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.h create mode 100644 ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.m create mode 100644 ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.h create mode 100644 ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m create mode 100644 ownCloudSDK/GraphAPI/Parser Support/GAGraphObject.h create mode 100644 ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.h create mode 100644 ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.m rename ownCloudSDK/{Share => Identity}/OCGroup.h (100%) rename ownCloudSDK/{Share => Identity}/OCGroup.m (100%) create mode 100644 ownCloudSDK/Identity/OCIdentity+DataItem.h create mode 100644 ownCloudSDK/Identity/OCIdentity+DataItem.m rename ownCloudSDK/{Share/OCRecipient.h => Identity/OCIdentity.h} (85%) rename ownCloudSDK/{Share/OCRecipient.m => Identity/OCIdentity.m} (90%) rename ownCloudSDK/{Share => Identity}/OCUser.h (81%) rename ownCloudSDK/{Share => Identity}/OCUser.m (65%) create mode 100644 ownCloudSDK/Image Types/Avatars/OCAvatar.h create mode 100644 ownCloudSDK/Image Types/Avatars/OCAvatar.m create mode 100644 ownCloudSDK/Image Types/Symbols/OCSymbol.h create mode 100644 ownCloudSDK/Image Types/Symbols/OCSymbol.m create mode 100644 ownCloudSDK/Item/OCItem+OCDataItem.h create mode 100644 ownCloudSDK/Item/OCItem+OCDataItem.m create mode 100644 ownCloudSDK/Item/OCItem+OCTypeAlias.h create mode 100644 ownCloudSDK/Item/OCItem+OCTypeAlias.m create mode 100644 ownCloudSDK/Locale/OCLocale+SystemLanguage.h create mode 100644 ownCloudSDK/Locale/OCLocale+SystemLanguage.m create mode 100644 ownCloudSDK/Location/OCLocation.h create mode 100644 ownCloudSDK/Location/OCLocation.m create mode 100644 ownCloudSDK/Platforms/OCPlatform.h create mode 100644 ownCloudSDK/Platforms/OCPlatform.m create mode 100644 ownCloudSDK/Protocols/OCViewProvider.h create mode 100644 ownCloudSDK/Protocols/OCViewProviderContext.h create mode 100644 ownCloudSDK/Protocols/OCViewProviderContext.m create mode 100644 ownCloudSDK/Security/OCCertificateStore.h create mode 100644 ownCloudSDK/Security/OCCertificateStore.m create mode 100644 ownCloudSDK/Security/OCCertificateStoreRecord.h create mode 100644 ownCloudSDK/Security/OCCertificateStoreRecord.m create mode 100644 ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.h create mode 100644 ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.m create mode 100644 ownCloudSDK/Setup/OCBookmark+ServerInstance.h create mode 100644 ownCloudSDK/Setup/OCBookmark+ServerInstance.m create mode 100644 ownCloudSDK/Setup/OCServerInstance.h create mode 100644 ownCloudSDK/Setup/OCServerInstance.m create mode 100644 ownCloudSDK/Share/OCShare+OCDataItem.h create mode 100644 ownCloudSDK/Share/OCShare+OCDataItem.m create mode 100644 ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.h create mode 100644 ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.m create mode 100644 ownCloudSDK/Share/Roles/OCShareRole.h create mode 100644 ownCloudSDK/Share/Roles/OCShareRole.m create mode 100644 ownCloudSDK/Statistics/OCStatistic.h create mode 100644 ownCloudSDK/Statistics/OCStatistic.m create mode 100644 ownCloudSDK/VFS/CONCEPT.md create mode 100644 ownCloudSDK/VFS/OCItem+OCVFSItem.h create mode 100644 ownCloudSDK/VFS/OCItem+OCVFSItem.m create mode 100644 ownCloudSDK/VFS/OCVFSContent.h create mode 100644 ownCloudSDK/VFS/OCVFSContent.m create mode 100644 ownCloudSDK/VFS/OCVFSCore.h create mode 100644 ownCloudSDK/VFS/OCVFSCore.m create mode 100644 ownCloudSDK/VFS/OCVFSNode.h create mode 100644 ownCloudSDK/VFS/OCVFSNode.m create mode 100644 ownCloudSDK/VFS/OCVFSTypes.h create mode 100644 ownCloudSDK/Vaults/OCVaultDriveList.h create mode 100644 ownCloudSDK/Vaults/OCVaultDriveList.m create mode 100644 ownCloudSDK/Vaults/OCVaultLocation.h create mode 100644 ownCloudSDK/Vaults/OCVaultLocation.m create mode 100644 ownCloudSDKTests/DataSourceTests.m create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeFile.h create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeFile.m create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.h create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.m create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeGenerator.h create mode 100644 tools/ocapigen/Code Generation/Generator/OCCodeGenerator.m create mode 100644 tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.h create mode 100644 tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.m create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchema.h create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchema.m create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.h create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.m create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchemaProperty.h create mode 100644 tools/ocapigen/Code Generation/Schema/OCSchemaProperty.m create mode 100644 tools/ocapigen/YAML Parser/OCYAMLNode.h create mode 100644 tools/ocapigen/YAML Parser/OCYAMLNode.m create mode 100644 tools/ocapigen/YAML Parser/OCYAMLParser.h create mode 100644 tools/ocapigen/YAML Parser/OCYAMLParser.m create mode 100644 tools/ocapigen/ocapigen.xcodeproj/project.pbxproj create mode 100644 tools/ocapigen/ocapigen.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 tools/ocapigen/ocapigen/main.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 140099a3..3bd0877e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -183,3 +183,4 @@ - support for Share API - OAuth2 - Latest ownCloud Server API + diff --git a/ownCloudSDK.xcodeproj/project.pbxproj b/ownCloudSDK.xcodeproj/project.pbxproj index d0c2c88a..dbf5c4d8 100644 --- a/ownCloudSDK.xcodeproj/project.pbxproj +++ b/ownCloudSDK.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -50,6 +50,23 @@ DC0AE4572310793100428681 /* KeyValueStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0AE4562310793100428681 /* KeyValueStoreTests.m */; }; DC0AE4F22311C75300428681 /* OCKeyValueStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0AE4F02311C75300428681 /* OCKeyValueStack.h */; }; DC0AE4F32311C75300428681 /* OCKeyValueStack.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0AE4F12311C75300428681 /* OCKeyValueStack.m */; }; + DC0BE5B128F80BBA00CE2101 /* OCShareRole.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0BE5AF28F80BBA00CE2101 /* OCShareRole.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0BE5B228F80BBA00CE2101 /* OCShareRole.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0BE5B028F80BBA00CE2101 /* OCShareRole.m */; }; + DC0BE5B828F80DBF00CE2101 /* OCSymbol.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0BE5B628F80DBF00CE2101 /* OCSymbol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0BE5B928F80DBF00CE2101 /* OCSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0BE5B728F80DBF00CE2101 /* OCSymbol.m */; }; + DC0BE5BE28F9427900CE2101 /* OCStatistic.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0BE5BC28F9427900CE2101 /* OCStatistic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0BE5BF28F9427900CE2101 /* OCStatistic.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0BE5BD28F9427900CE2101 /* OCStatistic.m */; }; + DC0CE17B28C5DDE8009ABDFB /* OCAppProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0CE17928C5DDE8009ABDFB /* OCAppProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0CE17C28C5DDE8009ABDFB /* OCAppProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE17A28C5DDE8009ABDFB /* OCAppProvider.m */; }; + DC0CE18028C63232009ABDFB /* OCConnection+AppProviders.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE17E28C63232009ABDFB /* OCConnection+AppProviders.m */; }; + DC0CE18728C63B15009ABDFB /* OCAppProviderApp.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0CE18528C63B15009ABDFB /* OCAppProviderApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0CE18828C63B15009ABDFB /* OCAppProviderApp.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE18628C63B15009ABDFB /* OCAppProviderApp.m */; }; + DC0CE18B28C63B2E009ABDFB /* OCAppProviderFileType.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0CE18928C63B2E009ABDFB /* OCAppProviderFileType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0CE18C28C63B2E009ABDFB /* OCAppProviderFileType.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE18A28C63B2E009ABDFB /* OCAppProviderFileType.m */; }; + DC0CE19628C8907D009ABDFB /* OCResourceRequestURLItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0CE19428C8907D009ABDFB /* OCResourceRequestURLItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0CE19728C8907D009ABDFB /* OCResourceRequestURLItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE19528C8907D009ABDFB /* OCResourceRequestURLItem.m */; }; + DC0CE19A28C89227009ABDFB /* OCResourceSourceURLItems.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0CE19828C89227009ABDFB /* OCResourceSourceURLItems.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC0CE19B28C89227009ABDFB /* OCResourceSourceURLItems.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0CE19928C89227009ABDFB /* OCResourceSourceURLItems.m */; }; DC114A9422A7A87C00CBD597 /* NSData+OCRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = DC114A9222A7A87C00CBD597 /* NSData+OCRandom.h */; }; DC114A9522A7A87C00CBD597 /* NSData+OCRandom.m in Sources */ = {isa = PBXBuildFile; fileRef = DC114A9322A7A87C00CBD597 /* NSData+OCRandom.m */; }; DC114A9822A7AA2E00CBD597 /* NSString+OCRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = DC114A9622A7AA2E00CBD597 /* NSString+OCRandom.h */; }; @@ -113,6 +130,8 @@ DC20DE5121BFCEB00096000B /* OCLogToggle.m in Sources */ = {isa = PBXBuildFile; fileRef = DC20DE4F21BFCEB00096000B /* OCLogToggle.m */; }; DC20DE5921C013290096000B /* ownCloudMocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA8922720F5F13E00AEFF98 /* ownCloudMocking.framework */; }; DC20DE8F21C104DE0096000B /* OCLogTag.h in Headers */ = {isa = PBXBuildFile; fileRef = DC20DE8D21C104DE0096000B /* OCLogTag.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2218C228228F7000808BCE /* OCVFSContent.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2218C028228F7000808BCE /* OCVFSContent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2218C328228F7000808BCE /* OCVFSContent.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2218C128228F7000808BCE /* OCVFSContent.m */; }; DC22669A22817DC600FB29EE /* OCVault+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DC22669822817DC600FB29EE /* OCVault+Internal.h */; }; DC22669B22817DC600FB29EE /* OCVault+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = DC22669922817DC600FB29EE /* OCVault+Internal.m */; }; DC2266A82282BC8100FB29EE /* OCBookmark+IPNotificationNames.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2266A62282BC8100FB29EE /* OCBookmark+IPNotificationNames.h */; }; @@ -138,8 +157,12 @@ DC27BBC0230498C3002CC2F8 /* OCHTTPCookieStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27BBBE230498C3002CC2F8 /* OCHTTPCookieStorage.m */; }; DC27BBC32304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.h in Headers */ = {isa = PBXBuildFile; fileRef = DC27BBC12304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC27BBC42304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27BBC22304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.m */; }; + DC28F823294B6DE600AC4013 /* OCItemPolicy+OCDataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC28F821294B6DE600AC4013 /* OCItemPolicy+OCDataItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC28F824294B6DE600AC4013 /* OCItemPolicy+OCDataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC28F822294B6DE600AC4013 /* OCItemPolicy+OCDataItem.m */; }; DC29F4C224323C4900347658 /* OCMessageTemplate.h in Headers */ = {isa = PBXBuildFile; fileRef = DC29F4C024323C4900347658 /* OCMessageTemplate.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC29F4C324323C4900347658 /* OCMessageTemplate.m in Sources */ = {isa = PBXBuildFile; fileRef = DC29F4C124323C4900347658 /* OCMessageTemplate.m */; }; + DC2A127628D05DD30088A2B7 /* OCItem+OCTypeAlias.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2A127428D05DD20088A2B7 /* OCItem+OCTypeAlias.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2A127728D05DD30088A2B7 /* OCItem+OCTypeAlias.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2A127528D05DD30088A2B7 /* OCItem+OCTypeAlias.m */; }; DC2AA57022DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2AA56E22DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC2AA57122DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2AA56F22DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.m */; }; DC2AA57922DDD005001D5C39 /* OCSyncActionLocalCopyDelete.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2AA57722DDD005001D5C39 /* OCSyncActionLocalCopyDelete.h */; }; @@ -193,6 +216,11 @@ DC35969722403E5B00C4D6E6 /* OCQueryCondition+SQLBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC35969522403E5B00C4D6E6 /* OCQueryCondition+SQLBuilder.m */; }; DC35969A2240EC0A00C4D6E6 /* OCQueryCondition+Item.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3596982240EC0A00C4D6E6 /* OCQueryCondition+Item.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC35969B2240EC0A00C4D6E6 /* OCQueryCondition+Item.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3596992240EC0A00C4D6E6 /* OCQueryCondition+Item.m */; }; + DC36EC7C27B5362800967483 /* OCConnection+OData.h in Headers */ = {isa = PBXBuildFile; fileRef = DC36EC7A27B5362800967483 /* OCConnection+OData.h */; }; + DC36EC7D27B5362800967483 /* OCConnection+OData.m in Sources */ = {isa = PBXBuildFile; fileRef = DC36EC7B27B5362800967483 /* OCConnection+OData.m */; }; + DC36EC8127B560D600967483 /* OCQueryCondition+ODataBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = DC36EC7F27B560D600967483 /* OCQueryCondition+ODataBuilder.h */; }; + DC36EC8227B560D600967483 /* OCQueryCondition+ODataBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC36EC8027B560D600967483 /* OCQueryCondition+ODataBuilder.m */; }; + DC36EC8527B5611500967483 /* OCODataTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DC36EC8327B5611500967483 /* OCODataTypes.h */; }; DC381FC722C80BA400284699 /* OCCore+NameConflicts.h in Headers */ = {isa = PBXBuildFile; fileRef = DC381FC522C80BA400284699 /* OCCore+NameConflicts.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC381FC822C80BA400284699 /* OCCore+NameConflicts.m in Sources */ = {isa = PBXBuildFile; fileRef = DC381FC622C80BA400284699 /* OCCore+NameConflicts.m */; }; DC381FCB22C8146F00284699 /* NSString+NameConflicts.h in Headers */ = {isa = PBXBuildFile; fileRef = DC381FC922C8146F00284699 /* NSString+NameConflicts.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -207,6 +235,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 */; }; @@ -222,6 +252,19 @@ DC3F2B51204AED8400189B9A /* OCMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3F2B50204AED8300189B9A /* OCMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3FE4BA229BD424002E009C /* OCCoreDirectoryUpdateJob.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3FE4B8229BD424002E009C /* OCCoreDirectoryUpdateJob.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3FE4BB229BD424002E009C /* OCCoreDirectoryUpdateJob.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3FE4B9229BD424002E009C /* OCCoreDirectoryUpdateJob.m */; }; + DC41C79025EA5F7A0074F23B /* OCResourceSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C78E25EA5F7A0074F23B /* OCResourceSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C79125EA5F7A0074F23B /* OCResourceSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C78F25EA5F7A0074F23B /* OCResourceSource.m */; }; + DC41C7A625EA61D70074F23B /* OCResourceTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7A425EA61D70074F23B /* OCResourceTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7BA25EA62520074F23B /* OCResourceSourceItemThumbnails.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7B825EA62520074F23B /* OCResourceSourceItemThumbnails.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7BB25EA62520074F23B /* OCResourceSourceItemThumbnails.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C7B925EA62520074F23B /* OCResourceSourceItemThumbnails.m */; }; + DC41C7CD25EA627A0074F23B /* OCResourceRequestItemThumbnail.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7CB25EA627A0074F23B /* OCResourceRequestItemThumbnail.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7CE25EA627A0074F23B /* OCResourceRequestItemThumbnail.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C7CC25EA627A0074F23B /* OCResourceRequestItemThumbnail.m */; }; + DC41C7DC25EA62CD0074F23B /* OCResourceSourceAvatars.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7DA25EA62CD0074F23B /* OCResourceSourceAvatars.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7DD25EA62CD0074F23B /* OCResourceSourceAvatars.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C7DB25EA62CD0074F23B /* OCResourceSourceAvatars.m */; }; + DC41C7EF25EA62E40074F23B /* OCResourceRequestAvatar.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7ED25EA62E40074F23B /* OCResourceRequestAvatar.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7F025EA62E40074F23B /* OCResourceRequestAvatar.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C7EE25EA62E40074F23B /* OCResourceRequestAvatar.m */; }; + DC41C7FD25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.h in Headers */ = {isa = PBXBuildFile; fileRef = DC41C7FB25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC41C7FE25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.m in Sources */ = {isa = PBXBuildFile; fileRef = DC41C7FC25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.m */; }; DC434D0A20D5AA9E00740056 /* OCCore+CommandCreateFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC434D0920D5AA9D00740056 /* OCCore+CommandCreateFolder.m */; }; DC434D0E20D68C3000740056 /* NSString+OCPath.h in Headers */ = {isa = PBXBuildFile; fileRef = DC434D0C20D68C3000740056 /* NSString+OCPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC434D0F20D68C3000740056 /* NSString+OCPath.m in Sources */ = {isa = PBXBuildFile; fileRef = DC434D0D20D68C3000740056 /* NSString+OCPath.m */; }; @@ -230,8 +273,64 @@ DC446C8A206B8FBF00189B9A /* ISRunLoopThread.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DC446C89206B8FBF00189B9A /* ISRunLoopThread.LICENSE */; }; DC45ABAE231018250065669D /* OCKeyValueRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = DC45ABAC231018250065669D /* OCKeyValueRecord.h */; }; DC45ABAF231018250065669D /* OCKeyValueRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = DC45ABAD231018250065669D /* OCKeyValueRecord.m */; }; + DC46F3C42843C66B00038880 /* OCAction.h in Headers */ = {isa = PBXBuildFile; fileRef = DC46F3C22843C66B00038880 /* OCAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC46F3C52843C66B00038880 /* OCAction.m in Sources */ = {isa = PBXBuildFile; fileRef = DC46F3C32843C66B00038880 /* OCAction.m */; }; DC47DF762770CEE300989D84 /* NSError+OCErrorTools.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47DF742770CEE300989D84 /* NSError+OCErrorTools.h */; }; DC47DF772770CEE300989D84 /* NSError+OCErrorTools.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47DF752770CEE300989D84 /* NSError+OCErrorTools.m */; }; + DC47E4BF27A5820D0020E8EF /* GASpecialFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49127A5820C0020E8EF /* GASpecialFolder.m */; }; + DC47E4C027A5820D0020E8EF /* GAUser.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49227A5820C0020E8EF /* GAUser.m */; }; + DC47E4C127A5820D0020E8EF /* GAQuota.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49327A5820C0020E8EF /* GAQuota.m */; }; + DC47E4C227A5820D0020E8EF /* GAGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E49427A5820C0020E8EF /* GAGroup.h */; }; + DC47E4C327A5820D0020E8EF /* GAODataError.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49527A5820C0020E8EF /* GAODataError.m */; }; + DC47E4C427A5820D0020E8EF /* GAODataErrorMain.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E49627A5820C0020E8EF /* GAODataErrorMain.h */; }; + DC47E4C527A5820D0020E8EF /* GAItemReference.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49727A5820C0020E8EF /* GAItemReference.m */; }; + DC47E4C627A5820D0020E8EF /* GAOpenGraphFile.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E49827A5820C0020E8EF /* GAOpenGraphFile.h */; }; + DC47E4C727A5820D0020E8EF /* GADriveItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49927A5820C0020E8EF /* GADriveItem.m */; }; + DC47E4C827A5820D0020E8EF /* GATrash.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49A27A5820C0020E8EF /* GATrash.m */; }; + DC47E4C927A5820D0020E8EF /* GAIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49B27A5820C0020E8EF /* GAIdentity.m */; }; + DC47E4CA27A5820D0020E8EF /* GAIdentitySet.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49C27A5820C0020E8EF /* GAIdentitySet.m */; }; + DC47E4CB27A5820D0020E8EF /* GAHashes.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E49D27A5820C0020E8EF /* GAHashes.h */; }; + DC47E4CC27A5820D0020E8EF /* GAFileSystemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E49E27A5820C0020E8EF /* GAFileSystemInfo.m */; }; + DC47E4CD27A5820D0020E8EF /* GAImage.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E49F27A5820C0020E8EF /* GAImage.h */; }; + DC47E4CE27A5820D0020E8EF /* GAODataErrorDetail.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4A027A5820C0020E8EF /* GAODataErrorDetail.h */; }; + DC47E4CF27A5820D0020E8EF /* GAFolderView.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A127A5820C0020E8EF /* GAFolderView.m */; }; + DC47E4D027A5820D0020E8EF /* GARoot.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A227A5820C0020E8EF /* GARoot.m */; }; + DC47E4D127A5820D0020E8EF /* GADrive.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A327A5820C0020E8EF /* GADrive.m */; }; + DC47E4D227A5820D0020E8EF /* GADirectoryObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4A427A5820C0020E8EF /* GADirectoryObject.h */; }; + DC47E4D327A5820D0020E8EF /* GAFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A527A5820C0020E8EF /* GAFolder.m */; }; + DC47E4D427A5820D0020E8EF /* GAPasswordProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4A627A5820C0020E8EF /* GAPasswordProfile.h */; }; + DC47E4D527A5820D0020E8EF /* GADeleted.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A727A5820C0020E8EF /* GADeleted.m */; }; + DC47E4D627A5820D0020E8EF /* GAGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4A827A5820C0020E8EF /* GAGroup.m */; }; + DC47E4D727A5820D0020E8EF /* GAQuota.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4A927A5820C0020E8EF /* GAQuota.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC47E4D827A5820D0020E8EF /* GAUser.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4AA27A5820C0020E8EF /* GAUser.h */; }; + DC47E4D927A5820D0020E8EF /* GASpecialFolder.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4AB27A5820C0020E8EF /* GASpecialFolder.h */; }; + DC47E4DA27A5820D0020E8EF /* GAOpenGraphFile.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4AC27A5820C0020E8EF /* GAOpenGraphFile.m */; }; + DC47E4DB27A5820D0020E8EF /* GAItemReference.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4AD27A5820C0020E8EF /* GAItemReference.h */; }; + DC47E4DC27A5820D0020E8EF /* GAODataErrorMain.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4AE27A5820C0020E8EF /* GAODataErrorMain.m */; }; + DC47E4DD27A5820D0020E8EF /* GAODataError.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4AF27A5820C0020E8EF /* GAODataError.h */; }; + DC47E4DE27A5820D0020E8EF /* GAFileSystemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B027A5820C0020E8EF /* GAFileSystemInfo.h */; }; + DC47E4DF27A5820D0020E8EF /* GAHashes.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4B127A5820C0020E8EF /* GAHashes.m */; }; + DC47E4E027A5820D0020E8EF /* GAIdentitySet.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B227A5820C0020E8EF /* GAIdentitySet.h */; }; + DC47E4E127A5820D0020E8EF /* GATrash.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B327A5820C0020E8EF /* GATrash.h */; }; + DC47E4E227A5820D0020E8EF /* GAIdentity.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B427A5820C0020E8EF /* GAIdentity.h */; }; + DC47E4E327A5820D0020E8EF /* GADriveItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B527A5820C0020E8EF /* GADriveItem.h */; }; + DC47E4E427A5820D0020E8EF /* GADeleted.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B627A5820C0020E8EF /* GADeleted.h */; }; + DC47E4E527A5820D0020E8EF /* GAFolder.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4B727A5820C0020E8EF /* GAFolder.h */; }; + DC47E4E627A5820D0020E8EF /* GAPasswordProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4B827A5820C0020E8EF /* GAPasswordProfile.m */; }; + DC47E4E727A5820D0020E8EF /* GADirectoryObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4B927A5820C0020E8EF /* GADirectoryObject.m */; }; + DC47E4E827A5820D0020E8EF /* GAFolderView.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4BA27A5820C0020E8EF /* GAFolderView.h */; }; + DC47E4E927A5820D0020E8EF /* GARoot.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4BB27A5820C0020E8EF /* GARoot.h */; }; + DC47E4EA27A5820D0020E8EF /* GADrive.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4BC27A5820C0020E8EF /* GADrive.h */; }; + DC47E4EB27A5820D0020E8EF /* GAODataErrorDetail.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4BD27A5820C0020E8EF /* GAODataErrorDetail.m */; }; + DC47E4EC27A5820D0020E8EF /* GAImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4BE27A5820C0020E8EF /* GAImage.m */; }; + DC47E4F027A7E2050020E8EF /* OCDrive.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4EE27A7E2050020E8EF /* OCDrive.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC47E4F127A7E2050020E8EF /* OCDrive.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4EF27A7E2050020E8EF /* OCDrive.m */; }; + DC47E4F527A83D9B0020E8EF /* OCQuota.h in Headers */ = {isa = PBXBuildFile; fileRef = DC47E4F327A83D9B0020E8EF /* OCQuota.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC47E4F627A83D9B0020E8EF /* OCQuota.m in Sources */ = {isa = PBXBuildFile; fileRef = DC47E4F427A83D9B0020E8EF /* OCQuota.m */; }; + DC49B55328339BE200DAF13B /* NSArray+OCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55128339BE200DAF13B /* NSArray+OCMapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC49B55428339BE200DAF13B /* NSArray+OCMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55228339BE200DAF13B /* NSArray+OCMapping.m */; }; + DC49B5602837DCE700DAF13B /* OCItem+OCVFSItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC49B55E2837DCE700DAF13B /* OCItem+OCVFSItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC49B5612837DCE700DAF13B /* OCItem+OCVFSItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC49B55F2837DCE700DAF13B /* OCItem+OCVFSItem.m */; }; DC4A2C5E20D4608100A47260 /* OCIssueChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4A2C5C20D4608100A47260 /* OCIssueChoice.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC4A2C5F20D4608100A47260 /* OCIssueChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4A2C5D20D4608100A47260 /* OCIssueChoice.m */; }; DC4AFAA6206A6E7100189B9A /* OCSQLiteResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4AFAA4206A6E7100189B9A /* OCSQLiteResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -251,6 +350,12 @@ DC4B11FF220996480062BCDD /* OCProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4B11FD220996480062BCDD /* OCProgress.m */; }; DC4E0A5820927048007EB05F /* OCItemVersionIdentifier.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4E0A5620927048007EB05F /* OCItemVersionIdentifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC4E0A5920927048007EB05F /* OCItemVersionIdentifier.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4E0A5720927048007EB05F /* OCItemVersionIdentifier.m */; }; + DC510D2E27E1463900F2754F /* OCDataSourceSubscription.h in Headers */ = {isa = PBXBuildFile; fileRef = DC510D2C27E1463900F2754F /* OCDataSourceSubscription.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC510D2F27E1463900F2754F /* OCDataSourceSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = DC510D2D27E1463900F2754F /* OCDataSourceSubscription.m */; }; + DC510D3227E1469600F2754F /* OCDataSourceSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = DC510D3027E1469600F2754F /* OCDataSourceSnapshot.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC510D3327E1469600F2754F /* OCDataSourceSnapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = DC510D3127E1469600F2754F /* OCDataSourceSnapshot.m */; }; + DC510D3627E146BD00F2754F /* OCDataItemRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = DC510D3427E146BD00F2754F /* OCDataItemRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC510D3727E146BD00F2754F /* OCDataItemRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = DC510D3527E146BD00F2754F /* OCDataItemRecord.m */; }; DC51FD89247562C20069AB79 /* OCCellularManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC51FD87247562C20069AB79 /* OCCellularManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC51FD8A247562C20069AB79 /* OCCellularManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC51FD88247562C20069AB79 /* OCCellularManager.m */; }; DC51FD8D247565080069AB79 /* OCCellularSwitch.h in Headers */ = {isa = PBXBuildFile; fileRef = DC51FD8B247565080069AB79 /* OCCellularSwitch.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -278,9 +383,11 @@ DC5D9E6824963DED00BFFE8E /* OCMessageChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = DC5D9E6624963DED00BFFE8E /* OCMessageChoice.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC5D9E6924963DED00BFFE8E /* OCMessageChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = DC5D9E6724963DED00BFFE8E /* OCMessageChoice.m */; }; DC61E931221423D2002889D6 /* HTTPPipelineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC61E930221423D2002889D6 /* HTTPPipelineTests.m */; }; + DC622C4E29019515001D73A0 /* OCLocale+SystemLanguage.h in Headers */ = {isa = PBXBuildFile; fileRef = DC622C4C29019515001D73A0 /* OCLocale+SystemLanguage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC622C4F29019515001D73A0 /* OCLocale+SystemLanguage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC622C4D29019515001D73A0 /* OCLocale+SystemLanguage.m */; }; DC62FD62211B11FD0034B628 /* OCItem+OCThumbnail.h in Headers */ = {isa = PBXBuildFile; fileRef = DC62FD60211B11FD0034B628 /* OCItem+OCThumbnail.h */; }; DC62FD63211B11FD0034B628 /* OCItem+OCThumbnail.m in Sources */ = {isa = PBXBuildFile; fileRef = DC62FD61211B11FD0034B628 /* OCItem+OCThumbnail.m */; }; - DC66C6B420540DBD00189B9A /* NSDate+OCDateParser.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66C6B220540DBD00189B9A /* NSDate+OCDateParser.h */; }; + DC66C6B420540DBD00189B9A /* NSDate+OCDateParser.h in Headers */ = {isa = PBXBuildFile; fileRef = DC66C6B220540DBD00189B9A /* NSDate+OCDateParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC66C6B520540DBD00189B9A /* NSDate+OCDateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = DC66C6B320540DBD00189B9A /* NSDate+OCDateParser.m */; }; DC68057D212EB438006C3B1F /* OCExtensionMatch.h in Headers */ = {isa = PBXBuildFile; fileRef = DC68057B212EB438006C3B1F /* OCExtensionMatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC68057E212EB438006C3B1F /* OCExtensionMatch.m in Sources */ = {isa = PBXBuildFile; fileRef = DC68057C212EB438006C3B1F /* OCExtensionMatch.m */; }; @@ -302,6 +409,10 @@ DC6BFFEE231FB9F8005FA5CB /* OCEventRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6BFFEC231FB9F8005FA5CB /* OCEventRecord.m */; }; DC6BFFF623206215005FA5CB /* OCEventQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6BFFF423206215005FA5CB /* OCEventQueue.h */; }; DC6BFFF723206215005FA5CB /* OCEventQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6BFFF523206215005FA5CB /* OCEventQueue.m */; }; + DC6C0A4C2923A0050045FF2A /* OCBookmark+DataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6C0A4A2923A0050045FF2A /* OCBookmark+DataItem.h */; }; + DC6C0A4D2923A0050045FF2A /* OCBookmark+DataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6C0A4B2923A0050045FF2A /* OCBookmark+DataItem.m */; }; + DC6C0A512923A59A0045FF2A /* OCDataSourceMapped.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6C0A4F2923A59A0045FF2A /* OCDataSourceMapped.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C0A522923A59A0045FF2A /* OCDataSourceMapped.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6C0A502923A59A0045FF2A /* OCDataSourceMapped.m */; }; DC6CC30726428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6CC30526428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC6CC30826428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6CC30626428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.m */; }; DC6CC30F2642A0720040ECAC /* OCAuthenticationBrowserSessionMIBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6CC30D2642A0720040ECAC /* OCAuthenticationBrowserSessionMIBrowser.h */; }; @@ -321,6 +432,8 @@ DC6DEEB924C5C82400E3772E /* OCHTTPPolicyBookmark.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6DEEB724C5C82400E3772E /* OCHTTPPolicyBookmark.h */; }; DC6DEEBA24C5C82400E3772E /* OCHTTPPolicyBookmark.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6DEEB824C5C82400E3772E /* OCHTTPPolicyBookmark.m */; }; DC6F81522684C32600863D8A /* DAVRawResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6F81512684C32600863D8A /* DAVRawResponseTests.m */; }; + DC6FDAF229507579004F0C7F /* OCShare+OCDataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DC6FDAF029507579004F0C7F /* OCShare+OCDataItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6FDAF329507579004F0C7F /* OCShare+OCDataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6FDAF129507579004F0C7F /* OCShare+OCDataItem.m */; }; DC7014252209CE7A009D4FD9 /* OCHTTPPipelineManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7014232209CE7A009D4FD9 /* OCHTTPPipelineManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC7014262209CE7A009D4FD9 /* OCHTTPPipelineManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7014242209CE7A009D4FD9 /* OCHTTPPipelineManager.m */; }; DC701477220AE696009D4FD9 /* OCHTTPPipelineTask.h in Headers */ = {isa = PBXBuildFile; fileRef = DC701475220AE696009D4FD9 /* OCHTTPPipelineTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -354,6 +467,8 @@ DC73F3AD254BF95C00CE5FA9 /* OCClassSettings+Documentation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC73F3AB254BF95C00CE5FA9 /* OCClassSettings+Documentation.m */; }; DC73F3BF254BFE9900CE5FA9 /* NSArray+ObjCRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = DC73F3BD254BFE9900CE5FA9 /* NSArray+ObjCRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC73F3C0254BFE9900CE5FA9 /* NSArray+ObjCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = DC73F3BE254BFE9900CE5FA9 /* NSArray+ObjCRuntime.m */; }; + DC74E44427AD86DA005B4B03 /* OCLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC74E44227AD86DA005B4B03 /* OCLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC74E44527AD86DA005B4B03 /* OCLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC74E44327AD86DA005B4B03 /* OCLocation.m */; }; DC75D30B214BF1BA00B6FB62 /* NSString+OCFormatting.h in Headers */ = {isa = PBXBuildFile; fileRef = DC75D309214BF1BA00B6FB62 /* NSString+OCFormatting.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC75D30C214BF1BA00B6FB62 /* NSString+OCFormatting.m in Sources */ = {isa = PBXBuildFile; fileRef = DC75D30A214BF1BA00B6FB62 /* NSString+OCFormatting.m */; }; DC75D30F214C015F00B6FB62 /* OCItem+OCItemCreationDebugging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC75D30D214C015E00B6FB62 /* OCItem+OCItemCreationDebugging.h */; }; @@ -380,6 +495,13 @@ DC8245B321FB31E500775AB9 /* OCActivityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8245B121FB31E500775AB9 /* OCActivityManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC8245B421FB31E500775AB9 /* OCActivityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8245B221FB31E500775AB9 /* OCActivityManager.m */; }; DC8245BA21FB334200775AB9 /* OCCore+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8245B921FB334200775AB9 /* OCCore+Internal.h */; }; + DC82665F2818972200F91F7D /* OCVaultLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC82665D2818972200F91F7D /* OCVaultLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC8266602818972200F91F7D /* OCVaultLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC82665E2818972200F91F7D /* OCVaultLocation.m */; }; + DC826665281AC59D00F91F7D /* OCVFSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = DC826663281AC59D00F91F7D /* OCVFSCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC826666281AC59D00F91F7D /* OCVFSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = DC826664281AC59D00F91F7D /* OCVFSCore.m */; }; + DC826669281AC5B000F91F7D /* OCVFSNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DC826667281AC5B000F91F7D /* OCVFSNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC82666A281AC5B000F91F7D /* OCVFSNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DC826668281AC5B000F91F7D /* OCVFSNode.m */; }; + DC826680281FE66600F91F7D /* OCVFSTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DC82667E281FE66600F91F7D /* OCVFSTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC8556ED204DEA2900189B9A /* OCHTTPDAVRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8556EB204DEA2900189B9A /* OCHTTPDAVRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC8556EE204DEA2900189B9A /* OCHTTPDAVRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8556EC204DEA2900189B9A /* OCHTTPDAVRequest.m */; }; DC8556F1204DEB9200189B9A /* OCXMLNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8556EF204DEB9200189B9A /* OCXMLNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -405,13 +527,47 @@ DC8EB30523952085009148F9 /* OCAuthenticationBrowserSessionUIWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8EB30323952084009148F9 /* OCAuthenticationBrowserSessionUIWebView.m */; }; DC8FE6FF221CAF280016BDEE /* OCProgressManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DC8FE6FD221CAF280016BDEE /* OCProgressManager.h */; }; DC8FE700221CAF280016BDEE /* OCProgressManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DC8FE6FE221CAF280016BDEE /* OCProgressManager.m */; }; + DC9219DD2964CB4500F538EE /* GAEducationUser.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219CD2964CB4400F538EE /* GAEducationUser.h */; }; + DC9219DE2964CB4500F538EE /* GAAppRoleAssignment.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219CE2964CB4400F538EE /* GAAppRoleAssignment.h */; }; + DC9219DF2964CB4500F538EE /* GAEntity.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219CF2964CB4500F538EE /* GAEntity.h */; }; + DC9219E02964CB4500F538EE /* GAAppRole.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219D02964CB4500F538EE /* GAAppRole.h */; }; + DC9219E12964CB4500F538EE /* GAApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219D12964CB4500F538EE /* GAApplication.h */; }; + DC9219E32964CB4500F538EE /* GAEducationUser.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219D32964CB4500F538EE /* GAEducationUser.m */; }; + DC9219E42964CB4500F538EE /* GAApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219D42964CB4500F538EE /* GAApplication.m */; }; + DC9219E52964CB4500F538EE /* GAEducationSchool.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219D52964CB4500F538EE /* GAEducationSchool.m */; }; + DC9219E62964CB4500F538EE /* GAAppRole.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219D62964CB4500F538EE /* GAAppRole.m */; }; + DC9219E72964CB4500F538EE /* GAEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219D72964CB4500F538EE /* GAEntity.m */; }; + DC9219E82964CB4500F538EE /* GAEducationSchool.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219D82964CB4500F538EE /* GAEducationSchool.h */; }; + DC9219E92964CB4500F538EE /* GAEducationOrganization.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219D92964CB4500F538EE /* GAEducationOrganization.h */; }; + DC9219EB2964CB4500F538EE /* GAEducationOrganization.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219DB2964CB4500F538EE /* GAEducationOrganization.m */; }; + DC9219EC2964CB4500F538EE /* GAAppRoleAssignment.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219DC2964CB4500F538EE /* GAAppRoleAssignment.m */; }; + DC9219F32964CB6000F538EE /* GATagAssignment.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219ED2964CB6000F538EE /* GATagAssignment.m */; }; + DC9219F42964CB6000F538EE /* GATagUnassignment.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219EE2964CB6000F538EE /* GATagUnassignment.m */; }; + DC9219F52964CB6000F538EE /* GATagUnassignment.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219EF2964CB6000F538EE /* GATagUnassignment.h */; }; + DC9219F62964CB6000F538EE /* GAObjectIdentity.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219F02964CB6000F538EE /* GAObjectIdentity.h */; }; + DC9219F72964CB6000F538EE /* GATagAssignment.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9219F12964CB6000F538EE /* GATagAssignment.h */; }; + DC9219F82964CB6000F538EE /* GAObjectIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9219F22964CB6000F538EE /* GAObjectIdentity.m */; }; DC971BEE20D15D4400428EF1 /* OCSQLiteQueryCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = DC971BEC20D15D4400428EF1 /* OCSQLiteQueryCondition.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC971BEF20D15D4400428EF1 /* OCSQLiteQueryCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = DC971BED20D15D4400428EF1 /* OCSQLiteQueryCondition.m */; }; DC98BDF521E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC98BDF321E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.h */; }; DC98BDF621E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BDF421E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.m */; }; DC98BDF821E73EFF003B5658 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC98BDF721E73EFF003B5658 /* Network.framework */; settings = {ATTRIBUTES = (Required, ); }; }; + DC9A116827CFCC1300D90BA4 /* GAPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9A116627CFCC1200D90BA4 /* GAPermission.h */; }; + DC9A116927CFCC1300D90BA4 /* GAPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9A116727CFCC1200D90BA4 /* GAPermission.m */; }; DC9B4D3922E987EF0089BF78 /* OCClaim.m in Sources */ = {isa = PBXBuildFile; fileRef = DC1D4D3E20DC2281005A3DFC /* OCClaim.m */; }; DC9B4D3A22E987EF0089BF78 /* OCClaim.h in Headers */ = {isa = PBXBuildFile; fileRef = DC1D4D3D20DC2281005A3DFC /* OCClaim.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9B4FD12941FF540037F8F8 /* OCCertificateStore.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9B4FCF2941FF540037F8F8 /* OCCertificateStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9B4FD22941FF540037F8F8 /* OCCertificateStore.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9B4FD02941FF540037F8F8 /* OCCertificateStore.m */; }; + DC9B4FD52941FF630037F8F8 /* OCCertificateStoreRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9B4FD32941FF630037F8F8 /* OCCertificateStoreRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9B4FD62941FF630037F8F8 /* OCCertificateStoreRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9B4FD42941FF630037F8F8 /* OCCertificateStoreRecord.m */; }; + DC9C19E227839E440021222E /* OCResourceSourceStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9C19E027839E440021222E /* OCResourceSourceStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9C19E327839E440021222E /* OCResourceSourceStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9C19E127839E440021222E /* OCResourceSourceStorage.m */; }; + DC9C19E6278488360021222E /* OCResourceManagerJob.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9C19E4278488360021222E /* OCResourceManagerJob.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9C19E7278488360021222E /* OCResourceManagerJob.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9C19E5278488360021222E /* OCResourceManagerJob.m */; }; + DC9C19EE278CD0B30021222E /* OCDatabase+ResourceStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9C19EC278CD0B30021222E /* OCDatabase+ResourceStorage.h */; }; + DC9C19EF278CD0B30021222E /* OCDatabase+ResourceStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9C19ED278CD0B30021222E /* OCDatabase+ResourceStorage.m */; }; + DC9C19F2278EE7230021222E /* OCResourceRequestImage.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9C19F0278EE7230021222E /* OCResourceRequestImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC9C19F3278EE7230021222E /* OCResourceRequestImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9C19F1278EE7230021222E /* OCResourceRequestImage.m */; }; DC9D22EA25A8754200CF5675 /* OCHTTPRequest+JSON.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D22E825A8754200CF5675 /* OCHTTPRequest+JSON.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC9D22EB25A8754200CF5675 /* OCHTTPRequest+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = DC9D22E925A8754200CF5675 /* OCHTTPRequest+JSON.m */; }; DCA35D4D24CF685B00DBE2B0 /* OCDiagnosticNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DCA35D4B24CF685B00DBE2B0 /* OCDiagnosticNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -477,6 +633,14 @@ DCB0A46521B922A400FAC4E9 /* OCCoreConnectionStatusSignalProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB0A46321B922A400FAC4E9 /* OCCoreConnectionStatusSignalProvider.m */; }; DCB0A46C21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB0A46A21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.h */; }; DCB0A46D21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB0A46B21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.m */; }; + DCB330C629EF2F0F00BFF393 /* OCIdentity+DataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB330C429EF2F0F00BFF393 /* OCIdentity+DataItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCB330C729EF2F0F00BFF393 /* OCIdentity+DataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB330C529EF2F0F00BFF393 /* OCIdentity+DataItem.m */; }; + DCB330DD29F142FB00BFF393 /* OCShareRole+OCDataItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB330DB29F142FB00BFF393 /* OCShareRole+OCDataItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCB330DE29F142FB00BFF393 /* OCShareRole+OCDataItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB330DC29F142FB00BFF393 /* OCShareRole+OCDataItem.m */; }; + DCB4F6E728324A3A005AD181 /* OCVaultDriveList.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB4F6E528324A3A005AD181 /* OCVaultDriveList.h */; }; + DCB4F6E828324A3A005AD181 /* OCVaultDriveList.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB4F6E628324A3A005AD181 /* OCVaultDriveList.m */; }; + DCB4F6EC28324B90005AD181 /* OCDataSourceKVO.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB4F6EA28324B90005AD181 /* OCDataSourceKVO.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCB4F6ED28324B90005AD181 /* OCDataSourceKVO.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB4F6EB28324B90005AD181 /* OCDataSourceKVO.m */; }; DCB572AE2099EFC600B793CE /* OCDatabase+Schemas.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB572AC2099EFC600B793CE /* OCDatabase+Schemas.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCB572AF2099EFC600B793CE /* OCDatabase+Schemas.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB572AD2099EFC600B793CE /* OCDatabase+Schemas.m */; }; DCB6D05822A13E7500CA47C5 /* NSString+OCSQLTools.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB6D05622A13E7500CA47C5 /* NSString+OCSQLTools.h */; }; @@ -485,6 +649,15 @@ DCC3701024D4B3B7008B0DEB /* OCDatabase+Diagnostic.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC3700E24D4B3B7008B0DEB /* OCDatabase+Diagnostic.m */; }; DCC3701324D4D134008B0DEB /* OCScanJobActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC3701124D4D134008B0DEB /* OCScanJobActivity.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC3701424D4D134008B0DEB /* OCScanJobActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC3701224D4D134008B0DEB /* OCScanJobActivity.m */; }; + DCC4F3EA27D74DE300ABF4C9 /* OCDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC4F3E827D74DE300ABF4C9 /* OCDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCC4F3EB27D74DE300ABF4C9 /* OCDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC4F3E927D74DE300ABF4C9 /* OCDataSource.m */; }; + DCC4F3F327D756C300ABF4C9 /* OCDataTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC4F3F127D756C300ABF4C9 /* OCDataTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCC4F3F727D757FE00ABF4C9 /* OCDataConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC4F3F527D757FE00ABF4C9 /* OCDataConverter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCC4F3F827D757FE00ABF4C9 /* OCDataConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC4F3F627D757FE00ABF4C9 /* OCDataConverter.m */; }; + DCC4F3FB27D75AA600ABF4C9 /* OCDataRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC4F3F927D75AA600ABF4C9 /* OCDataRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCC4F3FC27D75AA600ABF4C9 /* OCDataRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC4F3FA27D75AA600ABF4C9 /* OCDataRenderer.m */; }; + DCC4F3FF27D75BF700ABF4C9 /* OCDataConverterPipeline.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC4F3FD27D75BF700ABF4C9 /* OCDataConverterPipeline.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCC4F40027D75BF700ABF4C9 /* OCDataConverterPipeline.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC4F3FE27D75BF700ABF4C9 /* OCDataConverterPipeline.m */; }; DCC599F422EEE65700499B29 /* OCCore+Claims.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC599F222EEE65700499B29 /* OCCore+Claims.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC599F522EEE65700499B29 /* OCCore+Claims.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC599F322EEE65700499B29 /* OCCore+Claims.m */; }; DCC6563D20C979FB00110A97 /* TestTools.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6563C20C979FB00110A97 /* TestTools.m */; }; @@ -542,11 +715,15 @@ DCD038A02542CA4500F97534 /* NSString+OCClassSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD0389E2542CA4500F97534 /* NSString+OCClassSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCD038A12542CA4500F97534 /* NSString+OCClassSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD0389F2542CA4500F97534 /* NSString+OCClassSettings.m */; }; DCD039442544D17E00F97534 /* class-settings-sdk in CopyFiles */ = {isa = PBXBuildFile; fileRef = DCD039432544D17E00F97534 /* class-settings-sdk */; }; + DCD09E3C2A1B573100BFF393 /* OCResourceSourceURL.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD09E3A2A1B573100BFF393 /* OCResourceSourceURL.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCD09E3D2A1B573100BFF393 /* OCResourceSourceURL.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD09E3B2A1B573100BFF393 /* OCResourceSourceURL.m */; }; DCD2D40322F059190071FB8F /* OCClassSettingsUserPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD2D40122F059190071FB8F /* OCClassSettingsUserPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCD2D40422F059190071FB8F /* OCClassSettingsUserPreferences.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD2D40222F059190071FB8F /* OCClassSettingsUserPreferences.m */; }; DCD2D42322F180EF0071FB8F /* ItemPolicyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD2D42222F180EF0071FB8F /* ItemPolicyTests.m */; }; DCD3439C2059319C00189B9A /* OCSQLiteDB.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD3439A2059319C00189B9A /* OCSQLiteDB.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCD3439D2059319C00189B9A /* OCSQLiteDB.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD3439B2059319C00189B9A /* OCSQLiteDB.m */; }; + DCD3F6A027EA887600D86662 /* OCDataSourceArray.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD3F69E27EA887600D86662 /* OCDataSourceArray.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCD3F6A127EA887600D86662 /* OCDataSourceArray.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD3F69F27EA887600D86662 /* OCDataSourceArray.m */; }; DCD49A0D24E68D72008D9544 /* OCBookmark+Diagnostics.m in Sources */ = {isa = PBXBuildFile; fileRef = DCD49A0924E68BD5008D9544 /* OCBookmark+Diagnostics.m */; }; DCD49A0E24E68F7B008D9544 /* OCBookmark+Diagnostics.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD49A0824E68BD5008D9544 /* OCBookmark+Diagnostics.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCD63276223BB1710090169E /* OCCapabilities.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD63274223BB1710090169E /* OCCapabilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -568,6 +745,13 @@ DCDB76252739D51200EE7A06 /* OCServerLocatorLookupTable.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDB76232739D51200EE7A06 /* OCServerLocatorLookupTable.m */; }; DCDB76282739EF9A00EE7A06 /* OCExtension+ServerLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDB76262739EF9A00EE7A06 /* OCExtension+ServerLocator.h */; }; DCDB76292739EF9A00EE7A06 /* OCExtension+ServerLocator.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDB76272739EF9A00EE7A06 /* OCExtension+ServerLocator.m */; }; + DCDBB5E72523DDA200FAD707 /* OCConnection+Avatars.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBB5E52523DDA200FAD707 /* OCConnection+Avatars.m */; }; + DCDBB5EA2523E3AF00FAD707 /* OCAvatar.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB5E82523E3AF00FAD707 /* OCAvatar.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCDBB5EB2523E3AF00FAD707 /* OCAvatar.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBB5E92523E3AF00FAD707 /* OCAvatar.m */; }; + DCDBB5F725248B0300FAD707 /* OCResource.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB5F525248B0300FAD707 /* OCResource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCDBB5F825248B0300FAD707 /* OCResource.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBB5F625248B0300FAD707 /* OCResource.m */; }; + DCDBB5FB25248B0F00FAD707 /* OCResourceRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB5F925248B0F00FAD707 /* OCResourceRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCDBB5FC25248B0F00FAD707 /* OCResourceRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBB5FA25248B0F00FAD707 /* OCResourceRequest.m */; }; DCDBEE2C2048A6A800189B9A /* OCConnection+Setup.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBEE2A2048A6A700189B9A /* OCConnection+Setup.m */; }; DCDBEE302048A71200189B9A /* OCConnection+Tools.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBEE2E2048A71200189B9A /* OCConnection+Tools.m */; }; DCDBEE342048A8BC00189B9A /* OCConnection+Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBEE322048A8BC00189B9A /* OCConnection+Authentication.m */; }; @@ -580,8 +764,8 @@ DCDCA61D245093E800AFA158 /* NSURL+OCPrivateLink.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDCA61B245093E800AFA158 /* NSURL+OCPrivateLink.m */; }; DCDD9B14222986D50052A001 /* OCShare+OCXMLObjectCreation.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDD9B12222986D50052A001 /* OCShare+OCXMLObjectCreation.h */; }; DCDD9B15222986D50052A001 /* OCShare+OCXMLObjectCreation.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDD9B13222986D50052A001 /* OCShare+OCXMLObjectCreation.m */; }; - DCDD9B18222989E50052A001 /* OCRecipient.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDD9B16222989E50052A001 /* OCRecipient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCDD9B19222989E50052A001 /* OCRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDD9B17222989E50052A001 /* OCRecipient.m */; }; + DCDD9B18222989E50052A001 /* OCIdentity.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDD9B16222989E50052A001 /* OCIdentity.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCDD9B19222989E50052A001 /* OCIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDD9B17222989E50052A001 /* OCIdentity.m */; }; DCDD9B1C22298D050052A001 /* OCGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDD9B1A22298D050052A001 /* OCGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCDD9B1D22298D050052A001 /* OCGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDD9B1B22298D050052A001 /* OCGroup.m */; }; DCDD9B2B22312ED80052A001 /* OCRateLimiter.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDD9B2922312ED80052A001 /* OCRateLimiter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -596,6 +780,8 @@ DCE2661D2113323C0001FB2C /* OCCore+CommandDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE2661B2113323C0001FB2C /* OCCore+CommandDownload.m */; }; DCE26620211348B00001FB2C /* OCCore+CommandLocalModification.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE2661E211348AF0001FB2C /* OCCore+CommandLocalModification.m */; }; DCE26621211348B00001FB2C /* OCCore+CommandLocalImport.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE2661F211348B00001FB2C /* OCCore+CommandLocalImport.m */; }; + DCE2F04327FB928B00E9E136 /* NSArray+OCFiltering.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE2F04127FB928B00E9E136 /* NSArray+OCFiltering.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE2F04427FB928B00E9E136 /* NSArray+OCFiltering.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE2F04227FB928B00E9E136 /* NSArray+OCFiltering.m */; }; DCE2F04E27FDE01D00E9E136 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCE2F04D27FDE01D00E9E136 /* OpenSSL */; }; DCE370942099D18100114981 /* OCDatabaseConsistentOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE370922099D18100114981 /* OCDatabaseConsistentOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCE370952099D18100114981 /* OCDatabaseConsistentOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE370932099D18100114981 /* OCDatabaseConsistentOperation.m */; }; @@ -605,6 +791,14 @@ DCE451A62459AD3F0074363F /* OCTUSJob.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE451A42459AD3F0074363F /* OCTUSJob.m */; }; DCE48DD8220E1C7B00839E97 /* OCHTTPPipelineTaskCache.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE48DD6220E1C7A00839E97 /* OCHTTPPipelineTaskCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCE48DD9220E1C7B00839E97 /* OCHTTPPipelineTaskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE48DD7220E1C7B00839E97 /* OCHTTPPipelineTaskCache.m */; }; + DCE62EA92771EA0200E3193F /* OCResourceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE62EA72771EA0100E3193F /* OCResourceManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE62EAA2771EA0200E3193F /* OCResourceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE62EA82771EA0200E3193F /* OCResourceManager.m */; }; + DCE62EAD2771ED5700E3193F /* OCResourceImage.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE62EAB2771ED5700E3193F /* OCResourceImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE62EAE2771ED5700E3193F /* OCResourceImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE62EAC2771ED5700E3193F /* OCResourceImage.m */; }; + DCE741D829B7DD5000BFF393 /* OCServerInstance.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE741D629B7DD5000BFF393 /* OCServerInstance.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE741D929B7DD5000BFF393 /* OCServerInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE741D729B7DD5000BFF393 /* OCServerInstance.m */; }; + DCE741DC29B7EA7000BFF393 /* OCBookmark+ServerInstance.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE741DA29B7EA7000BFF393 /* OCBookmark+ServerInstance.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE741DD29B7EA7000BFF393 /* OCBookmark+ServerInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE741DB29B7EA7000BFF393 /* OCBookmark+ServerInstance.m */; }; DCE784F922325D4F00733F01 /* OCConnection+Recipients.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE784F722325D4F00733F01 /* OCConnection+Recipients.m */; }; DCE784FC2232748100733F01 /* OCHTTPResponse+DAVError.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE784FA2232748100733F01 /* OCHTTPResponse+DAVError.h */; }; DCE784FD2232748100733F01 /* OCHTTPResponse+DAVError.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE784FB2232748100733F01 /* OCHTTPResponse+DAVError.m */; }; @@ -617,7 +811,18 @@ DCEAA0D025CEB7F90017F99B /* OCLockRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEAA0CE25CEB7F90017F99B /* OCLockRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCEAA0D125CEB7F90017F99B /* OCLockRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEAA0CF25CEB7F90017F99B /* OCLockRequest.m */; }; DCEAA0DD25CEDBC40017F99B /* LockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEAA0DC25CEDBC40017F99B /* LockTests.m */; }; + DCEAF0462805B80D00980B6D /* OCResourceSourceDriveItems.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEAF0442805B80D00980B6D /* OCResourceSourceDriveItems.h */; }; + DCEAF0472805B80D00980B6D /* OCResourceSourceDriveItems.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0452805B80D00980B6D /* OCResourceSourceDriveItems.m */; }; + DCEAF04A2805B83B00980B6D /* OCResourceRequestDriveItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEAF0482805B83B00980B6D /* OCResourceRequestDriveItem.h */; }; + DCEAF04B2805B83B00980B6D /* OCResourceRequestDriveItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0492805B83B00980B6D /* OCResourceRequestDriveItem.m */; }; + DCEAF04E2806201300980B6D /* OCResourceText.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEAF04C2806201300980B6D /* OCResourceText.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCEAF04F2806201300980B6D /* OCResourceText.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF04D2806201300980B6D /* OCResourceText.m */; }; DCEB94DB21105FE0004EF8D7 /* rainbow.png in Resources */ = {isa = PBXBuildFile; fileRef = DCEB94DA21105FDE004EF8D7 /* rainbow.png */; }; + DCED67D327F1A48000686E4F /* OCDataTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = DCED67D127F1A48000686E4F /* OCDataTypes.m */; }; + DCED67D727F1A7B200686E4F /* OCCore+DataSources.h in Headers */ = {isa = PBXBuildFile; fileRef = DCED67D527F1A7B200686E4F /* OCCore+DataSources.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCED67D827F1A7B200686E4F /* OCCore+DataSources.m in Sources */ = {isa = PBXBuildFile; fileRef = DCED67D627F1A7B200686E4F /* OCCore+DataSources.m */; }; + DCED67DC27F1B13600686E4F /* OCDataItemPresentable.h in Headers */ = {isa = PBXBuildFile; fileRef = DCED67DA27F1B13600686E4F /* OCDataItemPresentable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCED67DD27F1B13600686E4F /* OCDataItemPresentable.m in Sources */ = {isa = PBXBuildFile; fileRef = DCED67DB27F1B13600686E4F /* OCDataItemPresentable.m */; }; DCEE0B5625E68C53006534B5 /* OCBookmarkManager+ItemResolution.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEE0B5425E68C53006534B5 /* OCBookmarkManager+ItemResolution.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCEE0B5725E68C53006534B5 /* OCBookmarkManager+ItemResolution.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEE0B5525E68C53006534B5 /* OCBookmarkManager+ItemResolution.m */; }; DCEE0B6F25E697AF006534B5 /* OCCoreManager+ItemResolution.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEE0B6D25E697AF006534B5 /* OCCoreManager+ItemResolution.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -639,6 +844,13 @@ DCEEB2F22047094500189B9A /* NSData+OCHash.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEEB2F02047094500189B9A /* NSData+OCHash.m */; }; DCEEB2F5204802CF00189B9A /* OCIssue.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEEB2F3204802CF00189B9A /* OCIssue.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCEEB2F6204802CF00189B9A /* OCIssue.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEEB2F4204802CF00189B9A /* OCIssue.m */; }; + DCF00BF527E28A77001F2AFC /* OCDataSourceSubscription+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF00BF327E28A77001F2AFC /* OCDataSourceSubscription+Internal.h */; }; + DCF00BF627E28A77001F2AFC /* OCDataSourceSubscription+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF00BF427E28A77001F2AFC /* OCDataSourceSubscription+Internal.m */; }; + DCF00C1927E698A4001F2AFC /* DataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF00C1827E698A4001F2AFC /* DataSourceTests.m */; }; + DCF072E42798630900E0B01D /* OCResourceTextPlaceholder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072E22798630900E0B01D /* OCResourceTextPlaceholder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF072E52798630900E0B01D /* OCResourceTextPlaceholder.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072E32798630900E0B01D /* OCResourceTextPlaceholder.m */; }; + DCF072E82798652500E0B01D /* OCResourceSourceAvatarPlaceholders.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF072E62798652500E0B01D /* OCResourceSourceAvatarPlaceholders.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF072E92798652500E0B01D /* OCResourceSourceAvatarPlaceholders.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF072E72798652500E0B01D /* OCResourceSourceAvatarPlaceholders.m */; }; DCF163F2274B917C00E0182A /* OCSQLiteCollation.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF163F0274B917C00E0182A /* OCSQLiteCollation.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF163F3274B917C00E0182A /* OCSQLiteCollation.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF163F1274B917C00E0182A /* OCSQLiteCollation.m */; }; DCF163F6274BA6C300E0182A /* OCSQLiteCollationLocalized.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF163F4274BA6C300E0182A /* OCSQLiteCollationLocalized.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -649,11 +861,36 @@ DCF1C6902631C296004D8B0F /* OCMeasurementEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF1C68E2631C296004D8B0F /* OCMeasurementEvent.m */; }; DCF39B552458268E00DEA137 /* OCTUSHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF39B532458268E00DEA137 /* OCTUSHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCF39B562458268E00DEA137 /* OCTUSHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF39B542458268E00DEA137 /* OCTUSHeader.m */; }; + DCF575D1279562DF003BEBBA /* OCViewProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575CF279562DF003BEBBA /* OCViewProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF575D7279567AB003BEBBA /* OCPlatform.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575D5279567AB003BEBBA /* OCPlatform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCF575D8279567AB003BEBBA /* OCPlatform.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF575D6279567AB003BEBBA /* OCPlatform.m */; }; + DCF575DF27956D84003BEBBA /* OCViewProviderContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DCF575DD27956D84003BEBBA /* OCViewProviderContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 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 */; }; + 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 */; }; DCFBACF721BAB35A00943F76 /* PerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFBACF621BAB35A00943F76 /* PerformanceTests.m */; }; DCFBACF921BAB5F500943F76 /* OCDetailedPerformanceTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFBACF821BAB5F500943F76 /* OCDetailedPerformanceTestCase.m */; }; + DCFC9ED828003EFC005D9144 /* GAShared.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9ED628003EFB005D9144 /* GAShared.m */; }; + DCFC9ED928003EFC005D9144 /* GAShared.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFC9ED728003EFB005D9144 /* GAShared.h */; }; + DCFC9EDC28003F0D005D9144 /* GARemoteItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFC9EDA28003F0D005D9144 /* GARemoteItem.h */; }; + DCFC9EDD28003F0D005D9144 /* GARemoteItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9EDB28003F0D005D9144 /* GARemoteItem.m */; }; + DCFC9EE128004791005D9144 /* OCDataSourceComposition.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFC9EDF28004791005D9144 /* OCDataSourceComposition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFC9EE228004791005D9144 /* OCDataSourceComposition.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFC9EE028004791005D9144 /* OCDataSourceComposition.m */; }; + DCFE3B7A27A1666B00939415 /* GAGraphObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE3B7827A1666B00939415 /* GAGraphObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFE3B7E27A1669300939415 /* GAGraphContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE3B7C27A1669300939415 /* GAGraphContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFE3B7F27A1669300939415 /* GAGraphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3B7D27A1669300939415 /* GAGraphContext.m */; }; + DCFE3B8227A167C800939415 /* GAGraph.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE3B8027A167C700939415 /* GAGraph.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFE3B8827A16AE800939415 /* OCConnection+GraphAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE3B8627A16AE800939415 /* OCConnection+GraphAPI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFE3B8927A16AE800939415 /* OCConnection+GraphAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3B8727A16AE800939415 /* OCConnection+GraphAPI.m */; }; + DCFE3B9D27A1A6E500939415 /* GAGraphData+Decoder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE3B9B27A1A6E500939415 /* GAGraphData+Decoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCFE3B9E27A1A6E500939415 /* GAGraphData+Decoder.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3B9C27A1A6E500939415 /* GAGraphData+Decoder.m */; }; + DCFE682028D857B500091D2A /* NSError+OCISError.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE681E28D857B500091D2A /* NSError+OCISError.h */; }; + DCFE682128D857B500091D2A /* NSError+OCISError.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE681F28D857B500091D2A /* NSError+OCISError.m */; }; + DCFE682428D865BD00091D2A /* NSDictionary+OCFormEncoding.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFE682228D865BD00091D2A /* NSDictionary+OCFormEncoding.h */; }; + DCFE682528D865BD00091D2A /* NSDictionary+OCFormEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE682328D865BD00091D2A /* NSDictionary+OCFormEncoding.m */; }; DCFF1AAD216552C100ABE40A /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCFF1AAC216552C000ABE40A /* AuthenticationServices.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; DCFF1AB021655C8800ABE40A /* OCItem+OCFileURLMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = DCFF1AAE21655C8800ABE40A /* OCItem+OCFileURLMetadata.h */; }; DCFF1AB121655C8800ABE40A /* OCItem+OCFileURLMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFF1AAF21655C8800ABE40A /* OCItem+OCFileURLMetadata.m */; }; @@ -833,6 +1070,23 @@ DC0AE4562310793100428681 /* KeyValueStoreTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KeyValueStoreTests.m; sourceTree = ""; }; DC0AE4F02311C75300428681 /* OCKeyValueStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCKeyValueStack.h; sourceTree = ""; }; DC0AE4F12311C75300428681 /* OCKeyValueStack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCKeyValueStack.m; sourceTree = ""; }; + DC0BE5AF28F80BBA00CE2101 /* OCShareRole.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCShareRole.h; sourceTree = ""; }; + DC0BE5B028F80BBA00CE2101 /* OCShareRole.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCShareRole.m; sourceTree = ""; }; + DC0BE5B628F80DBF00CE2101 /* OCSymbol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSymbol.h; sourceTree = ""; }; + DC0BE5B728F80DBF00CE2101 /* OCSymbol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSymbol.m; sourceTree = ""; }; + DC0BE5BC28F9427900CE2101 /* OCStatistic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCStatistic.h; sourceTree = ""; }; + DC0BE5BD28F9427900CE2101 /* OCStatistic.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCStatistic.m; sourceTree = ""; }; + DC0CE17928C5DDE8009ABDFB /* OCAppProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAppProvider.h; sourceTree = ""; }; + DC0CE17A28C5DDE8009ABDFB /* OCAppProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAppProvider.m; sourceTree = ""; }; + DC0CE17E28C63232009ABDFB /* OCConnection+AppProviders.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+AppProviders.m"; sourceTree = ""; }; + DC0CE18528C63B15009ABDFB /* OCAppProviderApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAppProviderApp.h; sourceTree = ""; }; + DC0CE18628C63B15009ABDFB /* OCAppProviderApp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAppProviderApp.m; sourceTree = ""; }; + DC0CE18928C63B2E009ABDFB /* OCAppProviderFileType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAppProviderFileType.h; sourceTree = ""; }; + DC0CE18A28C63B2E009ABDFB /* OCAppProviderFileType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAppProviderFileType.m; sourceTree = ""; }; + DC0CE19428C8907D009ABDFB /* OCResourceRequestURLItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequestURLItem.h; sourceTree = ""; }; + DC0CE19528C8907D009ABDFB /* OCResourceRequestURLItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequestURLItem.m; sourceTree = ""; }; + DC0CE19828C89227009ABDFB /* OCResourceSourceURLItems.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceURLItems.h; sourceTree = ""; }; + DC0CE19928C89227009ABDFB /* OCResourceSourceURLItems.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceURLItems.m; sourceTree = ""; }; DC114A9222A7A87C00CBD597 /* NSData+OCRandom.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+OCRandom.h"; sourceTree = ""; }; DC114A9322A7A87C00CBD597 /* NSData+OCRandom.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+OCRandom.m"; sourceTree = ""; }; DC114A9622A7AA2E00CBD597 /* NSString+OCRandom.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+OCRandom.h"; sourceTree = ""; }; @@ -897,6 +1151,8 @@ DC20DE4E21BFCEB00096000B /* OCLogToggle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLogToggle.h; sourceTree = ""; }; DC20DE4F21BFCEB00096000B /* OCLogToggle.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLogToggle.m; sourceTree = ""; }; DC20DE8D21C104DE0096000B /* OCLogTag.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLogTag.h; sourceTree = ""; }; + DC2218C028228F7000808BCE /* OCVFSContent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVFSContent.h; sourceTree = ""; }; + DC2218C128228F7000808BCE /* OCVFSContent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCVFSContent.m; sourceTree = ""; }; DC22669822817DC600FB29EE /* OCVault+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCVault+Internal.h"; sourceTree = ""; }; DC22669922817DC600FB29EE /* OCVault+Internal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVault+Internal.m"; sourceTree = ""; }; DC2266A62282BC8100FB29EE /* OCBookmark+IPNotificationNames.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmark+IPNotificationNames.h"; sourceTree = ""; }; @@ -922,8 +1178,12 @@ DC27BBBE230498C3002CC2F8 /* OCHTTPCookieStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCHTTPCookieStorage.m; sourceTree = ""; }; DC27BBC12304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSHTTPCookie+OCCookies.h"; sourceTree = ""; }; DC27BBC22304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSHTTPCookie+OCCookies.m"; sourceTree = ""; }; + DC28F821294B6DE600AC4013 /* OCItemPolicy+OCDataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItemPolicy+OCDataItem.h"; sourceTree = ""; }; + DC28F822294B6DE600AC4013 /* OCItemPolicy+OCDataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItemPolicy+OCDataItem.m"; sourceTree = ""; }; DC29F4C024323C4900347658 /* OCMessageTemplate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCMessageTemplate.h; sourceTree = ""; }; DC29F4C124323C4900347658 /* OCMessageTemplate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCMessageTemplate.m; sourceTree = ""; }; + DC2A127428D05DD20088A2B7 /* OCItem+OCTypeAlias.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCTypeAlias.h"; sourceTree = ""; }; + DC2A127528D05DD30088A2B7 /* OCItem+OCTypeAlias.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCItem+OCTypeAlias.m"; sourceTree = ""; }; DC2AA56E22DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCItemPolicyProcessorAvailableOffline.h; sourceTree = ""; }; DC2AA56F22DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCItemPolicyProcessorAvailableOffline.m; sourceTree = ""; }; DC2AA57722DDD005001D5C39 /* OCSyncActionLocalCopyDelete.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSyncActionLocalCopyDelete.h; sourceTree = ""; }; @@ -975,6 +1235,11 @@ DC35969522403E5B00C4D6E6 /* OCQueryCondition+SQLBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCQueryCondition+SQLBuilder.m"; sourceTree = ""; }; DC3596982240EC0A00C4D6E6 /* OCQueryCondition+Item.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCQueryCondition+Item.h"; sourceTree = ""; }; DC3596992240EC0A00C4D6E6 /* OCQueryCondition+Item.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCQueryCondition+Item.m"; sourceTree = ""; }; + DC36EC7A27B5362800967483 /* OCConnection+OData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCConnection+OData.h"; sourceTree = ""; }; + DC36EC7B27B5362800967483 /* OCConnection+OData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+OData.m"; sourceTree = ""; }; + DC36EC7F27B560D600967483 /* OCQueryCondition+ODataBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCQueryCondition+ODataBuilder.h"; sourceTree = ""; }; + DC36EC8027B560D600967483 /* OCQueryCondition+ODataBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCQueryCondition+ODataBuilder.m"; sourceTree = ""; }; + DC36EC8327B5611500967483 /* OCODataTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCODataTypes.h; sourceTree = ""; }; DC381FC522C80BA400284699 /* OCCore+NameConflicts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCCore+NameConflicts.h"; sourceTree = ""; }; DC381FC622C80BA400284699 /* OCCore+NameConflicts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+NameConflicts.m"; sourceTree = ""; }; DC381FC922C8146F00284699 /* NSString+NameConflicts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+NameConflicts.h"; sourceTree = ""; }; @@ -989,6 +1254,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 = ""; }; @@ -1004,6 +1271,20 @@ DC3F2B50204AED8300189B9A /* OCMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCMacros.h; sourceTree = ""; }; DC3FE4B8229BD424002E009C /* OCCoreDirectoryUpdateJob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCoreDirectoryUpdateJob.h; sourceTree = ""; }; DC3FE4B9229BD424002E009C /* OCCoreDirectoryUpdateJob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCoreDirectoryUpdateJob.m; sourceTree = ""; }; + DC41C78E25EA5F7A0074F23B /* OCResourceSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSource.h; sourceTree = ""; }; + DC41C78F25EA5F7A0074F23B /* OCResourceSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSource.m; sourceTree = ""; }; + DC41C7A425EA61D70074F23B /* OCResourceTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceTypes.h; sourceTree = ""; }; + DC41C7B825EA62520074F23B /* OCResourceSourceItemThumbnails.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceItemThumbnails.h; sourceTree = ""; }; + DC41C7B925EA62520074F23B /* OCResourceSourceItemThumbnails.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceItemThumbnails.m; sourceTree = ""; }; + DC41C7CB25EA627A0074F23B /* OCResourceRequestItemThumbnail.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequestItemThumbnail.h; sourceTree = ""; }; + DC41C7CC25EA627A0074F23B /* OCResourceRequestItemThumbnail.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequestItemThumbnail.m; sourceTree = ""; }; + DC41C7DA25EA62CD0074F23B /* OCResourceSourceAvatars.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceAvatars.h; sourceTree = ""; }; + DC41C7DB25EA62CD0074F23B /* OCResourceSourceAvatars.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceAvatars.m; sourceTree = ""; }; + DC41C7ED25EA62E40074F23B /* OCResourceRequestAvatar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequestAvatar.h; sourceTree = ""; }; + DC41C7EE25EA62E40074F23B /* OCResourceRequestAvatar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequestAvatar.m; sourceTree = ""; }; + DC41C7FB25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceItemLocalThumbnails.h; sourceTree = ""; }; + DC41C7FC25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceItemLocalThumbnails.m; sourceTree = ""; }; + DC41C80925EA653D0074F23B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; DC434D0920D5AA9D00740056 /* OCCore+CommandCreateFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCCore+CommandCreateFolder.m"; sourceTree = ""; }; DC434D0C20D68C3000740056 /* NSString+OCPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+OCPath.h"; sourceTree = ""; }; DC434D0D20D68C3000740056 /* NSString+OCPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+OCPath.m"; sourceTree = ""; }; @@ -1012,8 +1293,64 @@ DC446C89206B8FBF00189B9A /* ISRunLoopThread.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = ISRunLoopThread.LICENSE; sourceTree = ""; }; DC45ABAC231018250065669D /* OCKeyValueRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCKeyValueRecord.h; sourceTree = ""; }; DC45ABAD231018250065669D /* OCKeyValueRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCKeyValueRecord.m; sourceTree = ""; }; + DC46F3C22843C66B00038880 /* OCAction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAction.h; sourceTree = ""; }; + DC46F3C32843C66B00038880 /* OCAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAction.m; sourceTree = ""; }; DC47DF742770CEE300989D84 /* NSError+OCErrorTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+OCErrorTools.h"; sourceTree = ""; }; DC47DF752770CEE300989D84 /* NSError+OCErrorTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+OCErrorTools.m"; sourceTree = ""; }; + DC47E49127A5820C0020E8EF /* GASpecialFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GASpecialFolder.m; sourceTree = ""; }; + DC47E49227A5820C0020E8EF /* GAUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAUser.m; sourceTree = ""; }; + DC47E49327A5820C0020E8EF /* GAQuota.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAQuota.m; sourceTree = ""; }; + DC47E49427A5820C0020E8EF /* GAGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAGroup.h; sourceTree = ""; }; + DC47E49527A5820C0020E8EF /* GAODataError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAODataError.m; sourceTree = ""; }; + DC47E49627A5820C0020E8EF /* GAODataErrorMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAODataErrorMain.h; sourceTree = ""; }; + DC47E49727A5820C0020E8EF /* GAItemReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAItemReference.m; sourceTree = ""; }; + DC47E49827A5820C0020E8EF /* GAOpenGraphFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAOpenGraphFile.h; sourceTree = ""; }; + DC47E49927A5820C0020E8EF /* GADriveItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADriveItem.m; sourceTree = ""; }; + DC47E49A27A5820C0020E8EF /* GATrash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GATrash.m; sourceTree = ""; }; + DC47E49B27A5820C0020E8EF /* GAIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAIdentity.m; sourceTree = ""; }; + DC47E49C27A5820C0020E8EF /* GAIdentitySet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAIdentitySet.m; sourceTree = ""; }; + DC47E49D27A5820C0020E8EF /* GAHashes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAHashes.h; sourceTree = ""; }; + DC47E49E27A5820C0020E8EF /* GAFileSystemInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAFileSystemInfo.m; sourceTree = ""; }; + DC47E49F27A5820C0020E8EF /* GAImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAImage.h; sourceTree = ""; }; + DC47E4A027A5820C0020E8EF /* GAODataErrorDetail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAODataErrorDetail.h; sourceTree = ""; }; + DC47E4A127A5820C0020E8EF /* GAFolderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAFolderView.m; sourceTree = ""; }; + DC47E4A227A5820C0020E8EF /* GARoot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GARoot.m; sourceTree = ""; }; + DC47E4A327A5820C0020E8EF /* GADrive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADrive.m; sourceTree = ""; }; + DC47E4A427A5820C0020E8EF /* GADirectoryObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADirectoryObject.h; sourceTree = ""; }; + DC47E4A527A5820C0020E8EF /* GAFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAFolder.m; sourceTree = ""; }; + DC47E4A627A5820C0020E8EF /* GAPasswordProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAPasswordProfile.h; sourceTree = ""; }; + DC47E4A727A5820C0020E8EF /* GADeleted.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADeleted.m; sourceTree = ""; }; + DC47E4A827A5820C0020E8EF /* GAGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAGroup.m; sourceTree = ""; }; + DC47E4A927A5820C0020E8EF /* GAQuota.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAQuota.h; sourceTree = ""; }; + DC47E4AA27A5820C0020E8EF /* GAUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAUser.h; sourceTree = ""; }; + DC47E4AB27A5820C0020E8EF /* GASpecialFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GASpecialFolder.h; sourceTree = ""; }; + DC47E4AC27A5820C0020E8EF /* GAOpenGraphFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAOpenGraphFile.m; sourceTree = ""; }; + DC47E4AD27A5820C0020E8EF /* GAItemReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAItemReference.h; sourceTree = ""; }; + DC47E4AE27A5820C0020E8EF /* GAODataErrorMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAODataErrorMain.m; sourceTree = ""; }; + DC47E4AF27A5820C0020E8EF /* GAODataError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAODataError.h; sourceTree = ""; }; + DC47E4B027A5820C0020E8EF /* GAFileSystemInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAFileSystemInfo.h; sourceTree = ""; }; + DC47E4B127A5820C0020E8EF /* GAHashes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAHashes.m; sourceTree = ""; }; + DC47E4B227A5820C0020E8EF /* GAIdentitySet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAIdentitySet.h; sourceTree = ""; }; + DC47E4B327A5820C0020E8EF /* GATrash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GATrash.h; sourceTree = ""; }; + DC47E4B427A5820C0020E8EF /* GAIdentity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAIdentity.h; sourceTree = ""; }; + DC47E4B527A5820C0020E8EF /* GADriveItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADriveItem.h; sourceTree = ""; }; + DC47E4B627A5820C0020E8EF /* GADeleted.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADeleted.h; sourceTree = ""; }; + DC47E4B727A5820C0020E8EF /* GAFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAFolder.h; sourceTree = ""; }; + DC47E4B827A5820C0020E8EF /* GAPasswordProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAPasswordProfile.m; sourceTree = ""; }; + DC47E4B927A5820C0020E8EF /* GADirectoryObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADirectoryObject.m; sourceTree = ""; }; + DC47E4BA27A5820C0020E8EF /* GAFolderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAFolderView.h; sourceTree = ""; }; + DC47E4BB27A5820C0020E8EF /* GARoot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GARoot.h; sourceTree = ""; }; + DC47E4BC27A5820C0020E8EF /* GADrive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADrive.h; sourceTree = ""; }; + DC47E4BD27A5820C0020E8EF /* GAODataErrorDetail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAODataErrorDetail.m; sourceTree = ""; }; + DC47E4BE27A5820C0020E8EF /* GAImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAImage.m; sourceTree = ""; }; + DC47E4EE27A7E2050020E8EF /* OCDrive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDrive.h; sourceTree = ""; }; + DC47E4EF27A7E2050020E8EF /* OCDrive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDrive.m; sourceTree = ""; }; + DC47E4F327A83D9B0020E8EF /* OCQuota.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCQuota.h; sourceTree = ""; }; + DC47E4F427A83D9B0020E8EF /* OCQuota.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCQuota.m; sourceTree = ""; }; + DC49B55128339BE200DAF13B /* NSArray+OCMapping.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+OCMapping.h"; sourceTree = ""; }; + DC49B55228339BE200DAF13B /* NSArray+OCMapping.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+OCMapping.m"; sourceTree = ""; }; + DC49B55E2837DCE700DAF13B /* OCItem+OCVFSItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCVFSItem.h"; sourceTree = ""; }; + DC49B55F2837DCE700DAF13B /* OCItem+OCVFSItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItem+OCVFSItem.m"; sourceTree = ""; }; DC4A2C5C20D4608100A47260 /* OCIssueChoice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCIssueChoice.h; sourceTree = ""; }; DC4A2C5D20D4608100A47260 /* OCIssueChoice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCIssueChoice.m; sourceTree = ""; }; DC4AFAA4206A6E7100189B9A /* OCSQLiteResultSet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSQLiteResultSet.h; sourceTree = ""; }; @@ -1033,6 +1370,12 @@ DC4B11FD220996480062BCDD /* OCProgress.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCProgress.m; sourceTree = ""; }; DC4E0A5620927048007EB05F /* OCItemVersionIdentifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCItemVersionIdentifier.h; sourceTree = ""; }; DC4E0A5720927048007EB05F /* OCItemVersionIdentifier.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCItemVersionIdentifier.m; sourceTree = ""; }; + DC510D2C27E1463900F2754F /* OCDataSourceSubscription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceSubscription.h; sourceTree = ""; }; + DC510D2D27E1463900F2754F /* OCDataSourceSubscription.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceSubscription.m; sourceTree = ""; }; + DC510D3027E1469600F2754F /* OCDataSourceSnapshot.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceSnapshot.h; sourceTree = ""; }; + DC510D3127E1469600F2754F /* OCDataSourceSnapshot.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceSnapshot.m; sourceTree = ""; }; + DC510D3427E146BD00F2754F /* OCDataItemRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataItemRecord.h; sourceTree = ""; }; + DC510D3527E146BD00F2754F /* OCDataItemRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataItemRecord.m; sourceTree = ""; }; DC51FD87247562C20069AB79 /* OCCellularManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCellularManager.h; sourceTree = ""; }; DC51FD88247562C20069AB79 /* OCCellularManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCellularManager.m; sourceTree = ""; }; DC51FD8B247565080069AB79 /* OCCellularSwitch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCellularSwitch.h; sourceTree = ""; }; @@ -1061,6 +1404,8 @@ DC5D9E6624963DED00BFFE8E /* OCMessageChoice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCMessageChoice.h; sourceTree = ""; }; DC5D9E6724963DED00BFFE8E /* OCMessageChoice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCMessageChoice.m; sourceTree = ""; }; DC61E930221423D2002889D6 /* HTTPPipelineTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTTPPipelineTests.m; sourceTree = ""; }; + DC622C4C29019515001D73A0 /* OCLocale+SystemLanguage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCLocale+SystemLanguage.h"; sourceTree = ""; }; + DC622C4D29019515001D73A0 /* OCLocale+SystemLanguage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCLocale+SystemLanguage.m"; sourceTree = ""; }; DC62FD60211B11FD0034B628 /* OCItem+OCThumbnail.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCThumbnail.h"; sourceTree = ""; }; DC62FD61211B11FD0034B628 /* OCItem+OCThumbnail.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItem+OCThumbnail.m"; sourceTree = ""; }; DC66C6B220540DBD00189B9A /* NSDate+OCDateParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+OCDateParser.h"; sourceTree = ""; }; @@ -1081,6 +1426,10 @@ DC6BFFEC231FB9F8005FA5CB /* OCEventRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCEventRecord.m; sourceTree = ""; }; DC6BFFF423206215005FA5CB /* OCEventQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCEventQueue.h; sourceTree = ""; }; DC6BFFF523206215005FA5CB /* OCEventQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCEventQueue.m; sourceTree = ""; }; + DC6C0A4A2923A0050045FF2A /* OCBookmark+DataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmark+DataItem.h"; sourceTree = ""; }; + DC6C0A4B2923A0050045FF2A /* OCBookmark+DataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCBookmark+DataItem.m"; sourceTree = ""; }; + DC6C0A4F2923A59A0045FF2A /* OCDataSourceMapped.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceMapped.h; sourceTree = ""; }; + DC6C0A502923A59A0045FF2A /* OCDataSourceMapped.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceMapped.m; sourceTree = ""; }; DC6CC30526428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAuthenticationBrowserSessionCustomScheme.h; sourceTree = ""; }; DC6CC30626428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAuthenticationBrowserSessionCustomScheme.m; sourceTree = ""; }; DC6CC30D2642A0720040ECAC /* OCAuthenticationBrowserSessionMIBrowser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAuthenticationBrowserSessionMIBrowser.h; sourceTree = ""; }; @@ -1101,6 +1450,8 @@ DC6DEEB724C5C82400E3772E /* OCHTTPPolicyBookmark.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCHTTPPolicyBookmark.h; sourceTree = ""; }; DC6DEEB824C5C82400E3772E /* OCHTTPPolicyBookmark.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCHTTPPolicyBookmark.m; sourceTree = ""; }; DC6F81512684C32600863D8A /* DAVRawResponseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DAVRawResponseTests.m; sourceTree = ""; }; + DC6FDAF029507579004F0C7F /* OCShare+OCDataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCShare+OCDataItem.h"; sourceTree = ""; }; + DC6FDAF129507579004F0C7F /* OCShare+OCDataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCShare+OCDataItem.m"; sourceTree = ""; }; DC7014232209CE7A009D4FD9 /* OCHTTPPipelineManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCHTTPPipelineManager.h; sourceTree = ""; }; DC7014242209CE7A009D4FD9 /* OCHTTPPipelineManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCHTTPPipelineManager.m; sourceTree = ""; }; DC701475220AE696009D4FD9 /* OCHTTPPipelineTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCHTTPPipelineTask.h; sourceTree = ""; }; @@ -1138,6 +1489,8 @@ DC73F3AB254BF95C00CE5FA9 /* OCClassSettings+Documentation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCClassSettings+Documentation.m"; sourceTree = ""; }; DC73F3BD254BFE9900CE5FA9 /* NSArray+ObjCRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+ObjCRuntime.h"; sourceTree = ""; }; DC73F3BE254BFE9900CE5FA9 /* NSArray+ObjCRuntime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+ObjCRuntime.m"; sourceTree = ""; }; + DC74E44227AD86DA005B4B03 /* OCLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLocation.h; sourceTree = ""; }; + DC74E44327AD86DA005B4B03 /* OCLocation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLocation.m; sourceTree = ""; }; DC75D309214BF1BA00B6FB62 /* NSString+OCFormatting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+OCFormatting.h"; sourceTree = ""; }; DC75D30A214BF1BA00B6FB62 /* NSString+OCFormatting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+OCFormatting.m"; sourceTree = ""; }; DC75D30D214C015E00B6FB62 /* OCItem+OCItemCreationDebugging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCItemCreationDebugging.h"; sourceTree = ""; }; @@ -1169,6 +1522,14 @@ DC8245B121FB31E500775AB9 /* OCActivityManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCActivityManager.h; sourceTree = ""; }; DC8245B221FB31E500775AB9 /* OCActivityManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCActivityManager.m; sourceTree = ""; }; DC8245B921FB334200775AB9 /* OCCore+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCCore+Internal.h"; sourceTree = ""; }; + DC82665D2818972200F91F7D /* OCVaultLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVaultLocation.h; sourceTree = ""; }; + DC82665E2818972200F91F7D /* OCVaultLocation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCVaultLocation.m; sourceTree = ""; }; + DC826662281A9E7100F91F7D /* CONCEPT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONCEPT.md; sourceTree = ""; }; + DC826663281AC59D00F91F7D /* OCVFSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVFSCore.h; sourceTree = ""; }; + DC826664281AC59D00F91F7D /* OCVFSCore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCVFSCore.m; sourceTree = ""; }; + DC826667281AC5B000F91F7D /* OCVFSNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVFSNode.h; sourceTree = ""; }; + DC826668281AC5B000F91F7D /* OCVFSNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCVFSNode.m; sourceTree = ""; }; + DC82667E281FE66600F91F7D /* OCVFSTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVFSTypes.h; sourceTree = ""; }; DC8556EB204DEA2900189B9A /* OCHTTPDAVRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCHTTPDAVRequest.h; sourceTree = ""; }; DC8556EC204DEA2900189B9A /* OCHTTPDAVRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCHTTPDAVRequest.m; sourceTree = ""; }; DC8556EF204DEB9200189B9A /* OCXMLNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCXMLNode.h; sourceTree = ""; }; @@ -1195,11 +1556,47 @@ DC8EB30323952084009148F9 /* OCAuthenticationBrowserSessionUIWebView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAuthenticationBrowserSessionUIWebView.m; sourceTree = ""; }; DC8FE6FD221CAF280016BDEE /* OCProgressManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCProgressManager.h; sourceTree = ""; }; DC8FE6FE221CAF280016BDEE /* OCProgressManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCProgressManager.m; sourceTree = ""; }; + DC9219CD2964CB4400F538EE /* GAEducationUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEducationUser.h; sourceTree = ""; }; + DC9219CE2964CB4400F538EE /* GAAppRoleAssignment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAAppRoleAssignment.h; sourceTree = ""; }; + DC9219CF2964CB4500F538EE /* GAEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEntity.h; sourceTree = ""; }; + DC9219D02964CB4500F538EE /* GAAppRole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAAppRole.h; sourceTree = ""; }; + DC9219D12964CB4500F538EE /* GAApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAApplication.h; sourceTree = ""; }; + DC9219D22964CB4500F538EE /* GAEducationClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEducationClass.m; sourceTree = ""; }; + DC9219D32964CB4500F538EE /* GAEducationUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEducationUser.m; sourceTree = ""; }; + DC9219D42964CB4500F538EE /* GAApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAApplication.m; sourceTree = ""; }; + DC9219D52964CB4500F538EE /* GAEducationSchool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEducationSchool.m; sourceTree = ""; }; + DC9219D62964CB4500F538EE /* GAAppRole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAAppRole.m; sourceTree = ""; }; + DC9219D72964CB4500F538EE /* GAEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEntity.m; sourceTree = ""; }; + DC9219D82964CB4500F538EE /* GAEducationSchool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEducationSchool.h; sourceTree = ""; }; + DC9219D92964CB4500F538EE /* GAEducationOrganization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEducationOrganization.h; sourceTree = ""; }; + DC9219DA2964CB4500F538EE /* GAEducationClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAEducationClass.h; sourceTree = ""; }; + DC9219DB2964CB4500F538EE /* GAEducationOrganization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAEducationOrganization.m; sourceTree = ""; }; + DC9219DC2964CB4500F538EE /* GAAppRoleAssignment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAAppRoleAssignment.m; sourceTree = ""; }; + DC9219ED2964CB6000F538EE /* GATagAssignment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GATagAssignment.m; sourceTree = ""; }; + DC9219EE2964CB6000F538EE /* GATagUnassignment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GATagUnassignment.m; sourceTree = ""; }; + DC9219EF2964CB6000F538EE /* GATagUnassignment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GATagUnassignment.h; sourceTree = ""; }; + DC9219F02964CB6000F538EE /* GAObjectIdentity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAObjectIdentity.h; sourceTree = ""; }; + DC9219F12964CB6000F538EE /* GATagAssignment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GATagAssignment.h; sourceTree = ""; }; + DC9219F22964CB6000F538EE /* GAObjectIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAObjectIdentity.m; sourceTree = ""; }; DC971BEC20D15D4400428EF1 /* OCSQLiteQueryCondition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSQLiteQueryCondition.h; sourceTree = ""; }; DC971BED20D15D4400428EF1 /* OCSQLiteQueryCondition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSQLiteQueryCondition.m; sourceTree = ""; }; DC98BDF321E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCoreNetworkMonitorSignalProvider.h; sourceTree = ""; }; DC98BDF421E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCoreNetworkMonitorSignalProvider.m; sourceTree = ""; }; DC98BDF721E73EFF003B5658 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; + DC9A116627CFCC1200D90BA4 /* GAPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAPermission.h; sourceTree = ""; }; + DC9A116727CFCC1200D90BA4 /* GAPermission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAPermission.m; sourceTree = ""; }; + DC9B4FCF2941FF540037F8F8 /* OCCertificateStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCertificateStore.h; sourceTree = ""; }; + DC9B4FD02941FF540037F8F8 /* OCCertificateStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCertificateStore.m; sourceTree = ""; }; + DC9B4FD32941FF630037F8F8 /* OCCertificateStoreRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCertificateStoreRecord.h; sourceTree = ""; }; + DC9B4FD42941FF630037F8F8 /* OCCertificateStoreRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCertificateStoreRecord.m; sourceTree = ""; }; + DC9C19E027839E440021222E /* OCResourceSourceStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceStorage.h; sourceTree = ""; }; + DC9C19E127839E440021222E /* OCResourceSourceStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceStorage.m; sourceTree = ""; }; + DC9C19E4278488360021222E /* OCResourceManagerJob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceManagerJob.h; sourceTree = ""; }; + DC9C19E5278488360021222E /* OCResourceManagerJob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceManagerJob.m; sourceTree = ""; }; + DC9C19EC278CD0B30021222E /* OCDatabase+ResourceStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCDatabase+ResourceStorage.h"; sourceTree = ""; }; + DC9C19ED278CD0B30021222E /* OCDatabase+ResourceStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCDatabase+ResourceStorage.m"; sourceTree = ""; }; + DC9C19F0278EE7230021222E /* OCResourceRequestImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequestImage.h; sourceTree = ""; }; + DC9C19F1278EE7230021222E /* OCResourceRequestImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequestImage.m; sourceTree = ""; }; DC9D22E825A8754200CF5675 /* OCHTTPRequest+JSON.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCHTTPRequest+JSON.h"; sourceTree = ""; }; DC9D22E925A8754200CF5675 /* OCHTTPRequest+JSON.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCHTTPRequest+JSON.m"; sourceTree = ""; }; DCA35D4B24CF685B00DBE2B0 /* OCDiagnosticNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDiagnosticNode.h; sourceTree = ""; }; @@ -1268,6 +1665,14 @@ DCB0A46321B922A400FAC4E9 /* OCCoreConnectionStatusSignalProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCoreConnectionStatusSignalProvider.m; sourceTree = ""; }; DCB0A46A21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCoreServerStatusSignalProvider.h; sourceTree = ""; }; DCB0A46B21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCoreServerStatusSignalProvider.m; sourceTree = ""; }; + DCB330C429EF2F0F00BFF393 /* OCIdentity+DataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCIdentity+DataItem.h"; sourceTree = ""; }; + DCB330C529EF2F0F00BFF393 /* OCIdentity+DataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCIdentity+DataItem.m"; sourceTree = ""; }; + DCB330DB29F142FB00BFF393 /* OCShareRole+OCDataItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCShareRole+OCDataItem.h"; sourceTree = ""; }; + DCB330DC29F142FB00BFF393 /* OCShareRole+OCDataItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCShareRole+OCDataItem.m"; sourceTree = ""; }; + DCB4F6E528324A3A005AD181 /* OCVaultDriveList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCVaultDriveList.h; sourceTree = ""; }; + DCB4F6E628324A3A005AD181 /* OCVaultDriveList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCVaultDriveList.m; sourceTree = ""; }; + DCB4F6EA28324B90005AD181 /* OCDataSourceKVO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceKVO.h; sourceTree = ""; }; + DCB4F6EB28324B90005AD181 /* OCDataSourceKVO.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceKVO.m; sourceTree = ""; }; DCB572AC2099EFC600B793CE /* OCDatabase+Schemas.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCDatabase+Schemas.h"; sourceTree = ""; }; DCB572AD2099EFC600B793CE /* OCDatabase+Schemas.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCDatabase+Schemas.m"; sourceTree = ""; }; DCB6D05622A13E7500CA47C5 /* NSString+OCSQLTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+OCSQLTools.h"; sourceTree = ""; }; @@ -1276,6 +1681,16 @@ DCC3700E24D4B3B7008B0DEB /* OCDatabase+Diagnostic.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCDatabase+Diagnostic.m"; sourceTree = ""; }; DCC3701124D4D134008B0DEB /* OCScanJobActivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCScanJobActivity.h; sourceTree = ""; }; DCC3701224D4D134008B0DEB /* OCScanJobActivity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCScanJobActivity.m; sourceTree = ""; }; + DCC4F3E827D74DE300ABF4C9 /* OCDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSource.h; sourceTree = ""; }; + DCC4F3E927D74DE300ABF4C9 /* OCDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSource.m; sourceTree = ""; }; + DCC4F3F027D74E7000ABF4C9 /* CONCEPT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONCEPT.md; sourceTree = ""; }; + DCC4F3F127D756C300ABF4C9 /* OCDataTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataTypes.h; sourceTree = ""; }; + DCC4F3F527D757FE00ABF4C9 /* OCDataConverter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataConverter.h; sourceTree = ""; }; + DCC4F3F627D757FE00ABF4C9 /* OCDataConverter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataConverter.m; sourceTree = ""; }; + DCC4F3F927D75AA600ABF4C9 /* OCDataRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataRenderer.h; sourceTree = ""; }; + DCC4F3FA27D75AA600ABF4C9 /* OCDataRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataRenderer.m; sourceTree = ""; }; + DCC4F3FD27D75BF700ABF4C9 /* OCDataConverterPipeline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataConverterPipeline.h; sourceTree = ""; }; + DCC4F3FE27D75BF700ABF4C9 /* OCDataConverterPipeline.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataConverterPipeline.m; sourceTree = ""; }; DCC599F222EEE65700499B29 /* OCCore+Claims.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCCore+Claims.h"; sourceTree = ""; }; DCC599F322EEE65700499B29 /* OCCore+Claims.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+Claims.m"; sourceTree = ""; }; DCC6563B20C979FB00110A97 /* TestTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestTools.h; sourceTree = ""; }; @@ -1338,11 +1753,15 @@ DCD0389E2542CA4500F97534 /* NSString+OCClassSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+OCClassSettings.h"; sourceTree = ""; }; DCD0389F2542CA4500F97534 /* NSString+OCClassSettings.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+OCClassSettings.m"; sourceTree = ""; }; DCD039432544D17E00F97534 /* class-settings-sdk */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "class-settings-sdk"; path = "doc/class-settings-sdk"; sourceTree = ""; }; + DCD09E3A2A1B573100BFF393 /* OCResourceSourceURL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceURL.h; sourceTree = ""; }; + DCD09E3B2A1B573100BFF393 /* OCResourceSourceURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceURL.m; sourceTree = ""; }; DCD2D40122F059190071FB8F /* OCClassSettingsUserPreferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCClassSettingsUserPreferences.h; sourceTree = ""; }; DCD2D40222F059190071FB8F /* OCClassSettingsUserPreferences.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCClassSettingsUserPreferences.m; sourceTree = ""; }; DCD2D42222F180EF0071FB8F /* ItemPolicyTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ItemPolicyTests.m; sourceTree = ""; }; DCD3439A2059319C00189B9A /* OCSQLiteDB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSQLiteDB.h; sourceTree = ""; }; DCD3439B2059319C00189B9A /* OCSQLiteDB.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSQLiteDB.m; sourceTree = ""; }; + DCD3F69E27EA887600D86662 /* OCDataSourceArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceArray.h; sourceTree = ""; }; + DCD3F69F27EA887600D86662 /* OCDataSourceArray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceArray.m; sourceTree = ""; }; DCD49A0824E68BD5008D9544 /* OCBookmark+Diagnostics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmark+Diagnostics.h"; sourceTree = ""; }; DCD49A0924E68BD5008D9544 /* OCBookmark+Diagnostics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCBookmark+Diagnostics.m"; sourceTree = ""; }; DCD63274223BB1710090169E /* OCCapabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCapabilities.h; sourceTree = ""; }; @@ -1364,6 +1783,13 @@ DCDB76232739D51200EE7A06 /* OCServerLocatorLookupTable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCServerLocatorLookupTable.m; sourceTree = ""; }; DCDB76262739EF9A00EE7A06 /* OCExtension+ServerLocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCExtension+ServerLocator.h"; sourceTree = ""; }; DCDB76272739EF9A00EE7A06 /* OCExtension+ServerLocator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCExtension+ServerLocator.m"; sourceTree = ""; }; + DCDBB5E52523DDA200FAD707 /* OCConnection+Avatars.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+Avatars.m"; sourceTree = ""; }; + DCDBB5E82523E3AF00FAD707 /* OCAvatar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCAvatar.h; sourceTree = ""; }; + DCDBB5E92523E3AF00FAD707 /* OCAvatar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCAvatar.m; sourceTree = ""; }; + DCDBB5F525248B0300FAD707 /* OCResource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResource.h; sourceTree = ""; }; + DCDBB5F625248B0300FAD707 /* OCResource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResource.m; sourceTree = ""; }; + DCDBB5F925248B0F00FAD707 /* OCResourceRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequest.h; sourceTree = ""; }; + DCDBB5FA25248B0F00FAD707 /* OCResourceRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequest.m; sourceTree = ""; }; DCDBEE2A2048A6A700189B9A /* OCConnection+Setup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+Setup.m"; sourceTree = ""; }; DCDBEE2E2048A71200189B9A /* OCConnection+Tools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+Tools.m"; sourceTree = ""; }; DCDBEE322048A8BC00189B9A /* OCConnection+Authentication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+Authentication.m"; sourceTree = ""; }; @@ -1376,8 +1802,8 @@ DCDCA61B245093E800AFA158 /* NSURL+OCPrivateLink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+OCPrivateLink.m"; sourceTree = ""; }; DCDD9B12222986D50052A001 /* OCShare+OCXMLObjectCreation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCShare+OCXMLObjectCreation.h"; sourceTree = ""; }; DCDD9B13222986D50052A001 /* OCShare+OCXMLObjectCreation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCShare+OCXMLObjectCreation.m"; sourceTree = ""; }; - DCDD9B16222989E50052A001 /* OCRecipient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCRecipient.h; sourceTree = ""; }; - DCDD9B17222989E50052A001 /* OCRecipient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCRecipient.m; sourceTree = ""; }; + DCDD9B16222989E50052A001 /* OCIdentity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCIdentity.h; sourceTree = ""; }; + DCDD9B17222989E50052A001 /* OCIdentity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCIdentity.m; sourceTree = ""; }; DCDD9B1A22298D050052A001 /* OCGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCGroup.h; sourceTree = ""; }; DCDD9B1B22298D050052A001 /* OCGroup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCGroup.m; sourceTree = ""; }; DCDD9B2922312ED80052A001 /* OCRateLimiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCRateLimiter.h; sourceTree = ""; }; @@ -1392,6 +1818,8 @@ DCE2661B2113323C0001FB2C /* OCCore+CommandDownload.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+CommandDownload.m"; sourceTree = ""; }; DCE2661E211348AF0001FB2C /* OCCore+CommandLocalModification.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCCore+CommandLocalModification.m"; sourceTree = ""; }; DCE2661F211348B00001FB2C /* OCCore+CommandLocalImport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCCore+CommandLocalImport.m"; sourceTree = ""; }; + DCE2F04127FB928B00E9E136 /* NSArray+OCFiltering.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+OCFiltering.h"; sourceTree = ""; }; + DCE2F04227FB928B00E9E136 /* NSArray+OCFiltering.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+OCFiltering.m"; sourceTree = ""; }; DCE370922099D18100114981 /* OCDatabaseConsistentOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDatabaseConsistentOperation.h; sourceTree = ""; }; DCE370932099D18100114981 /* OCDatabaseConsistentOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDatabaseConsistentOperation.m; sourceTree = ""; }; DCE3D4E22701C40B0074C254 /* OCCoreUpdateScheduleRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCoreUpdateScheduleRecord.h; sourceTree = ""; }; @@ -1400,6 +1828,14 @@ DCE451A42459AD3F0074363F /* OCTUSJob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCTUSJob.m; sourceTree = ""; }; DCE48DD6220E1C7A00839E97 /* OCHTTPPipelineTaskCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCHTTPPipelineTaskCache.h; sourceTree = ""; }; DCE48DD7220E1C7B00839E97 /* OCHTTPPipelineTaskCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCHTTPPipelineTaskCache.m; sourceTree = ""; }; + DCE62EA72771EA0100E3193F /* OCResourceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceManager.h; sourceTree = ""; }; + DCE62EA82771EA0200E3193F /* OCResourceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceManager.m; sourceTree = ""; }; + DCE62EAB2771ED5700E3193F /* OCResourceImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceImage.h; sourceTree = ""; }; + DCE62EAC2771ED5700E3193F /* OCResourceImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceImage.m; sourceTree = ""; }; + DCE741D629B7DD5000BFF393 /* OCServerInstance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCServerInstance.h; sourceTree = ""; }; + DCE741D729B7DD5000BFF393 /* OCServerInstance.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCServerInstance.m; sourceTree = ""; }; + DCE741DA29B7EA7000BFF393 /* OCBookmark+ServerInstance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmark+ServerInstance.h"; sourceTree = ""; }; + DCE741DB29B7EA7000BFF393 /* OCBookmark+ServerInstance.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCBookmark+ServerInstance.m"; sourceTree = ""; }; DCE784F722325D4F00733F01 /* OCConnection+Recipients.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+Recipients.m"; sourceTree = ""; }; DCE784FA2232748100733F01 /* OCHTTPResponse+DAVError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCHTTPResponse+DAVError.h"; sourceTree = ""; }; DCE784FB2232748100733F01 /* OCHTTPResponse+DAVError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCHTTPResponse+DAVError.m"; sourceTree = ""; }; @@ -1412,7 +1848,18 @@ DCEAA0CE25CEB7F90017F99B /* OCLockRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLockRequest.h; sourceTree = ""; }; DCEAA0CF25CEB7F90017F99B /* OCLockRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLockRequest.m; sourceTree = ""; }; DCEAA0DC25CEDBC40017F99B /* LockTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LockTests.m; sourceTree = ""; }; + DCEAF0442805B80D00980B6D /* OCResourceSourceDriveItems.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceDriveItems.h; sourceTree = ""; }; + DCEAF0452805B80D00980B6D /* OCResourceSourceDriveItems.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceDriveItems.m; sourceTree = ""; }; + DCEAF0482805B83B00980B6D /* OCResourceRequestDriveItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceRequestDriveItem.h; sourceTree = ""; }; + DCEAF0492805B83B00980B6D /* OCResourceRequestDriveItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceRequestDriveItem.m; sourceTree = ""; }; + DCEAF04C2806201300980B6D /* OCResourceText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceText.h; sourceTree = ""; }; + DCEAF04D2806201300980B6D /* OCResourceText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceText.m; sourceTree = ""; }; DCEB94DA21105FDE004EF8D7 /* rainbow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rainbow.png; sourceTree = ""; }; + DCED67D127F1A48000686E4F /* OCDataTypes.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataTypes.m; sourceTree = ""; }; + DCED67D527F1A7B200686E4F /* OCCore+DataSources.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCCore+DataSources.h"; sourceTree = ""; }; + DCED67D627F1A7B200686E4F /* OCCore+DataSources.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+DataSources.m"; sourceTree = ""; }; + DCED67DA27F1B13600686E4F /* OCDataItemPresentable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataItemPresentable.h; sourceTree = ""; }; + DCED67DB27F1B13600686E4F /* OCDataItemPresentable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataItemPresentable.m; sourceTree = ""; }; DCEE0B5425E68C53006534B5 /* OCBookmarkManager+ItemResolution.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmarkManager+ItemResolution.h"; sourceTree = ""; }; DCEE0B5525E68C53006534B5 /* OCBookmarkManager+ItemResolution.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCBookmarkManager+ItemResolution.m"; sourceTree = ""; }; DCEE0B6D25E697AF006534B5 /* OCCoreManager+ItemResolution.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCCoreManager+ItemResolution.h"; sourceTree = ""; }; @@ -1432,6 +1879,13 @@ DCEEB2F02047094500189B9A /* NSData+OCHash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+OCHash.m"; sourceTree = ""; }; DCEEB2F3204802CF00189B9A /* OCIssue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCIssue.h; sourceTree = ""; }; DCEEB2F4204802CF00189B9A /* OCIssue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCIssue.m; sourceTree = ""; }; + DCF00BF327E28A77001F2AFC /* OCDataSourceSubscription+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCDataSourceSubscription+Internal.h"; sourceTree = ""; }; + DCF00BF427E28A77001F2AFC /* OCDataSourceSubscription+Internal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCDataSourceSubscription+Internal.m"; sourceTree = ""; }; + DCF00C1827E698A4001F2AFC /* DataSourceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DataSourceTests.m; sourceTree = ""; }; + DCF072E22798630900E0B01D /* OCResourceTextPlaceholder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceTextPlaceholder.h; sourceTree = ""; }; + DCF072E32798630900E0B01D /* OCResourceTextPlaceholder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceTextPlaceholder.m; sourceTree = ""; }; + DCF072E62798652500E0B01D /* OCResourceSourceAvatarPlaceholders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCResourceSourceAvatarPlaceholders.h; sourceTree = ""; }; + DCF072E72798652500E0B01D /* OCResourceSourceAvatarPlaceholders.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCResourceSourceAvatarPlaceholders.m; sourceTree = ""; }; DCF163F0274B917C00E0182A /* OCSQLiteCollation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSQLiteCollation.h; sourceTree = ""; }; DCF163F1274B917C00E0182A /* OCSQLiteCollation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSQLiteCollation.m; sourceTree = ""; }; DCF163F4274BA6C300E0182A /* OCSQLiteCollationLocalized.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSQLiteCollationLocalized.h; sourceTree = ""; }; @@ -1442,12 +1896,37 @@ DCF1C68E2631C296004D8B0F /* OCMeasurementEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCMeasurementEvent.m; sourceTree = ""; }; DCF39B532458268E00DEA137 /* OCTUSHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCTUSHeader.h; sourceTree = ""; }; DCF39B542458268E00DEA137 /* OCTUSHeader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCTUSHeader.m; sourceTree = ""; }; + DCF575CF279562DF003BEBBA /* OCViewProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCViewProvider.h; sourceTree = ""; }; + DCF575D5279567AB003BEBBA /* OCPlatform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCPlatform.h; sourceTree = ""; }; + DCF575D6279567AB003BEBBA /* OCPlatform.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCPlatform.m; sourceTree = ""; }; + DCF575DD27956D84003BEBBA /* OCViewProviderContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCViewProviderContext.h; sourceTree = ""; }; + DCF575DE27956D84003BEBBA /* OCViewProviderContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCViewProviderContext.m; sourceTree = ""; }; DCF95AE825666FBB00806D2A /* OCClassSetting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCClassSetting.h; sourceTree = ""; }; DCF95AE925666FBB00806D2A /* OCClassSetting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCClassSetting.m; sourceTree = ""; }; + DCFA564C2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCClassSettingsFlatSourcePostBuild.h; sourceTree = ""; }; + DCFA564D2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCClassSettingsFlatSourcePostBuild.m; sourceTree = ""; }; DCFBACF021BAA77F00943F76 /* largePropFindResponse1000.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = largePropFindResponse1000.xml; sourceTree = ""; }; DCFBACF621BAB35A00943F76 /* PerformanceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformanceTests.m; sourceTree = ""; }; DCFBACF821BAB5F500943F76 /* OCDetailedPerformanceTestCase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDetailedPerformanceTestCase.m; sourceTree = ""; }; DCFBACFA21BAB6F200943F76 /* OCDetailedPerformanceTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDetailedPerformanceTestCase.h; sourceTree = ""; }; + DCFC9ED628003EFB005D9144 /* GAShared.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GAShared.m; sourceTree = ""; }; + DCFC9ED728003EFB005D9144 /* GAShared.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GAShared.h; sourceTree = ""; }; + DCFC9EDA28003F0D005D9144 /* GARemoteItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GARemoteItem.h; sourceTree = ""; }; + DCFC9EDB28003F0D005D9144 /* GARemoteItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GARemoteItem.m; sourceTree = ""; }; + DCFC9EDF28004791005D9144 /* OCDataSourceComposition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCDataSourceComposition.h; sourceTree = ""; }; + DCFC9EE028004791005D9144 /* OCDataSourceComposition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCDataSourceComposition.m; sourceTree = ""; }; + DCFE3B7827A1666B00939415 /* GAGraphObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GAGraphObject.h; sourceTree = ""; }; + DCFE3B7C27A1669300939415 /* GAGraphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GAGraphContext.h; sourceTree = ""; }; + DCFE3B7D27A1669300939415 /* GAGraphContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GAGraphContext.m; sourceTree = ""; }; + DCFE3B8027A167C700939415 /* GAGraph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GAGraph.h; sourceTree = ""; }; + DCFE3B8627A16AE800939415 /* OCConnection+GraphAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCConnection+GraphAPI.h"; sourceTree = ""; }; + DCFE3B8727A16AE800939415 /* OCConnection+GraphAPI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCConnection+GraphAPI.m"; sourceTree = ""; }; + DCFE3B9B27A1A6E500939415 /* GAGraphData+Decoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GAGraphData+Decoder.h"; sourceTree = ""; }; + DCFE3B9C27A1A6E500939415 /* GAGraphData+Decoder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "GAGraphData+Decoder.m"; sourceTree = ""; }; + DCFE681E28D857B500091D2A /* NSError+OCISError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+OCISError.h"; sourceTree = ""; }; + DCFE681F28D857B500091D2A /* NSError+OCISError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+OCISError.m"; sourceTree = ""; }; + DCFE682228D865BD00091D2A /* NSDictionary+OCFormEncoding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+OCFormEncoding.h"; sourceTree = ""; }; + DCFE682328D865BD00091D2A /* NSDictionary+OCFormEncoding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+OCFormEncoding.m"; sourceTree = ""; }; DCFF1AAC216552C000ABE40A /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; DCFF1AAE21655C8800ABE40A /* OCItem+OCFileURLMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+OCFileURLMetadata.h"; sourceTree = ""; }; DCFF1AAF21655C8800ABE40A /* OCItem+OCFileURLMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItem+OCFileURLMetadata.m"; sourceTree = ""; }; @@ -1547,10 +2026,41 @@ DC0376EC271B1C8500151E8C /* OCLocaleFilterClassSettings.h */, DC0376F1271B1D4600151E8C /* OCLocaleFilterVariables.m */, DC0376F0271B1D4600151E8C /* OCLocaleFilterVariables.h */, + DC622C4D29019515001D73A0 /* OCLocale+SystemLanguage.m */, + DC622C4C29019515001D73A0 /* OCLocale+SystemLanguage.h */, ); path = Locale; sourceTree = ""; }; + DC04FFBF27F5988F00F22569 /* Subscriptions */ = { + isa = PBXGroup; + children = ( + DC510D2D27E1463900F2754F /* OCDataSourceSubscription.m */, + DC510D2C27E1463900F2754F /* OCDataSourceSubscription.h */, + DCF00BF427E28A77001F2AFC /* OCDataSourceSubscription+Internal.m */, + DCF00BF327E28A77001F2AFC /* OCDataSourceSubscription+Internal.h */, + ); + path = Subscriptions; + sourceTree = ""; + }; + DC04FFC027F5989B00F22569 /* Snapshots */ = { + isa = PBXGroup; + children = ( + DC510D3127E1469600F2754F /* OCDataSourceSnapshot.m */, + DC510D3027E1469600F2754F /* OCDataSourceSnapshot.h */, + ); + path = Snapshots; + sourceTree = ""; + }; + DC04FFC127F598A800F22569 /* Item Records */ = { + isa = PBXGroup; + children = ( + DC510D3527E146BD00F2754F /* OCDataItemRecord.m */, + DC510D3427E146BD00F2754F /* OCDataItemRecord.h */, + ); + path = "Item Records"; + sourceTree = ""; + }; DC07C28B21244F9F00B815A4 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1571,6 +2081,77 @@ path = Extensions; sourceTree = ""; }; + DC0BE5B328F80BCE00CE2101 /* Avatars */ = { + isa = PBXGroup; + children = ( + DCDBB5E92523E3AF00FAD707 /* OCAvatar.m */, + DCDBB5E82523E3AF00FAD707 /* OCAvatar.h */, + ); + path = Avatars; + sourceTree = ""; + }; + DC0BE5B428F80D8F00CE2101 /* Image Types */ = { + isa = PBXGroup; + children = ( + DC0BE5B528F80D9C00CE2101 /* Symbols */, + DC0BE5B328F80BCE00CE2101 /* Avatars */, + ); + path = "Image Types"; + sourceTree = ""; + }; + DC0BE5B528F80D9C00CE2101 /* Symbols */ = { + isa = PBXGroup; + children = ( + DC0BE5B728F80DBF00CE2101 /* OCSymbol.m */, + DC0BE5B628F80DBF00CE2101 /* OCSymbol.h */, + ); + path = Symbols; + sourceTree = ""; + }; + DC0BE5BA28F8145500CE2101 /* Roles */ = { + isa = PBXGroup; + children = ( + DC0BE5B028F80BBA00CE2101 /* OCShareRole.m */, + DC0BE5AF28F80BBA00CE2101 /* OCShareRole.h */, + DCB330DC29F142FB00BFF393 /* OCShareRole+OCDataItem.m */, + DCB330DB29F142FB00BFF393 /* OCShareRole+OCDataItem.h */, + ); + path = Roles; + sourceTree = ""; + }; + DC0BE5BB28F9426100CE2101 /* Statistics */ = { + isa = PBXGroup; + children = ( + DC0BE5BD28F9427900CE2101 /* OCStatistic.m */, + DC0BE5BC28F9427900CE2101 /* OCStatistic.h */, + ); + path = Statistics; + sourceTree = ""; + }; + DC0CE17828C5DDC6009ABDFB /* App Providers */ = { + isa = PBXGroup; + children = ( + DC0CE17A28C5DDE8009ABDFB /* OCAppProvider.m */, + DC0CE17928C5DDE8009ABDFB /* OCAppProvider.h */, + DC0CE18628C63B15009ABDFB /* OCAppProviderApp.m */, + DC0CE18528C63B15009ABDFB /* OCAppProviderApp.h */, + DC0CE18A28C63B2E009ABDFB /* OCAppProviderFileType.m */, + DC0CE18928C63B2E009ABDFB /* OCAppProviderFileType.h */, + ); + path = "App Providers"; + sourceTree = ""; + }; + DC0CE19328C8904E009ABDFB /* URLItems */ = { + isa = PBXGroup; + children = ( + DC0CE19928C89227009ABDFB /* OCResourceSourceURLItems.m */, + DC0CE19828C89227009ABDFB /* OCResourceSourceURLItems.h */, + DC0CE19528C8907D009ABDFB /* OCResourceRequestURLItem.m */, + DC0CE19428C8907D009ABDFB /* OCResourceRequestURLItem.h */, + ); + path = URLItems; + sourceTree = ""; + }; DC139CC220DBB7780090175A /* Checksums */ = { isa = PBXGroup; children = ( @@ -1637,6 +2218,10 @@ DCDC62DF24D8A04900585261 /* NSArray+OCSegmentedProcessing.h */, DC73F3BE254BFE9900CE5FA9 /* NSArray+ObjCRuntime.m */, DC73F3BD254BFE9900CE5FA9 /* NSArray+ObjCRuntime.h */, + DCE2F04227FB928B00E9E136 /* NSArray+OCFiltering.m */, + DCE2F04127FB928B00E9E136 /* NSArray+OCFiltering.h */, + DC49B55228339BE200DAF13B /* NSArray+OCMapping.m */, + DC49B55128339BE200DAF13B /* NSArray+OCMapping.h */, ); path = Foundation; sourceTree = ""; @@ -1874,6 +2459,10 @@ DC2565F32260C86A00828AA5 /* OCCertificateRuleChecker.h */, DC2565F02260BE3600828AA5 /* OCCertificate+PrivacyLogging.m */, DC2565EF2260BE3600828AA5 /* OCCertificate+PrivacyLogging.h */, + DC9B4FD02941FF540037F8F8 /* OCCertificateStore.m */, + DC9B4FCF2941FF540037F8F8 /* OCCertificateStore.h */, + DC9B4FD42941FF630037F8F8 /* OCCertificateStoreRecord.m */, + DC9B4FD32941FF630037F8F8 /* OCCertificateStoreRecord.h */, ); path = Security; sourceTree = ""; @@ -1933,6 +2522,18 @@ path = Condition; sourceTree = ""; }; + DC36EC7E27B560B400967483 /* OData */ = { + isa = PBXGroup; + children = ( + DC36EC8327B5611500967483 /* OCODataTypes.h */, + DC36EC7B27B5362800967483 /* OCConnection+OData.m */, + DC36EC7A27B5362800967483 /* OCConnection+OData.h */, + DC36EC8027B560D600967483 /* OCQueryCondition+ODataBuilder.m */, + DC36EC7F27B560D600967483 /* OCQueryCondition+ODataBuilder.h */, + ); + path = OData; + sourceTree = ""; + }; DC381FC422C80B7C00284699 /* NameConflicts */ = { isa = PBXGroup; children = ( @@ -1995,6 +2596,71 @@ path = Message; sourceTree = ""; }; + DC41C79C25EA60BA0074F23B /* Sources */ = { + isa = PBXGroup; + children = ( + DC41C78F25EA5F7A0074F23B /* OCResourceSource.m */, + DC41C78E25EA5F7A0074F23B /* OCResourceSource.h */, + DC9C19E127839E440021222E /* OCResourceSourceStorage.m */, + DC9C19E027839E440021222E /* OCResourceSourceStorage.h */, + DC9C19ED278CD0B30021222E /* OCDatabase+ResourceStorage.m */, + DC9C19EC278CD0B30021222E /* OCDatabase+ResourceStorage.h */, + ); + path = Sources; + sourceTree = ""; + }; + DC41C79D25EA60C30074F23B /* Resource */ = { + isa = PBXGroup; + children = ( + DCDBB5F625248B0300FAD707 /* OCResource.m */, + DCDBB5F525248B0300FAD707 /* OCResource.h */, + DCE62EAC2771ED5700E3193F /* OCResourceImage.m */, + DCE62EAB2771ED5700E3193F /* OCResourceImage.h */, + DCF072E32798630900E0B01D /* OCResourceTextPlaceholder.m */, + DCF072E22798630900E0B01D /* OCResourceTextPlaceholder.h */, + DCEAF04D2806201300980B6D /* OCResourceText.m */, + DCEAF04C2806201300980B6D /* OCResourceText.h */, + ); + path = Resource; + sourceTree = ""; + }; + DC41C79E25EA60D20074F23B /* Request */ = { + isa = PBXGroup; + children = ( + DCDBB5FA25248B0F00FAD707 /* OCResourceRequest.m */, + DCDBB5F925248B0F00FAD707 /* OCResourceRequest.h */, + DC9C19F1278EE7230021222E /* OCResourceRequestImage.m */, + DC9C19F0278EE7230021222E /* OCResourceRequestImage.h */, + ); + path = Request; + sourceTree = ""; + }; + DC41C7B725EA62240074F23B /* Thumbnails */ = { + isa = PBXGroup; + children = ( + DC41C7B925EA62520074F23B /* OCResourceSourceItemThumbnails.m */, + DC41C7B825EA62520074F23B /* OCResourceSourceItemThumbnails.h */, + DC41C7FC25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.m */, + DC41C7FB25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.h */, + DC41C7CC25EA627A0074F23B /* OCResourceRequestItemThumbnail.m */, + DC41C7CB25EA627A0074F23B /* OCResourceRequestItemThumbnail.h */, + ); + path = Thumbnails; + sourceTree = ""; + }; + DC41C7D925EA62B20074F23B /* Avatars */ = { + isa = PBXGroup; + children = ( + DCF072E72798652500E0B01D /* OCResourceSourceAvatarPlaceholders.m */, + DCF072E62798652500E0B01D /* OCResourceSourceAvatarPlaceholders.h */, + DC41C7DB25EA62CD0074F23B /* OCResourceSourceAvatars.m */, + DC41C7DA25EA62CD0074F23B /* OCResourceSourceAvatars.h */, + DC41C7EE25EA62E40074F23B /* OCResourceRequestAvatar.m */, + DC41C7ED25EA62E40074F23B /* OCResourceRequestAvatar.h */, + ); + path = Avatars; + sourceTree = ""; + }; DC446C81206B8D9400189B9A /* External */ = { isa = PBXGroup; children = ( @@ -2013,6 +2679,122 @@ path = ISRunLoopThread; sourceTree = ""; }; + DC46F3C12843C65300038880 /* Actions */ = { + isa = PBXGroup; + children = ( + DC46F3C32843C66B00038880 /* OCAction.m */, + DC46F3C22843C66B00038880 /* OCAction.h */, + ); + path = Actions; + sourceTree = ""; + }; + DC47E49027A5820C0020E8EF /* GeneratedTypes */ = { + isa = PBXGroup; + children = ( + DC9219D12964CB4500F538EE /* GAApplication.h */, + DC9219D42964CB4500F538EE /* GAApplication.m */, + DC9219D02964CB4500F538EE /* GAAppRole.h */, + DC9219D62964CB4500F538EE /* GAAppRole.m */, + DC9219CE2964CB4400F538EE /* GAAppRoleAssignment.h */, + DC9219DC2964CB4500F538EE /* GAAppRoleAssignment.m */, + DC47E4B627A5820C0020E8EF /* GADeleted.h */, + DC47E4A727A5820C0020E8EF /* GADeleted.m */, + DC47E4A427A5820C0020E8EF /* GADirectoryObject.h */, + DC47E4B927A5820C0020E8EF /* GADirectoryObject.m */, + DC47E4BC27A5820C0020E8EF /* GADrive.h */, + DC47E4A327A5820C0020E8EF /* GADrive.m */, + DC47E4B527A5820C0020E8EF /* GADriveItem.h */, + DC47E49927A5820C0020E8EF /* GADriveItem.m */, + DC9219DA2964CB4500F538EE /* GAEducationClass.h */, + DC9219D22964CB4500F538EE /* GAEducationClass.m */, + DC9219D92964CB4500F538EE /* GAEducationOrganization.h */, + DC9219DB2964CB4500F538EE /* GAEducationOrganization.m */, + DC9219D82964CB4500F538EE /* GAEducationSchool.h */, + DC9219D52964CB4500F538EE /* GAEducationSchool.m */, + DC9219CD2964CB4400F538EE /* GAEducationUser.h */, + DC9219D32964CB4500F538EE /* GAEducationUser.m */, + DC9219CF2964CB4500F538EE /* GAEntity.h */, + DC9219D72964CB4500F538EE /* GAEntity.m */, + DC47E4B027A5820C0020E8EF /* GAFileSystemInfo.h */, + DC47E49E27A5820C0020E8EF /* GAFileSystemInfo.m */, + DC47E4B727A5820C0020E8EF /* GAFolder.h */, + DC47E4A527A5820C0020E8EF /* GAFolder.m */, + DC47E4BA27A5820C0020E8EF /* GAFolderView.h */, + DC47E4A127A5820C0020E8EF /* GAFolderView.m */, + DC47E49427A5820C0020E8EF /* GAGroup.h */, + DC47E4A827A5820C0020E8EF /* GAGroup.m */, + DC47E49D27A5820C0020E8EF /* GAHashes.h */, + DC47E4B127A5820C0020E8EF /* GAHashes.m */, + DC47E4B427A5820C0020E8EF /* GAIdentity.h */, + DC47E49B27A5820C0020E8EF /* GAIdentity.m */, + DC47E4B227A5820C0020E8EF /* GAIdentitySet.h */, + DC47E49C27A5820C0020E8EF /* GAIdentitySet.m */, + DC47E49F27A5820C0020E8EF /* GAImage.h */, + DC47E4BE27A5820C0020E8EF /* GAImage.m */, + DC47E4AD27A5820C0020E8EF /* GAItemReference.h */, + DC47E49727A5820C0020E8EF /* GAItemReference.m */, + DC9219F02964CB6000F538EE /* GAObjectIdentity.h */, + DC9219F22964CB6000F538EE /* GAObjectIdentity.m */, + DC47E4AF27A5820C0020E8EF /* GAODataError.h */, + DC47E49527A5820C0020E8EF /* GAODataError.m */, + DC47E4A027A5820C0020E8EF /* GAODataErrorDetail.h */, + DC47E4BD27A5820C0020E8EF /* GAODataErrorDetail.m */, + DC47E49627A5820C0020E8EF /* GAODataErrorMain.h */, + DC47E4AE27A5820C0020E8EF /* GAODataErrorMain.m */, + DC47E49827A5820C0020E8EF /* GAOpenGraphFile.h */, + DC47E4AC27A5820C0020E8EF /* GAOpenGraphFile.m */, + DC47E4A627A5820C0020E8EF /* GAPasswordProfile.h */, + DC47E4B827A5820C0020E8EF /* GAPasswordProfile.m */, + DC9A116627CFCC1200D90BA4 /* GAPermission.h */, + DC9A116727CFCC1200D90BA4 /* GAPermission.m */, + DC47E4A927A5820C0020E8EF /* GAQuota.h */, + DC47E49327A5820C0020E8EF /* GAQuota.m */, + DCFC9EDA28003F0D005D9144 /* GARemoteItem.h */, + DCFC9EDB28003F0D005D9144 /* GARemoteItem.m */, + DC47E4BB27A5820C0020E8EF /* GARoot.h */, + DC47E4A227A5820C0020E8EF /* GARoot.m */, + DCFC9ED728003EFB005D9144 /* GAShared.h */, + DCFC9ED628003EFB005D9144 /* GAShared.m */, + DC47E4AB27A5820C0020E8EF /* GASpecialFolder.h */, + DC47E49127A5820C0020E8EF /* GASpecialFolder.m */, + DC9219F12964CB6000F538EE /* GATagAssignment.h */, + DC9219ED2964CB6000F538EE /* GATagAssignment.m */, + DC9219EF2964CB6000F538EE /* GATagUnassignment.h */, + DC9219EE2964CB6000F538EE /* GATagUnassignment.m */, + DC47E4B327A5820C0020E8EF /* GATrash.h */, + DC47E49A27A5820C0020E8EF /* GATrash.m */, + DC47E4AA27A5820C0020E8EF /* GAUser.h */, + DC47E49227A5820C0020E8EF /* GAUser.m */, + ); + path = GeneratedTypes; + sourceTree = ""; + }; + DC47E4ED27A7E1E70020E8EF /* Drive */ = { + isa = PBXGroup; + children = ( + DC47E4EF27A7E2050020E8EF /* OCDrive.m */, + DC47E4EE27A7E2050020E8EF /* OCDrive.h */, + DC47E4F427A83D9B0020E8EF /* OCQuota.m */, + DC47E4F327A83D9B0020E8EF /* OCQuota.h */, + ); + path = Drive; + sourceTree = ""; + }; + DC47E4F227A7E3BA0020E8EF /* Identity */ = { + isa = PBXGroup; + children = ( + DCDD9B17222989E50052A001 /* OCIdentity.m */, + DCDD9B16222989E50052A001 /* OCIdentity.h */, + DCB330C529EF2F0F00BFF393 /* OCIdentity+DataItem.m */, + DCB330C429EF2F0F00BFF393 /* OCIdentity+DataItem.h */, + DC545E79203F7DD0006111FA /* OCUser.m */, + DC545E78203F7DD0006111FA /* OCUser.h */, + DCDD9B1B22298D050052A001 /* OCGroup.m */, + DCDD9B1A22298D050052A001 /* OCGroup.h */, + ); + path = Identity; + sourceTree = ""; + }; DC4AFABA206AE94A00189B9A /* Internals */ = { isa = PBXGroup; children = ( @@ -2139,6 +2921,15 @@ path = Internal; sourceTree = ""; }; + DC6C0A4E2923A50A0045FF2A /* Mapped */ = { + isa = PBXGroup; + children = ( + DC6C0A502923A59A0045FF2A /* OCDataSourceMapped.m */, + DC6C0A4F2923A59A0045FF2A /* OCDataSourceMapped.h */, + ); + path = Mapped; + sourceTree = ""; + }; DC6CC30426428DA40040ECAC /* Custom Scheme */ = { isa = PBXGroup; children = ( @@ -2220,6 +3011,8 @@ DC9D22E825A8754200CF5675 /* OCHTTPRequest+JSON.h */, DCE17BC026B5A7E400B7C7DD /* OCHTTPRequest+Stream.m */, DCE17BBF26B5A7E400B7C7DD /* OCHTTPRequest+Stream.h */, + DCFE682328D865BD00091D2A /* NSDictionary+OCFormEncoding.m */, + DCFE682228D865BD00091D2A /* NSDictionary+OCFormEncoding.h */, ); path = Request; sourceTree = ""; @@ -2341,12 +3134,23 @@ DC72E4302063DD7600189B9A /* OCClassSettingsFlatSourcePropertyList.h */, DC1889782189AF3B00CFB3F9 /* OCClassSettingsFlatSourceEnvironment.m */, DC1889772189AF3B00CFB3F9 /* OCClassSettingsFlatSourceEnvironment.h */, + DCFA564D2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m */, + DCFA564C2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h */, DCD8439925E1BEE5008D9BBA /* NSDictionary+OCExpand.m */, DCD8439825E1BEE5008D9BBA /* NSDictionary+OCExpand.h */, ); path = Sources; sourceTree = ""; }; + DC74E44127AD86C3005B4B03 /* Location */ = { + isa = PBXGroup; + children = ( + DC74E44327AD86DA005B4B03 /* OCLocation.m */, + DC74E44227AD86DA005B4B03 /* OCLocation.h */, + ); + path = Location; + sourceTree = ""; + }; DC772E8E26FC908D002C0015 /* AWBrowser */ = { isa = PBXGroup; children = ( @@ -2395,6 +3199,23 @@ path = Example/Ocean; sourceTree = ""; }; + DC826661281A9E5B00F91F7D /* VFS */ = { + isa = PBXGroup; + children = ( + DC826662281A9E7100F91F7D /* CONCEPT.md */, + DC82667E281FE66600F91F7D /* OCVFSTypes.h */, + DC826664281AC59D00F91F7D /* OCVFSCore.m */, + DC826663281AC59D00F91F7D /* OCVFSCore.h */, + DC826668281AC5B000F91F7D /* OCVFSNode.m */, + DC826667281AC5B000F91F7D /* OCVFSNode.h */, + DC2218C128228F7000808BCE /* OCVFSContent.m */, + DC2218C028228F7000808BCE /* OCVFSContent.h */, + DC49B55F2837DCE700DAF13B /* OCItem+OCVFSItem.m */, + DC49B55E2837DCE700DAF13B /* OCItem+OCVFSItem.h */, + ); + path = VFS; + sourceTree = ""; + }; DC855701204FE9AB00189B9A /* App Identity */ = { isa = PBXGroup; children = ( @@ -2416,12 +3237,14 @@ DC8245B921FB334200775AB9 /* OCCore+Internal.h */, DCE227D522D612C4000BE0A5 /* AvailableOffline */, DCC599F122EEE64200499B29 /* Claims */, + DCED67D427F1A79100686E4F /* Data Sources */, DCE227CC22D60CF4000BE0A5 /* ItemPolicies */, DC2F63602239453E0063C2DA /* Sharing */, DCB0A46121B9227400FAC4E9 /* Connection Status */, DCADC04A2072D0FB00DB8E83 /* ItemList */, DC381FC422C80B7C00284699 /* NameConflicts */, DC381FD222C9E75400284699 /* DirectURL */, + DCDBB5EC2524897B00FAD707 /* Resources */, DC2D646221C3D60900EB26FD /* Thumbnails */, DC19BFE221CB9FAC007C20D1 /* FileProvider */, DC2FED5D228D5577004FDEC6 /* Favorites */, @@ -2464,6 +3287,8 @@ children = ( DCC8F9E12028554E00EB6701 /* OCBookmark.m */, DCC8F9E02028554E00EB6701 /* OCBookmark.h */, + DC6C0A4B2923A0050045FF2A /* OCBookmark+DataItem.m */, + DC6C0A4A2923A0050045FF2A /* OCBookmark+DataItem.h */, DC3E6E7E2609473200D7D847 /* OCBookmark+DBMigration.m */, DC3E6E7D2609473200D7D847 /* OCBookmark+DBMigration.h */, DCD49A0924E68BD5008D9544 /* OCBookmark+Diagnostics.m */, @@ -2498,6 +3323,8 @@ children = ( DCC8F9E52028556500EB6701 /* OCConnection.m */, DCC8F9E42028556500EB6701 /* OCConnection.h */, + DCFE3B8527A16AC200939415 /* GraphAPI */, + DC36EC7E27B560B400967483 /* OData */, DCCE49362684BBF5005961D8 /* DAVResponse */, DCD63279223BB1930090169E /* Capabilities */, DC85570A204FEA5E00189B9A /* Categories */, @@ -2509,7 +3336,9 @@ DC85570A204FEA5E00189B9A /* Categories */ = { isa = PBXGroup; children = ( + DC0CE17E28C63232009ABDFB /* OCConnection+AppProviders.m */, DCDBEE322048A8BC00189B9A /* OCConnection+Authentication.m */, + DCDBB5E52523DDA200FAD707 /* OCConnection+Avatars.m */, DC89135F20910F0E00028999 /* OCConnection+Compatibility.m */, DCE784F722325D4F00733F01 /* OCConnection+Recipients.m */, DCDBEE2A2048A6A700189B9A /* OCConnection+Setup.m */, @@ -2518,6 +3347,8 @@ DCDBEE2E2048A71200189B9A /* OCConnection+Tools.m */, DC5B96D424916CF200733594 /* OCConnection+Upload.m */, DC30947220542FA500189B9A /* OCConnection+Users.m */, + DCFE681F28D857B500091D2A /* NSError+OCISError.m */, + DCFE681E28D857B500091D2A /* NSError+OCISError.h */, ); name = Categories; sourceTree = ""; @@ -2541,6 +3372,10 @@ children = ( DCC8FA022029BA7A00EB6701 /* OCVault.m */, DCC8FA012029BA7A00EB6701 /* OCVault.h */, + DC82665E2818972200F91F7D /* OCVaultLocation.m */, + DC82665D2818972200F91F7D /* OCVaultLocation.h */, + DCB4F6E628324A3A005AD181 /* OCVaultDriveList.m */, + DCB4F6E528324A3A005AD181 /* OCVaultDriveList.h */, DC22669922817DC600FB29EE /* OCVault+Internal.m */, DC22669822817DC600FB29EE /* OCVault+Internal.h */, DCCE49312684B0EF005961D8 /* Prepopulation */, @@ -2593,6 +3428,10 @@ DCFF1AAE21655C8800ABE40A /* OCItem+OCFileURLMetadata.h */, DC85571B2050196000189B9A /* OCItem+OCXMLObjectCreation.m */, DC85571A2050196000189B9A /* OCItem+OCXMLObjectCreation.h */, + DC3AB1902808B3C400789435 /* OCItem+OCDataItem.m */, + DC3AB18F2808B3C400789435 /* OCItem+OCDataItem.h */, + DC2A127528D05DD30088A2B7 /* OCItem+OCTypeAlias.m */, + DC2A127428D05DD20088A2B7 /* OCItem+OCTypeAlias.h */, DC4E0A5720927048007EB05F /* OCItemVersionIdentifier.m */, DC4E0A5620927048007EB05F /* OCItemVersionIdentifier.h */, DC0283652090A8EE005B6334 /* Images */, @@ -2607,12 +3446,9 @@ DCC8F9F4202855A200EB6701 /* OCShare.h */, DCDD9B13222986D50052A001 /* OCShare+OCXMLObjectCreation.m */, DCDD9B12222986D50052A001 /* OCShare+OCXMLObjectCreation.h */, - DC545E79203F7DD0006111FA /* OCUser.m */, - DC545E78203F7DD0006111FA /* OCUser.h */, - DCDD9B1B22298D050052A001 /* OCGroup.m */, - DCDD9B1A22298D050052A001 /* OCGroup.h */, - DCDD9B17222989E50052A001 /* OCRecipient.m */, - DCDD9B16222989E50052A001 /* OCRecipient.h */, + DC6FDAF129507579004F0C7F /* OCShare+OCDataItem.m */, + DC6FDAF029507579004F0C7F /* OCShare+OCDataItem.h */, + DC0BE5BA28F8145500CE2101 /* Roles */, ); path = Share; sourceTree = ""; @@ -2829,6 +3665,15 @@ path = "Connection Status"; sourceTree = ""; }; + DCB4F6E928324B70005AD181 /* KVO Backed */ = { + isa = PBXGroup; + children = ( + DCB4F6EB28324B90005AD181 /* OCDataSourceKVO.m */, + DCB4F6EA28324B90005AD181 /* OCDataSourceKVO.h */, + ); + path = "KVO Backed"; + sourceTree = ""; + }; DCB6D05522A13E5800CA47C5 /* Tools */ = { isa = PBXGroup; children = ( @@ -2838,6 +3683,56 @@ path = Tools; sourceTree = ""; }; + DCC4F3E327D74DA500ABF4C9 /* Data Sources */ = { + isa = PBXGroup; + children = ( + DCC4F3F027D74E7000ABF4C9 /* CONCEPT.md */, + DCED67D127F1A48000686E4F /* OCDataTypes.m */, + DCC4F3F127D756C300ABF4C9 /* OCDataTypes.h */, + DCC4F40127D75DFF00ABF4C9 /* Sources */, + DCC4F40227D75E0B00ABF4C9 /* Converters */, + DCED67D927F1B0FE00686E4F /* Presentable */, + DCC4F40327D75E2100ABF4C9 /* Renderer */, + ); + path = "Data Sources"; + sourceTree = ""; + }; + DCC4F40127D75DFF00ABF4C9 /* Sources */ = { + isa = PBXGroup; + children = ( + DCC4F3E927D74DE300ABF4C9 /* OCDataSource.m */, + DCC4F3E827D74DE300ABF4C9 /* OCDataSource.h */, + DCD3F6A227EA887B00D86662 /* Array Backed */, + DCB4F6E928324B70005AD181 /* KVO Backed */, + DCFC9EDE28004767005D9144 /* Composition */, + DC6C0A4E2923A50A0045FF2A /* Mapped */, + DC04FFBF27F5988F00F22569 /* Subscriptions */, + DC04FFC027F5989B00F22569 /* Snapshots */, + DC04FFC127F598A800F22569 /* Item Records */, + ); + path = Sources; + sourceTree = ""; + }; + DCC4F40227D75E0B00ABF4C9 /* Converters */ = { + isa = PBXGroup; + children = ( + DCC4F3F627D757FE00ABF4C9 /* OCDataConverter.m */, + DCC4F3F527D757FE00ABF4C9 /* OCDataConverter.h */, + DCC4F3FE27D75BF700ABF4C9 /* OCDataConverterPipeline.m */, + DCC4F3FD27D75BF700ABF4C9 /* OCDataConverterPipeline.h */, + ); + path = Converters; + sourceTree = ""; + }; + DCC4F40327D75E2100ABF4C9 /* Renderer */ = { + isa = PBXGroup; + children = ( + DCC4F3FA27D75AA600ABF4C9 /* OCDataRenderer.m */, + DCC4F3F927D75AA600ABF4C9 /* OCDataRenderer.h */, + ); + path = Renderer; + sourceTree = ""; + }; DCC599F122EEE64200499B29 /* Claims */ = { isa = PBXGroup; children = ( @@ -2919,8 +3814,10 @@ DCC8FA112029D5EC00EB6701 /* OCTypes.h */, DC3F2B50204AED8300189B9A /* OCMacros.h */, DC8EB2FA23950B54009148F9 /* OCFeatureAvailability.h */, + DCF575D427956794003BEBBA /* Platforms */, DC855701204FE9AB00189B9A /* App Identity */, DC2EF1BD20ED776300BA2A3E /* Security */, + DCF575D327956780003BEBBA /* Protocols */, DCC6567120CA693700110A97 /* Resource Management */, DC72E4262063D54F00189B9A /* Settings */, DC855706204FEA0A00189B9A /* Bookmark */, @@ -2929,7 +3826,10 @@ DC51FD86247562A50069AB79 /* Cellular */, DC4B12002209C24F0062BCDD /* HTTP */, DC72E41C2061666900189B9A /* Host Simulator */, + DCFE3B7327A1660C00939415 /* GraphAPI */, + DCE741D129B74D6000BFF393 /* Setup */, DC855708204FEA3400189B9A /* Connection */, + DC0CE17828C5DDC6009ABDFB /* App Providers */, DCF39B572458290200DEA137 /* TUS */, DC4B11FB220996330062BCDD /* Progress */, DC19BFC621CA67B1007C20D1 /* Issues */, @@ -2939,11 +3839,19 @@ DC85570E204FEAE900189B9A /* Vaults */, DC855704204FE9E900189B9A /* Activity */, DC855702204FE9CA00189B9A /* Core */, + DC826661281A9E5B00F91F7D /* VFS */, DC3CE0382429FA6800AB8B88 /* Message Queue */, DC19BFF321CBE293007C20D1 /* Wait Condition */, DC855710204FEB1500189B9A /* Query */, + DCC4F3E327D74DA500ABF4C9 /* Data Sources */, + DC46F3C12843C65300038880 /* Actions */, + DC47E4ED27A7E1E70020E8EF /* Drive */, + DC74E44127AD86C3005B4B03 /* Location */, DC855711204FEB5200189B9A /* Item */, + DC0BE5BB28F9426100CE2101 /* Statistics */, + DC47E4F227A7E3BA0020E8EF /* Identity */, DC855712204FEB6B00189B9A /* Share */, + DC0BE5B428F80D8F00CE2101 /* Image Types */, DC855713204FEB7F00189B9A /* Errors */, DC07C28B21244F9F00B815A4 /* Extensions */, DC855715204FEB9800189B9A /* Categories */, @@ -2979,6 +3887,7 @@ DCA7A2C024EC81B000237085 /* CoreRedirectTests.m */, DC594B1C21EFD25000B882C4 /* CoreManagerTests.m */, DC03650520AAEFBD00F62732 /* DatabaseTests.m */, + DCF00C1827E698A4001F2AFC /* DataSourceTests.m */, DCB0A45B21B813A000FAC4E9 /* ExtensionTests.m */, DC72E4202063BE9400189B9A /* HostSimulatorTests.m */, DC61E930221423D2002889D6 /* HTTPPipelineTests.m */, @@ -3046,6 +3955,17 @@ path = DAVResponse; sourceTree = ""; }; + DCD09E392A1B56E100BFF393 /* URL-based */ = { + isa = PBXGroup; + children = ( + DCD09E3B2A1B573100BFF393 /* OCResourceSourceURL.m */, + DCD09E3A2A1B573100BFF393 /* OCResourceSourceURL.h */, + DCEAF0432805B7C900980B6D /* DriveItems */, + DC0CE19328C8904E009ABDFB /* URLItems */, + ); + path = "URL-based"; + sourceTree = ""; + }; DCD2D40022F058FC0071FB8F /* User Preferences */ = { isa = PBXGroup; children = ( @@ -3069,6 +3989,15 @@ path = SQLite; sourceTree = ""; }; + DCD3F6A227EA887B00D86662 /* Array Backed */ = { + isa = PBXGroup; + children = ( + DCD3F69F27EA887600D86662 /* OCDataSourceArray.m */, + DCD3F69E27EA887600D86662 /* OCDataSourceArray.h */, + ); + path = "Array Backed"; + sourceTree = ""; + }; DCD63279223BB1930090169E /* Capabilities */ = { isa = PBXGroup; children = ( @@ -3109,6 +4038,22 @@ path = LookupTable; sourceTree = ""; }; + DCDBB5EC2524897B00FAD707 /* Resources */ = { + isa = PBXGroup; + children = ( + DC41C80925EA653D0074F23B /* README.md */, + DC41C7A425EA61D70074F23B /* OCResourceTypes.h */, + DCE62EA62771E9E000E3193F /* Manager */, + DC41C79C25EA60BA0074F23B /* Sources */, + DC41C79E25EA60D20074F23B /* Request */, + DC41C79D25EA60C30074F23B /* Resource */, + DC41C7D925EA62B20074F23B /* Avatars */, + DCD09E392A1B56E100BFF393 /* URL-based */, + DC41C7B725EA62240074F23B /* Thumbnails */, + ); + path = Resources; + sourceTree = ""; + }; DCE227CC22D60CF4000BE0A5 /* ItemPolicies */ = { isa = PBXGroup; children = ( @@ -3116,6 +4061,8 @@ DCE227D622D612EC000BE0A5 /* OCCore+ItemPolicies.h */, DCE227D222D60D49000BE0A5 /* OCItemPolicy.m */, DCE227D122D60D49000BE0A5 /* OCItemPolicy.h */, + DC28F822294B6DE600AC4013 /* OCItemPolicy+OCDataItem.m */, + DC28F821294B6DE600AC4013 /* OCItemPolicy+OCDataItem.h */, DC2AA56C22DD1308001D5C39 /* Processors */, ); path = ItemPolicies; @@ -3129,6 +4076,28 @@ path = AvailableOffline; sourceTree = ""; }; + DCE62EA62771E9E000E3193F /* Manager */ = { + isa = PBXGroup; + children = ( + DCE62EA82771EA0200E3193F /* OCResourceManager.m */, + DCE62EA72771EA0100E3193F /* OCResourceManager.h */, + DC9C19E5278488360021222E /* OCResourceManagerJob.m */, + DC9C19E4278488360021222E /* OCResourceManagerJob.h */, + ); + path = Manager; + sourceTree = ""; + }; + DCE741D129B74D6000BFF393 /* Setup */ = { + isa = PBXGroup; + children = ( + DCE741D729B7DD5000BFF393 /* OCServerInstance.m */, + DCE741D629B7DD5000BFF393 /* OCServerInstance.h */, + DCE741DB29B7EA7000BFF393 /* OCBookmark+ServerInstance.m */, + DCE741DA29B7EA7000BFF393 /* OCBookmark+ServerInstance.h */, + ); + path = Setup; + sourceTree = ""; + }; DCEA7D942093553300F25223 /* Toolkit */ = { isa = PBXGroup; children = ( @@ -3165,6 +4134,35 @@ path = "Lock Manager"; sourceTree = ""; }; + DCEAF0432805B7C900980B6D /* DriveItems */ = { + isa = PBXGroup; + children = ( + DCEAF0452805B80D00980B6D /* OCResourceSourceDriveItems.m */, + DCEAF0442805B80D00980B6D /* OCResourceSourceDriveItems.h */, + DCEAF0492805B83B00980B6D /* OCResourceRequestDriveItem.m */, + DCEAF0482805B83B00980B6D /* OCResourceRequestDriveItem.h */, + ); + path = DriveItems; + sourceTree = ""; + }; + DCED67D427F1A79100686E4F /* Data Sources */ = { + isa = PBXGroup; + children = ( + DCED67D627F1A7B200686E4F /* OCCore+DataSources.m */, + DCED67D527F1A7B200686E4F /* OCCore+DataSources.h */, + ); + path = "Data Sources"; + sourceTree = ""; + }; + DCED67D927F1B0FE00686E4F /* Presentable */ = { + isa = PBXGroup; + children = ( + DCED67DB27F1B13600686E4F /* OCDataItemPresentable.m */, + DCED67DA27F1B13600686E4F /* OCDataItemPresentable.h */, + ); + path = Presentable; + sourceTree = ""; + }; DCF163EF274B916900E0182A /* Collations */ = { isa = PBXGroup; children = ( @@ -3200,6 +4198,65 @@ path = TUS; sourceTree = ""; }; + DCF575D327956780003BEBBA /* Protocols */ = { + isa = PBXGroup; + children = ( + DCF575CF279562DF003BEBBA /* OCViewProvider.h */, + DCF575DE27956D84003BEBBA /* OCViewProviderContext.m */, + DCF575DD27956D84003BEBBA /* OCViewProviderContext.h */, + ); + path = Protocols; + sourceTree = ""; + }; + DCF575D427956794003BEBBA /* Platforms */ = { + isa = PBXGroup; + children = ( + DCF575D6279567AB003BEBBA /* OCPlatform.m */, + DCF575D5279567AB003BEBBA /* OCPlatform.h */, + ); + path = Platforms; + sourceTree = ""; + }; + DCFC9EDE28004767005D9144 /* Composition */ = { + isa = PBXGroup; + children = ( + DCFC9EE028004791005D9144 /* OCDataSourceComposition.m */, + DCFC9EDF28004791005D9144 /* OCDataSourceComposition.h */, + ); + path = Composition; + sourceTree = ""; + }; + DCFE3B7327A1660C00939415 /* GraphAPI */ = { + isa = PBXGroup; + children = ( + DCFE3B8027A167C700939415 /* GAGraph.h */, + DC47E49027A5820C0020E8EF /* GeneratedTypes */, + DCFE3B8427A16A7B00939415 /* Parser Support */, + ); + path = GraphAPI; + sourceTree = ""; + }; + DCFE3B8427A16A7B00939415 /* Parser Support */ = { + isa = PBXGroup; + children = ( + DCFE3B7827A1666B00939415 /* GAGraphObject.h */, + DCFE3B7D27A1669300939415 /* GAGraphContext.m */, + DCFE3B7C27A1669300939415 /* GAGraphContext.h */, + DCFE3B9C27A1A6E500939415 /* GAGraphData+Decoder.m */, + DCFE3B9B27A1A6E500939415 /* GAGraphData+Decoder.h */, + ); + path = "Parser Support"; + sourceTree = ""; + }; + DCFE3B8527A16AC200939415 /* GraphAPI */ = { + isa = PBXGroup; + children = ( + DCFE3B8727A16AE800939415 /* OCConnection+GraphAPI.m */, + DCFE3B8627A16AE800939415 /* OCConnection+GraphAPI.h */, + ); + path = GraphAPI; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3238,26 +4295,40 @@ files = ( DC7014252209CE7A009D4FD9 /* OCHTTPPipelineManager.h in Headers */, DCB6D05822A13E7500CA47C5 /* NSString+OCSQLTools.h in Headers */, + DC9C19E6278488360021222E /* OCResourceManagerJob.h in Headers */, DC2AA57022DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.h in Headers */, DC72568020405752006111FA /* OCClassSettings.h in Headers */, DCC3700F24D4B3B7008B0DEB /* OCDatabase+Diagnostic.h in Headers */, + DCE741D829B7DD5000BFF393 /* OCServerInstance.h in Headers */, + DCDBB5EA2523E3AF00FAD707 /* OCAvatar.h in Headers */, DC39DC59204215A800189B9A /* NSProgress+OCEvent.h in Headers */, DC75D30F214C015F00B6FB62 /* OCItem+OCItemCreationDebugging.h in Headers */, DCDA307121412A0100DB61A9 /* OCSyncAction.h in Headers */, + DC47E4E927A5820D0020E8EF /* GARoot.h in Headers */, DCA91F2F21A0BDE400AEDFB4 /* OCSyncAction+FileProvider.h in Headers */, DCD8439A25E1BEE5008D9BBA /* NSDictionary+OCExpand.h in Headers */, DC0376DE271A33B900151E8C /* OCLocale.h in Headers */, DCFF1AB021655C8800ABE40A /* OCItem+OCFileURLMetadata.h in Headers */, DC22669A22817DC600FB29EE /* OCVault+Internal.h in Headers */, + DC9219F52964CB6000F538EE /* GATagUnassignment.h in Headers */, DCC8F9BC202852A200EB6701 /* ownCloudSDK.h in Headers */, + DC9219E82964CB4500F538EE /* GAEducationSchool.h in Headers */, DC6DEEB524C59BE300E3772E /* OCHTTPPolicyManager.h in Headers */, DC8556FB204F4F3000189B9A /* OCXMLParser.h in Headers */, DC27A1A020CBE8BD008ACB6C /* OCCore+FileProvider.h in Headers */, DC72E4322063DD7600189B9A /* OCClassSettingsFlatSourcePropertyList.h in Headers */, + DCC4F3F327D756C300ABF4C9 /* OCDataTypes.h in Headers */, + DCD09E3C2A1B573100BFF393 /* OCResourceSourceURL.h in Headers */, DCEEB2F12047094500189B9A /* NSData+OCHash.h in Headers */, + DC9B4FD52941FF630037F8F8 /* OCCertificateStoreRecord.h in Headers */, + DC826665281AC59D00F91F7D /* OCVFSCore.h in Headers */, + DC9219DE2964CB4500F538EE /* GAAppRoleAssignment.h in Headers */, + DC47E4F527A83D9B0020E8EF /* OCQuota.h in Headers */, DC2F6375223A61990063C2DA /* OCCoreQuery.h in Headers */, + DC0CE19A28C89227009ABDFB /* OCResourceSourceURLItems.h in Headers */, DC3E6E7F2609473200D7D847 /* OCBookmark+DBMigration.h in Headers */, DC1889802189EC2600CFB3F9 /* OCLogWriter.h in Headers */, + DC47E4C627A5820D0020E8EF /* GAOpenGraphFile.h in Headers */, DC6ABF67253462E100689C7B /* OCHostSimulator.h in Headers */, DC00DB1D219B120300C82737 /* OCHTTPDAVMultistatusResponse.h in Headers */, DCC6567820CA696A00110A97 /* OCBookmarkManager.h in Headers */, @@ -3266,27 +4337,38 @@ DC8FE6FF221CAF280016BDEE /* OCProgressManager.h in Headers */, DC8EB2FB23950B54009148F9 /* OCFeatureAvailability.h in Headers */, DCF1C6722631BFD5004D8B0F /* OCMeasurement.h in Headers */, + DC47E4DE27A5820D0020E8EF /* GAFileSystemInfo.h in Headers */, + DCFE3B7E27A1669300939415 /* GAGraphContext.h in Headers */, DC7650082403DF5000201812 /* NSError+OCNetworkFailure.h in Headers */, + DC47E4E227A5820D0020E8EF /* GAIdentity.h in Headers */, DC2C3F32230E8C5100D2282A /* OCKeyValueStore.h in Headers */, DC680585212EC27B006C3B1F /* OCExtension+License.h in Headers */, + DC41C7DC25EA62CD0074F23B /* OCResourceSourceAvatars.h in Headers */, DC07C296212450DC00B815A4 /* OCExtensionLocation.h in Headers */, DC446C85206B8DB500189B9A /* OCRunLoopThread.h in Headers */, + DC9219DF2964CB4500F538EE /* GAEntity.h in Headers */, DC72E4292063D61F00189B9A /* OCClassSettingsFlatSource.h in Headers */, DCC8F9FF20285C1500EB6701 /* OCAuthenticationMethodOAuth2.h in Headers */, DC545E7A203F7DD0006111FA /* OCUser.h in Headers */, DC8556ED204DEA2900189B9A /* OCHTTPDAVRequest.h in Headers */, DC6DEEB124C5990D00E3772E /* OCHTTPPolicy+PipelinePolicyHandler.h in Headers */, DC302AEE221EAC55003218C6 /* OCProxyProgress.h in Headers */, + DC28F823294B6DE600AC4013 /* OCItemPolicy+OCDataItem.h in Headers */, + DC49B55328339BE200DAF13B /* NSArray+OCMapping.h in Headers */, DC0AE4F22311C75300428681 /* OCKeyValueStack.h in Headers */, DC6B0472268D1950003FDEC1 /* OCBookmark+Prepopulation.h in Headers */, DC1D4D3720DBD58E005A3DFC /* OCFile.h in Headers */, + DC0BE5BE28F9427900CE2101 /* OCStatistic.h in Headers */, + DC41C7CD25EA627A0074F23B /* OCResourceRequestItemThumbnail.h in Headers */, DC34227A217CAA0B00705508 /* OCIPNotificationCenter.h in Headers */, + DC9219E02964CB4500F538EE /* GAAppRole.h in Headers */, DC6ABF6B2534633E00689C7B /* OCHostSimulatorManager.h in Headers */, DCF39B552458268E00DEA137 /* OCTUSHeader.h in Headers */, DCD7AA442580E5A5000CD155 /* NSURLSessionTask+Debug.h in Headers */, DC8EB2FE23951AAB009148F9 /* OCAuthenticationBrowserSession.h in Headers */, DCEEB2F5204802CF00189B9A /* OCIssue.h in Headers */, DC0376EE271B1C8500151E8C /* OCLocaleFilterClassSettings.h in Headers */, + DCE62EA92771EA0200E3193F /* OCResourceManager.h in Headers */, DC35969A2240EC0A00C4D6E6 /* OCQueryCondition+Item.h in Headers */, DC4B1171220830F20062BCDD /* OCHTTPPipelineBackend.h in Headers */, DC19BFD221CA6C15007C20D1 /* OCSyncIssueChoice.h in Headers */, @@ -3295,17 +4377,27 @@ DCC8FA21202B218100EB6701 /* OCAppIdentity.h in Headers */, DCC3701324D4D134008B0DEB /* OCScanJobActivity.h in Headers */, DC6BFFF623206215005FA5CB /* OCEventQueue.h in Headers */, + DC9C19F2278EE7230021222E /* OCResourceRequestImage.h in Headers */, DCDD9B1C22298D050052A001 /* OCGroup.h in Headers */, DC1889842189F50500CFB3F9 /* OCLogFileWriter.h in Headers */, + DCDBB5FB25248B0F00FAD707 /* OCResourceRequest.h in Headers */, DC576EC6226484E30087316D /* OCBackgroundManager.h in Headers */, DCE227D322D60D49000BE0A5 /* OCItemPolicy.h in Headers */, DC8556F6204F361100189B9A /* OCLogger.h in Headers */, + DC47E4D927A5820D0020E8EF /* GASpecialFolder.h in Headers */, + DC36EC7C27B5362800967483 /* OCConnection+OData.h in Headers */, DC434D0E20D68C3000740056 /* NSString+OCPath.h in Headers */, + DCE741DC29B7EA7000BFF393 /* OCBookmark+ServerInstance.h in Headers */, + DCC4F3F727D757FE00ABF4C9 /* OCDataConverter.h in Headers */, + DCFE3B9D27A1A6E500939415 /* GAGraphData+Decoder.h in Headers */, DC1C7AC2253F3CD9002F2B9F /* OCClassSettings+Metadata.h in Headers */, DC2AA57922DDD005001D5C39 /* OCSyncActionLocalCopyDelete.h in Headers */, DCA35D7624D00B2900DBE2B0 /* OCHTTPPipelineTask+Diagnostic.h in Headers */, DCDB76242739D51200EE7A06 /* OCServerLocatorLookupTable.h in Headers */, + DCFE682428D865BD00091D2A /* NSDictionary+OCFormEncoding.h in Headers */, + DCD3F6A027EA887600D86662 /* OCDataSourceArray.h in Headers */, DCC832CE242BB05A00153F8C /* OCCore+MessageResponseHandler.h in Headers */, + DCFC9EDC28003F0D005D9144 /* GARemoteItem.h in Headers */, DCC8FA0F2029C6A400EB6701 /* OCQueryChangeSet.h in Headers */, DC701484220B090B009D4FD9 /* OCHTTPTypes.h in Headers */, DC708CCE2141306100FE43CA /* OCSyncActionCopyMove.h in Headers */, @@ -3313,64 +4405,99 @@ DC5AD95422665AC800277DB0 /* OCHTTPPipelineTaskMetrics.h in Headers */, DC241E6E229549E200AEE068 /* OCAuthenticationMethodOpenIDConnect.h in Headers */, DCC8F9D9202854FB00EB6701 /* OCCore.h in Headers */, + DCE2F04327FB928B00E9E136 /* NSArray+OCFiltering.h in Headers */, DC2CD12B22E8E7FD0099C665 /* OCItemPolicyProcessorDownloadExpiration.h in Headers */, + DC47E4CE27A5820D0020E8EF /* GAODataErrorDetail.h in Headers */, DC6ABF732534683800689C7B /* OCExtension+HostSimulation.h in Headers */, + DC9C19E227839E440021222E /* OCResourceSourceStorage.h in Headers */, DCE17BC126B5A7E400B7C7DD /* OCHTTPRequest+Stream.h in Headers */, + DC0CE17B28C5DDE8009ABDFB /* OCAppProvider.h in Headers */, DC6CC30726428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.h in Headers */, + DCF072E42798630900E0B01D /* OCResourceTextPlaceholder.h in Headers */, DC4AFAB4206AE61400189B9A /* OCSQLiteQuery.h in Headers */, DC4AFAA6206A6E7100189B9A /* OCSQLiteResultSet.h in Headers */, DCEAA0B125CEB7290017F99B /* OCLock.h in Headers */, + DCB4F6EC28324B90005AD181 /* OCDataSourceKVO.h in Headers */, DC3521782251F15E00BC4F88 /* NSURLSessionTaskMetrics+OCCompactSummary.h in Headers */, 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 */, + DCB330C629EF2F0F00BFF393 /* OCIdentity+DataItem.h in Headers */, DCC8F9E62028556500EB6701 /* OCConnection.h in Headers */, DC24F8E821E2B3EF00C9119C /* OCWaitConditionIssue.h in Headers */, + DC47E4E427A5820D0020E8EF /* GADeleted.h in Headers */, DCB0A46C21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.h in Headers */, + DC0CE18B28C63B2E009ABDFB /* OCAppProviderFileType.h in Headers */, + DCEAF04E2806201300980B6D /* OCResourceText.h in Headers */, DC6D51D924A8BC4D006B75E6 /* OCNetworkMonitor.h in Headers */, DC8B7B3922D88FFD00E63657 /* OCItemPolicyProcessor.h in Headers */, DCF163F6274BA6C300E0182A /* OCSQLiteCollationLocalized.h in Headers */, DCD9B8822379783200691929 /* UIDevice+ModelID.h in Headers */, DC576ECA226484F50087316D /* OCBackgroundTask.h in Headers */, DCAEB06D21FA63D80067E147 /* OCSyncRecordActivity.h in Headers */, + DC0CE19628C8907D009ABDFB /* OCResourceRequestURLItem.h in Headers */, DCEA7D972093556600F25223 /* OCCache.h in Headers */, + DCB4F6E728324A3A005AD181 /* OCVaultDriveList.h in Headers */, + DC36EC8127B560D600967483 /* OCQueryCondition+ODataBuilder.h in Headers */, DCF1C68F2631C296004D8B0F /* OCMeasurementEvent.h in Headers */, + DC510D3627E146BD00F2754F /* OCDataItemRecord.h in Headers */, DC6DEEAD24C5978E00E3772E /* OCHTTPPolicy.h in Headers */, DC179CD520948DF30018DF7F /* NSProgress+OCExtensions.h in Headers */, DC6DEEB924C5C82400E3772E /* OCHTTPPolicyBookmark.h in Headers */, DCA35D7224D00A9800DBE2B0 /* OCHTTPPipeline+Diagnostic.h in Headers */, DCADC0442072CCC900DB8E83 /* OCCoreItemListTask.h in Headers */, DCD3439C2059319C00189B9A /* OCSQLiteDB.h in Headers */, + DCFA564E2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.h in Headers */, DC1D3DF22459B86200328EBC /* NSString+TUSMetadata.h in Headers */, DCD49A0E24E68F7B008D9544 /* OCBookmark+Diagnostics.h in Headers */, - DCDD9B18222989E50052A001 /* OCRecipient.h in Headers */, + DC47E4D827A5820D0020E8EF /* GAUser.h in Headers */, + DCDD9B18222989E50052A001 /* OCIdentity.h in Headers */, DCADC03F2072774200DB8E83 /* OCQuery+Internal.h in Headers */, + DCFE3B8827A16AE800939415 /* OCConnection+GraphAPI.h in Headers */, DC4A2C5E20D4608100A47260 /* OCIssueChoice.h in Headers */, DC9D22EA25A8754200CF5675 /* OCHTTPRequest+JSON.h in Headers */, DC07C29221244FD800B815A4 /* OCExtension.h in Headers */, + DCFE682028D857B500091D2A /* NSError+OCISError.h in Headers */, DCC8FA25202B259D00EB6701 /* OCSyncRecord.h in Headers */, + DC0CE18728C63B15009ABDFB /* OCAppProviderApp.h in Headers */, + DCFC9ED928003EFC005D9144 /* GAShared.h in Headers */, DC2D646821C3D71000EB26FD /* OCCore+Thumbnails.h in Headers */, DC6CC30F2642A0720040ECAC /* OCAuthenticationBrowserSessionMIBrowser.h in Headers */, DCC599F422EEE65700499B29 /* OCCore+Claims.h in Headers */, DC2CD12622E84AC30099C665 /* OCItemPolicyProcessorVacuum.h in Headers */, + DC826680281FE66600F91F7D /* OCVFSTypes.h in Headers */, + DCB330DD29F142FB00BFF393 /* OCShareRole+OCDataItem.h in Headers */, DC179CD1209475C20018DF7F /* UIImage+OCTools.h in Headers */, DCB572AE2099EFC600B793CE /* OCDatabase+Schemas.h in Headers */, DC07C29C2124526000B815A4 /* OCExtensionContext.h in Headers */, + DC9B4FD12941FF540037F8F8 /* OCCertificateStore.h in Headers */, DCE784FC2232748100733F01 /* OCHTTPResponse+DAVError.h in Headers */, + DC47E4E327A5820D0020E8EF /* GADriveItem.h in Headers */, + DC0BE5B828F80DBF00CE2101 /* OCSymbol.h in Headers */, DCE227D822D612EC000BE0A5 /* OCCore+ItemPolicies.h in Headers */, DC70147F220B0650009D4FD9 /* OCHTTPResponse.h in Headers */, DCC8F9F6202855A200EB6701 /* OCShare.h in Headers */, + DC47E4E827A5820D0020E8EF /* GAFolderView.h in Headers */, DC27BBC32304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.h in Headers */, DCA35D6124CF704100DBE2B0 /* OCSyncAction+Diagnostic.h in Headers */, DC5966A22276DB5D004CB28D /* OCSyncLane.h in Headers */, DCEEB2D82042F84B00189B9A /* NSObject+OCClassSettings.h in Headers */, DCC8FA152029EB9400EB6701 /* OCHTTPRequest.h in Headers */, + DC41C79025EA5F7A0074F23B /* OCResourceSource.h in Headers */, + DCF575DF27956D84003BEBBA /* OCViewProviderContext.h in Headers */, DC701477220AE696009D4FD9 /* OCHTTPPipelineTask.h in Headers */, + DC47E4DD27A5820D0020E8EF /* GAODataError.h in Headers */, + DC622C4E29019515001D73A0 /* OCLocale+SystemLanguage.h in Headers */, + DC47E4D727A5820D0020E8EF /* GAQuota.h in Headers */, DC1C7AC6253F8EE3002F2B9F /* NSError+OCClassSettings.h in Headers */, DC3C7FE121A6EDE00064D193 /* NSError+OCHTTPStatus.h in Headers */, DC19BFE921CBACB0007C20D1 /* OCProcessSession.h in Headers */, + DC47E4E527A5820D0020E8EF /* GAFolder.h in Headers */, DCDCA61C245093E800AFA158 /* NSURL+OCPrivateLink.h in Headers */, + DC47E4EA27A5820D0020E8EF /* GADrive.h in Headers */, DC7E0A6F2036F28B006111FA /* OCKeychain.h in Headers */, DC0376EA271B1A4900151E8C /* OCLocaleFilter.h in Headers */, DCC8FA122029D5EC00EB6701 /* OCTypes.h in Headers */, @@ -3378,11 +4505,13 @@ DC114A9822A7AA2E00CBD597 /* NSString+OCRandom.h in Headers */, DC19BFF121CBE28B007C20D1 /* OCWaitCondition.h in Headers */, DCDB76282739EF9A00EE7A06 /* OCExtension+ServerLocator.h in Headers */, + DC47E4D227A5820D0020E8EF /* GADirectoryObject.h in Headers */, DCA35D5D24CF6BEC00DBE2B0 /* NSArray+OCNullable.h in Headers */, DC3CE065242A49E100AB8B88 /* OCMessagePresenter.h in Headers */, DC04A4942330290A006285AC /* OCCoreProxy.h in Headers */, DCC8F9E22028554E00EB6701 /* OCBookmark.h in Headers */, DC20DE4B21BFCBC20096000B /* OCLogComponent.h in Headers */, + DC47E4CD27A5820D0020E8EF /* GAImage.h in Headers */, DC2F66A02603FCF6001BFDB6 /* OCCancelAction.h in Headers */, DCADC0482072CDEA00DB8E83 /* OCCoreItemList.h in Headers */, DC8245BA21FB334200775AB9 /* OCCore+Internal.h in Headers */, @@ -3390,20 +4519,27 @@ DC39DC462041A03300189B9A /* OCAuthenticationMethodBasicAuth.h in Headers */, DC2565F52260C86A00828AA5 /* OCCertificateRuleChecker.h in Headers */, DC85571C2050196000189B9A /* OCItem+OCXMLObjectCreation.h in Headers */, + DC9219DD2964CB4500F538EE /* GAEducationUser.h in Headers */, DC24F8F021E4F5BF00C9119C /* OCSQLiteDB+Internal.h in Headers */, + DCFE3B8227A167C800939415 /* GAGraph.h in Headers */, DC14CC4A21067320006DDA69 /* OCCore+ItemList.h in Headers */, DCAEB07121FA67060067E147 /* OCActivityUpdate.h in Headers */, DC594B1121EF4B2900B882C4 /* OCAsyncSequentialQueue.h in Headers */, DCF163F2274B917C00E0182A /* OCSQLiteCollation.h in Headers */, + DCF072E82798652500E0B01D /* OCResourceSourceAvatarPlaceholders.h in Headers */, DC4E0A5820927048007EB05F /* OCItemVersionIdentifier.h in Headers */, + DC47E4E027A5820D0020E8EF /* GAIdentitySet.h in Headers */, DCD2D40322F059190071FB8F /* OCClassSettingsUserPreferences.h in Headers */, DC3FE4BA229BD424002E009C /* OCCoreDirectoryUpdateJob.h in Headers */, DCEEB2DC20430B1400189B9A /* NSURL+OCURLQueryParameterExtensions.h in Headers */, + DC47E4E127A5820D0020E8EF /* GATrash.h in Headers */, + DC9219F62964CB6000F538EE /* GAObjectIdentity.h in Headers */, DC75D30B214BF1BA00B6FB62 /* NSString+OCFormatting.h in Headers */, DC381FD522C9E77500284699 /* OCCore+DirectURL.h in Headers */, DC1889792189AF3B00CFB3F9 /* OCClassSettingsFlatSourceEnvironment.h in Headers */, DCCE49392684BC1B005961D8 /* OCDAVRawResponse.h in Headers */, DCC83307242E1B4600153F8C /* OCCore+MessageAutoresolver.h in Headers */, + DC9219E92964CB4500F538EE /* GAEducationOrganization.h in Headers */, DC07C28E21244FC800B815A4 /* OCExtensionManager.h in Headers */, DCDB76122739D30500EE7A06 /* OCServerLocator.h in Headers */, DCE370942099D18100114981 /* OCDatabaseConsistentOperation.h in Headers */, @@ -3414,14 +4550,19 @@ DC66C6B420540DBD00189B9A /* NSDate+OCDateParser.h in Headers */, DCA35D4D24CF685B00DBE2B0 /* OCDiagnosticNode.h in Headers */, DCB0A45F21B828F400FAC4E9 /* OCCore+ConnectionStatus.h in Headers */, + DC9219E12964CB4500F538EE /* GAApplication.h in Headers */, DC0376F2271B1D4600151E8C /* OCLocaleFilterVariables.h in Headers */, DC45ABAE231018250065669D /* OCKeyValueRecord.h in Headers */, + DC510D2E27E1463900F2754F /* OCDataSourceSubscription.h in Headers */, DC5D9E6824963DED00BFFE8E /* OCMessageChoice.h in Headers */, DC51FD89247562C20069AB79 /* OCCellularManager.h in Headers */, + DC74E44427AD86DA005B4B03 /* OCLocation.h in Headers */, DC1C7ABE253F3C65002F2B9F /* OCClassSettings+Validation.h in Headers */, + DC9A116827CFCC1300D90BA4 /* GAPermission.h in Headers */, DC381FC722C80BA400284699 /* OCCore+NameConflicts.h in Headers */, DCE48DD8220E1C7B00839E97 /* OCHTTPPipelineTaskCache.h in Headers */, DC2F668D26035A33001BFDB6 /* OCSQLiteQuery+Private.h in Headers */, + DCE62EAD2771ED5700E3193F /* OCResourceImage.h in Headers */, DC07C2992124510200B815A4 /* OCExtensionTypes.h in Headers */, DC8556FF204F597800189B9A /* OCXMLParserNode.h in Headers */, DC19BFED21CBACBC007C20D1 /* OCProcessManager.h in Headers */, @@ -3434,39 +4575,57 @@ DC4B116D2208306C0062BCDD /* OCHTTPPipeline.h in Headers */, DC576ECE2264894E0087316D /* OCDeallocAction.h in Headers */, DCC8F9FB2028586900EB6701 /* OCAuthenticationMethod.h in Headers */, + DCEAF0462805B80D00980B6D /* OCResourceSourceDriveItems.h in Headers */, DC359692223FA7CC00C4D6E6 /* OCQueryCondition.h in Headers */, DC3F2B51204AED8400189B9A /* OCMacros.h in Headers */, DC2F636F2239557B0063C2DA /* OCShareQuery+Internal.h in Headers */, DC3CE0402429FAA200AB8B88 /* OCMessage.h in Headers */, + DC6C0A4C2923A0050045FF2A /* OCBookmark+DataItem.h in Headers */, DCC8FA2F202B405F00EB6701 /* OCEvent.h in Headers */, DCD63276223BB1710090169E /* OCCapabilities.h in Headers */, + DC47E4D427A5820D0020E8EF /* GAPasswordProfile.h in Headers */, DCEEB2ED2046E2B900189B9A /* OCCertificate.h in Headers */, + DC6FDAF229507579004F0C7F /* OCShare+OCDataItem.h in Headers */, DCA35D6524CF72B500DBE2B0 /* OCDiagnosticContext.h in Headers */, + DC47E4DB27A5820D0020E8EF /* GAItemReference.h in Headers */, DC4AFAB0206A8C1D00189B9A /* OCSQLiteStatement.h in Headers */, + DCC4F3EA27D74DE300ABF4C9 /* OCDataSource.h in Headers */, DC166E9E2428FD9A00347714 /* OCItemPolicyProcessorVersionUpdates.h in Headers */, + DC41C7A625EA61D70074F23B /* OCResourceTypes.h in Headers */, DC971BEE20D15D4400428EF1 /* OCSQLiteQueryCondition.h in Headers */, + DC47E4F027A7E2050020E8EF /* OCDrive.h in Headers */, DC0364FB20AAD75700F62732 /* OCCore+SyncEngine.h in Headers */, DC19BFDE21CB99D1007C20D1 /* OCIssue+SyncIssue.h in Headers */, + DCC4F3FB27D75AA600ABF4C9 /* OCDataRenderer.h in Headers */, + DC41C7FD25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.h in Headers */, DCADC0522072DE6600DB8E83 /* OCSQLiteMigration.h in Headers */, DCEEB2E92046BC2600189B9A /* OCHTTPStatus.h in Headers */, DC188993218B031600CFB3F9 /* OCLogSource.h in Headers */, + DC510D3227E1469600F2754F /* OCDataSourceSnapshot.h in Headers */, DC98BDF521E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.h in Headers */, DCDBEE382049EF3C00189B9A /* NSURL+OCURLNormalization.h in Headers */, DCA35D7E24D00EC400DBE2B0 /* OCWaitCondition+Diagnostic.h in Headers */, DC9B4D3A22E987EF0089BF78 /* OCClaim.h in Headers */, + DC47E4CB27A5820D0020E8EF /* GAHashes.h in Headers */, DC51FD8D247565080069AB79 /* OCCellularSwitch.h in Headers */, + DC2A127628D05DD30088A2B7 /* OCItem+OCTypeAlias.h in Headers */, DC35969622403E5B00C4D6E6 /* OCQueryCondition+SQLBuilder.h in Headers */, DC139CCC20DBBA8D0090175A /* OCChecksumAlgorithm.h in Headers */, DC708CE4214135E200FE43CA /* OCSyncActionDownload.h in Headers */, DC188997218B09CC00CFB3F9 /* OCLogFileSource.h in Headers */, + DCED67DC27F1B13600686E4F /* OCDataItemPresentable.h in Headers */, DCDD9B14222986D50052A001 /* OCShare+OCXMLObjectCreation.h in Headers */, + DC41C7EF25EA62E40074F23B /* OCResourceRequestAvatar.h in Headers */, DC772E9126FC90E2002C0015 /* OCAuthenticationBrowserSessionAWBrowser.h in Headers */, DCE451A52459AD3F0074363F /* OCTUSJob.h in Headers */, DCDD9B2B22312ED80052A001 /* OCRateLimiter.h in Headers */, DC6ABF7925365CB100689C7B /* OCHostSimulator+BuiltIn.h in Headers */, + DC9219F72964CB6000F538EE /* GATagAssignment.h in Headers */, DC3CE0482429FCDF00AB8B88 /* OCMessageQueue.h in Headers */, DCC6567420CA695600110A97 /* OCCoreManager.h in Headers */, + DC826669281AC5B000F91F7D /* OCVFSNode.h in Headers */, DCDC62E124D8A04900585261 /* NSArray+OCSegmentedProcessing.h in Headers */, + DC49B5602837DCE700DAF13B /* OCItem+OCVFSItem.h in Headers */, DC72E42E2063DBF900189B9A /* OCClassSettingsFlatSourceManagedConfiguration.h in Headers */, DC2F636B2239523A0063C2DA /* OCShareQuery.h in Headers */, DCB0A46421B922A400FAC4E9 /* OCCoreConnectionStatusSignalProvider.h in Headers */, @@ -3476,11 +4635,16 @@ DC139CC820DBB8440090175A /* OCChecksum.h in Headers */, DCC8FA032029BA7A00EB6701 /* OCVault.h in Headers */, DC29F4C224323C4900347658 /* OCMessageTemplate.h in Headers */, + DC6C0A512923A59A0045FF2A /* OCDataSourceMapped.h in Headers */, + DC0BE5B128F80BBA00CE2101 /* OCShareRole.h in Headers */, DCA35D5924CF6B2000DBE2B0 /* OCSyncRecord+Diagnostic.h in Headers */, + DC82665F2818972200F91F7D /* OCVaultLocation.h in Headers */, DC8556F1204DEB9200189B9A /* OCXMLNode.h in Headers */, + DC47E4C227A5820D0020E8EF /* GAGroup.h in Headers */, DC39DC4B2041A2FB00189B9A /* NSError+OCError.h in Headers */, DC139CD020DBC1690090175A /* OCChecksumAlgorithmSHA1.h in Headers */, DCA35D5524CF688700DBE2B0 /* OCDiagnosticSource.h in Headers */, + DCDBB5F725248B0300FAD707 /* OCResource.h in Headers */, DC19BFCA21CA6B91007C20D1 /* OCSyncIssue.h in Headers */, DC3E6E9226094EE200D7D847 /* OCDatabase+Versions.h in Headers */, DCCE49342684B148005961D8 /* OCVault+Prepopulation.h in Headers */, @@ -3488,6 +4652,9 @@ DC2F63632239455E0063C2DA /* OCRecipientSearchController.h in Headers */, DC381FCB22C8146F00284699 /* NSString+NameConflicts.h in Headers */, DC708CE8214135FE00FE43CA /* OCSyncActionUpload.h in Headers */, + DCF575D7279567AB003BEBBA /* OCPlatform.h in Headers */, + DC41C7BA25EA62520074F23B /* OCResourceSourceItemThumbnails.h in Headers */, + DC47E4C427A5820D0020E8EF /* GAODataErrorMain.h in Headers */, DC27BBBF230498C3002CC2F8 /* OCHTTPCookieStorage.h in Headers */, DC8245B321FB31E500775AB9 /* OCActivityManager.h in Headers */, DCB0A45921B7F76800FAC4E9 /* NSError+OCDAVError.h in Headers */, @@ -3496,17 +4663,27 @@ DC0283632090A3E8005B6334 /* OCItemThumbnail.h in Headers */, DCADC04D2072D54200DB8E83 /* OCSQLiteTableSchema.h in Headers */, DC73F3BF254BFE9900CE5FA9 /* NSArray+ObjCRuntime.h in Headers */, + DCC4F3FF27D75BF700ABF4C9 /* OCDataConverterPipeline.h in Headers */, 4C7295EA228DB0A800FA4E68 /* OCLogFileRecord.h in Headers */, + DCED67D727F1A7B200686E4F /* OCCore+DataSources.h in Headers */, DC20DE5021BFCEB00096000B /* OCLogToggle.h in Headers */, DCC8F9EA2028557100EB6701 /* OCDatabase.h in Headers */, DC76500C2404703A00201812 /* OCWaitConditionMetaDataRefresh.h in Headers */, DC4B11FE220996480062BCDD /* OCProgress.h in Headers */, + DC9C19EE278CD0B30021222E /* OCDatabase+ResourceStorage.h in Headers */, + DCFE3B7A27A1666B00939415 /* GAGraphObject.h in Headers */, DCE3D4E42701C40B0074C254 /* OCCoreUpdateScheduleRecord.h in Headers */, + DC46F3C42843C66B00038880 /* OCAction.h in Headers */, + DC2218C228228F7000808BCE /* OCVFSContent.h in Headers */, + DCFC9EE128004791005D9144 /* OCDataSourceComposition.h in Headers */, DC02835B209098D7005B6334 /* OCImage.h in Headers */, + DCEAF04A2805B83B00980B6D /* OCResourceRequestDriveItem.h in Headers */, DC3422822180765900705508 /* OCCore+ItemUpdates.h in Headers */, DCD038A02542CA4500F97534 /* NSString+OCClassSettings.h in Headers */, DCC8F9F22028559600EB6701 /* OCItem.h in Headers */, + DC36EC8527B5611500967483 /* OCODataTypes.h in Headers */, DC2266A82282BC8100FB29EE /* OCBookmark+IPNotificationNames.h in Headers */, + DCF575D1279562DF003BEBBA /* OCViewProvider.h in Headers */, DC708CDC214135C000FE43CA /* OCSyncActionCreateFolder.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3643,7 +4820,7 @@ DCC8F9A2202852A200EB6701 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1240; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = "ownCloud GmbH"; TargetAttributes = { DC3094832057358800189B9A = { @@ -3793,6 +4970,7 @@ /* Begin PBXShellScriptBuildPhase section */ DC102FAD22D3E0F300DEBC38 /* Update LastGitCommit key in Info.plist */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -3808,7 +4986,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "LASTGITCOMMIT=$(git rev-parse --short HEAD)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"LastGitCommit\" \"${LASTGITCOMMIT}\"\necho defaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"LastGitCommit\" \"${LASTGITCOMMIT}\"\n"; + shellScript = "LASTGITCOMMIT=$(git rev-parse --short HEAD)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"LastGitCommit\" \"${LASTGITCOMMIT}\"\n\nGITTAGS=$(git describe --tags)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"GitTags\" \"${GITTAGS}\"\n\nGITBRANCH=$(git branch --show-current)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"GitBranch\" \"${GITBRANCH}\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3864,18 +5042,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DCFC9EDD28003F0D005D9144 /* GARemoteItem.m in Sources */, DC381FCC22C8146F00284699 /* NSString+NameConflicts.m in Sources */, + DC0CE19B28C89227009ABDFB /* OCResourceSourceURLItems.m in Sources */, DCF95AEB25666FBB00806D2A /* OCClassSetting.m in Sources */, + DCEAF04B2805B83B00980B6D /* OCResourceRequestDriveItem.m in Sources */, DC8EB30523952085009148F9 /* OCAuthenticationBrowserSessionUIWebView.m in Sources */, DCE2661D2113323C0001FB2C /* OCCore+CommandDownload.m in Sources */, + DCC4F3EB27D74DE300ABF4C9 /* OCDataSource.m in Sources */, + DC510D3327E1469600F2754F /* OCDataSourceSnapshot.m in Sources */, DC0376EB271B1A4900151E8C /* OCLocaleFilter.m in Sources */, DCB0A46D21B9355C00FAC4E9 /* OCCoreServerStatusSignalProvider.m in Sources */, + DCDBB5EB2523E3AF00FAD707 /* OCAvatar.m in Sources */, DCEEB2F22047094500189B9A /* NSData+OCHash.m in Sources */, DCF163F3274B917C00E0182A /* OCSQLiteCollation.m in Sources */, DCDD9B1D22298D050052A001 /* OCGroup.m in Sources */, DC72E4332063DD7600189B9A /* OCClassSettingsFlatSourcePropertyList.m in Sources */, DC4AFAB9206AE92F00189B9A /* OCSQLiteTransaction.m in Sources */, DC76500D2404703A00201812 /* OCWaitConditionMetaDataRefresh.m in Sources */, + DC9219F82964CB6000F538EE /* GAObjectIdentity.m in Sources */, DC359693223FA7CC00C4D6E6 /* OCQueryCondition.m in Sources */, DCFFF57F20D3A51C0096D2D3 /* OCSyncContext.m in Sources */, DCA36D4E22A6B14200265534 /* OCPKCE.m in Sources */, @@ -3884,28 +5069,40 @@ DC2FED61228D5589004FDEC6 /* OCCore+Favorites.m in Sources */, DCC3701424D4D134008B0DEB /* OCScanJobActivity.m in Sources */, DC29F4C324323C4900347658 /* OCMessageTemplate.m in Sources */, + DCEAF0472805B80D00980B6D /* OCResourceSourceDriveItems.m in Sources */, DC3CE066242A49E100AB8B88 /* OCMessagePresenter.m in Sources */, + DC9C19EF278CD0B30021222E /* OCDatabase+ResourceStorage.m in Sources */, DCD038A12542CA4500F97534 /* NSString+OCClassSettings.m in Sources */, DC68057E212EB438006C3B1F /* OCExtensionMatch.m in Sources */, + DCC4F40027D75BF700ABF4C9 /* OCDataConverterPipeline.m in Sources */, DC9D22EB25A8754200CF5675 /* OCHTTPRequest+JSON.m in Sources */, DC772E9226FC90E2002C0015 /* OCAuthenticationBrowserSessionAWBrowser.m in Sources */, DC302AEF221EAC55003218C6 /* OCProxyProgress.m in Sources */, + DC9C19F3278EE7230021222E /* OCResourceRequestImage.m in Sources */, DC4B1172220830F20062BCDD /* OCHTTPPipelineBackend.m in Sources */, 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 */, + DCDBB5F825248B0300FAD707 /* OCResource.m in Sources */, DC2AA57A22DDD005001D5C39 /* OCSyncActionLocalCopyDelete.m in Sources */, DCC8F9EF2028558000EB6701 /* OCQuery.m in Sources */, DC6D51DA24A8BC4D006B75E6 /* OCNetworkMonitor.m in Sources */, DC04A4952330290A006285AC /* OCCoreProxy.m in Sources */, + DC6C0A522923A59A0045FF2A /* OCDataSourceMapped.m in Sources */, DCEE0B5725E68C53006534B5 /* OCBookmarkManager+ItemResolution.m in Sources */, DCD9B8832379783200691929 /* UIDevice+ModelID.m in Sources */, DC139CD120DBC1690090175A /* OCChecksumAlgorithmSHA1.m in Sources */, + DC9A116927CFCC1300D90BA4 /* GAPermission.m in Sources */, DCC8FA042029BA7A00EB6701 /* OCVault.m in Sources */, DC39DC5A204215A800189B9A /* NSProgress+OCEvent.m in Sources */, + DC47E4C527A5820D0020E8EF /* GAItemReference.m in Sources */, DCADC0402072774200DB8E83 /* OCQuery+Internal.m in Sources */, + DC0CE17C28C5DDE8009ABDFB /* OCAppProvider.m in Sources */, + DC41C79125EA5F7A0074F23B /* OCResourceSource.m in Sources */, DC30947420542FA500189B9A /* OCConnection+Users.m in Sources */, DC1C7AC7253F8EE3002F2B9F /* NSError+OCClassSettings.m in Sources */, DC3521792251F15E00BC4F88 /* NSURLSessionTaskMetrics+OCCompactSummary.m in Sources */, @@ -3915,6 +5112,7 @@ DC4AFAA7206A6E7100189B9A /* OCSQLiteResultSet.m in Sources */, DCE451A62459AD3F0074363F /* OCTUSJob.m in Sources */, DC19BFEE21CBACBC007C20D1 /* OCProcessManager.m in Sources */, + DC41C7BB25EA62520074F23B /* OCResourceSourceItemThumbnails.m in Sources */, DCC8FA0020285C1500EB6701 /* OCAuthenticationMethodOAuth2.m in Sources */, DCDB761F2739D4A300EE7A06 /* OCServerLocatorWebFinger.m in Sources */, DCDD9B15222986D50052A001 /* OCShare+OCXMLObjectCreation.m in Sources */, @@ -3922,8 +5120,13 @@ DCF163F7274BA6C300E0182A /* OCSQLiteCollationLocalized.m in Sources */, DC4B116E2208306C0062BCDD /* OCHTTPPipeline.m in Sources */, DC8B7B3A22D88FFD00E63657 /* OCItemPolicyProcessor.m in Sources */, + DC8266602818972200F91F7D /* OCVaultLocation.m in Sources */, + DC6FDAF329507579004F0C7F /* OCShare+OCDataItem.m in Sources */, DCA35D5E24CF6BEC00DBE2B0 /* NSArray+OCNullable.m in Sources */, + DCF575E027956D84003BEBBA /* OCViewProviderContext.m in Sources */, + DC510D2F27E1463900F2754F /* OCDataSourceSubscription.m in Sources */, DC2F63642239455E0063C2DA /* OCRecipientSearchController.m in Sources */, + DCFA564F2975B6490092C89F /* OCClassSettingsFlatSourcePostBuild.m in Sources */, DCD49A0D24E68D72008D9544 /* OCBookmark+Diagnostics.m in Sources */, DC98BDF621E73ECE003B5658 /* OCCoreNetworkMonitorSignalProvider.m in Sources */, DC8245B421FB31E500775AB9 /* OCActivityManager.m in Sources */, @@ -3941,37 +5144,62 @@ DC5D9E6924963DED00BFFE8E /* OCMessageChoice.m in Sources */, DC855700204F597800189B9A /* OCXMLParserNode.m in Sources */, DCEEB2D92042F84B00189B9A /* NSObject+OCClassSettings.m in Sources */, + DCC4F3F827D757FE00ABF4C9 /* OCDataConverter.m in Sources */, + DC28F824294B6DE600AC4013 /* OCItemPolicy+OCDataItem.m in Sources */, + DC9219E62964CB4500F538EE /* GAAppRole.m in Sources */, + DC2218C328228F7000808BCE /* OCVFSContent.m in Sources */, + DC0CE18028C63232009ABDFB /* OCConnection+AppProviders.m in Sources */, DC114A9922A7AA2E00CBD597 /* NSString+OCRandom.m in Sources */, + DC47E4D627A5820D0020E8EF /* GAGroup.m in Sources */, DC07C297212450DC00B815A4 /* OCExtensionLocation.m in Sources */, DC971BEF20D15D4400428EF1 /* OCSQLiteQueryCondition.m in Sources */, + DC9219F32964CB6000F538EE /* GATagAssignment.m in Sources */, DC446C84206B8DB500189B9A /* OCRunLoopThread.m in Sources */, DC6CF8212195D56C0013B9F9 /* OCCore+CommandUpdate.m in Sources */, DCB0A46021B828F400FAC4E9 /* OCCore+ConnectionStatus.m in Sources */, DC188994218B031600CFB3F9 /* OCLogSource.m in Sources */, DCC8FA26202B259D00EB6701 /* OCSyncRecord.m in Sources */, DC6ABF752536058A00689C7B /* OCHostSimulatorResponse.m in Sources */, + DC0CE18828C63B15009ABDFB /* OCAppProviderApp.m in Sources */, + DCD3F6A127EA887600D86662 /* OCDataSourceArray.m in Sources */, DCDD9B2C22312ED80052A001 /* OCRateLimiter.m in Sources */, DC0376DF271A33B900151E8C /* OCLocale.m in Sources */, + DC74E44527AD86DA005B4B03 /* OCLocation.m in Sources */, DCE3D4E52701C40B0074C254 /* OCCoreUpdateScheduleRecord.m in Sources */, + DCFE3B7F27A1669300939415 /* GAGraphContext.m in Sources */, + DCB4F6E828324A3A005AD181 /* OCVaultDriveList.m in Sources */, DC4E0A5920927048007EB05F /* OCItemVersionIdentifier.m in Sources */, + DC47E4C727A5820D0020E8EF /* GADriveItem.m in Sources */, + DC0BE5B928F80DBF00CE2101 /* OCSymbol.m in Sources */, DCEEB2EA2046BC2600189B9A /* OCHTTPStatus.m in Sources */, DC62FD63211B11FD0034B628 /* OCItem+OCThumbnail.m in Sources */, DCEEB2DD20430B1400189B9A /* NSURL+OCURLQueryParameterExtensions.m in Sources */, + DC47E4CC27A5820D0020E8EF /* GAFileSystemInfo.m in Sources */, DC2F6376223A61990063C2DA /* OCCoreQuery.m in Sources */, DCC8F9F7202855A200EB6701 /* OCShare.m in Sources */, + DC0CE19728C8907D009ABDFB /* OCResourceRequestURLItem.m in Sources */, DC47DF772770CEE300989D84 /* NSError+OCErrorTools.m in Sources */, + DCFE682128D857B500091D2A /* NSError+OCISError.m in Sources */, + DCE62EAA2771EA0200E3193F /* OCResourceManager.m in Sources */, DCA35D7324D00A9800DBE2B0 /* OCHTTPPipeline+Diagnostic.m in Sources */, DCDB76292739EF9A00EE7A06 /* OCExtension+ServerLocator.m in Sources */, DC73F3C0254BFE9900CE5FA9 /* NSArray+ObjCRuntime.m in Sources */, DC39DC472041A03300189B9A /* OCAuthenticationMethodBasicAuth.m in Sources */, + DC9219E32964CB4500F538EE /* GAEducationUser.m in Sources */, DCEAA0D125CEB7F90017F99B /* OCLockRequest.m in Sources */, DC73F3AD254BF95C00CE5FA9 /* OCClassSettings+Documentation.m in Sources */, DC1D3DF32459B86200328EBC /* NSString+TUSMetadata.m in Sources */, + DC41C7DD25EA62CD0074F23B /* OCResourceSourceAvatars.m in Sources */, + DC47E4D327A5820D0020E8EF /* GAFolder.m in Sources */, DC34227B217CAA0B00705508 /* OCIPNotificationCenter.m in Sources */, DCC8FA0C2029C0BE00EB6701 /* OCQueryFilter.m in Sources */, DCA35D6224CF704100DBE2B0 /* OCSyncAction+Diagnostic.m in Sources */, + DC49B5612837DCE700DAF13B /* OCItem+OCVFSItem.m in Sources */, DCDBEE2C2048A6A800189B9A /* OCConnection+Setup.m in Sources */, + DC9B4FD22941FF540037F8F8 /* OCCertificateStore.m in Sources */, + DC47E4C127A5820D0020E8EF /* GAQuota.m in Sources */, DC72E42A2063D61F00189B9A /* OCClassSettingsFlatSource.m in Sources */, + DCF575D8279567AB003BEBBA /* OCPlatform.m in Sources */, DC45ABAF231018250065669D /* OCKeyValueRecord.m in Sources */, DCC3701024D4B3B7008B0DEB /* OCDatabase+Diagnostic.m in Sources */, DC03AC472229598E006901DC /* OCConnection+Sharing.m in Sources */, @@ -3979,52 +5207,80 @@ DC0283642090A3E8005B6334 /* OCItemThumbnail.m in Sources */, DC2CD12722E84AC30099C665 /* OCItemPolicyProcessorVacuum.m in Sources */, DC75D310214C015F00B6FB62 /* OCItem+OCItemCreationDebugging.m in Sources */, + DC46F3C52843C66B00038880 /* OCAction.m in Sources */, DC07C28F21244FC800B815A4 /* OCExtensionManager.m in Sources */, DC1C7AC3253F3CD9002F2B9F /* OCClassSettings+Metadata.m in Sources */, DC576ECF2264894E0087316D /* OCDeallocAction.m in Sources */, DC8FE700221CAF280016BDEE /* OCProgressManager.m in Sources */, + DC47E4DF27A5820D0020E8EF /* GAHashes.m in Sources */, DC72568120405752006111FA /* OCClassSettings.m in Sources */, + DCDBB5E72523DDA200FAD707 /* OCConnection+Avatars.m in Sources */, + DCE741DD29B7EA7000BFF393 /* OCBookmark+ServerInstance.m in Sources */, + DC9219E72964CB4500F538EE /* GAEntity.m in Sources */, DCE370952099D18100114981 /* OCDatabaseConsistentOperation.m in Sources */, DCC8FA30202B405F00EB6701 /* OCEvent.m in Sources */, DCC8FA22202B218100EB6701 /* OCAppIdentity.m in Sources */, DCE227CF22D60CF5000BE0A5 /* OCCore+AvailableOffline.m in Sources */, + DC9C19E7278488360021222E /* OCResourceManagerJob.m in Sources */, DC3CE03F2429FAA200AB8B88 /* OCMessage.m in Sources */, DC6BFFEE231FB9F8005FA5CB /* OCEventRecord.m in Sources */, DCEAA0B325CEB7290017F99B /* OCLock.m in Sources */, + DC82666A281AC5B000F91F7D /* OCVFSNode.m in Sources */, + DC9219E52964CB4500F538EE /* GAEducationSchool.m in Sources */, DCEEB2EE2046E2B900189B9A /* OCCertificate.m in Sources */, DC20DE4C21BFCBC20096000B /* OCLogComponent.m in Sources */, DC07C29321244FD800B815A4 /* OCExtension.m in Sources */, DCD3439D2059319C00189B9A /* OCSQLiteDB.m in Sources */, DCE784F922325D4F00733F01 /* OCConnection+Recipients.m in Sources */, + DC0BE5B228F80BBA00CE2101 /* OCShareRole.m in Sources */, DCC6567520CA695600110A97 /* OCCoreManager.m in Sources */, DC2F66A12603FCF6001BFDB6 /* OCCancelAction.m in Sources */, DC6DEEBA24C5C82400E3772E /* OCHTTPPolicyBookmark.m in Sources */, DCEE0B7025E697AF006534B5 /* OCCoreManager+ItemResolution.m in Sources */, + DC36EC8227B560D600967483 /* OCQueryCondition+ODataBuilder.m in Sources */, DC0376EF271B1C8500151E8C /* OCLocaleFilterClassSettings.m in Sources */, + DC47E4BF27A5820D0020E8EF /* GASpecialFolder.m in Sources */, DC1889812189EC2600CFB3F9 /* OCLogWriter.m in Sources */, + DCF072E92798652500E0B01D /* OCResourceSourceAvatarPlaceholders.m in Sources */, + DC47E4C027A5820D0020E8EF /* GAUser.m in Sources */, DCADC0492072CDEA00DB8E83 /* OCCoreItemList.m in Sources */, + DC6C0A4D2923A0050045FF2A /* OCBookmark+DataItem.m in Sources */, DCDBEE392049EF3C00189B9A /* NSURL+OCURLNormalization.m in Sources */, DCC8F9DA202854FB00EB6701 /* OCCore.m in Sources */, DCAEB07221FA67060067E147 /* OCActivityUpdate.m in Sources */, + DCE741D929B7DD5000BFF393 /* OCServerInstance.m in Sources */, DCC832CF242BB05A00153F8C /* OCCore+MessageResponseHandler.m in Sources */, DC51FD8E247565080069AB79 /* OCCellularSwitch.m in Sources */, + DC47E4F627A83D9B0020E8EF /* OCQuota.m in Sources */, + DC510D3727E146BD00F2754F /* OCDataItemRecord.m in Sources */, + DCF072E52798630900E0B01D /* OCResourceTextPlaceholder.m in Sources */, DCC8F9EB2028557100EB6701 /* OCDatabase.m in Sources */, + DCFC9EE228004791005D9144 /* OCDataSourceComposition.m in Sources */, + DCED67DD27F1B13600686E4F /* OCDataItemPresentable.m in Sources */, DC0AE4F32311C75300428681 /* OCKeyValueStack.m in Sources */, DC19BFCB21CA6B91007C20D1 /* OCSyncIssue.m in Sources */, DC6ABF762536059200689C7B /* OCHostSimulator.m in Sources */, + DC9219F42964CB6000F538EE /* GATagUnassignment.m in Sources */, DCB6D05922A13E7500CA47C5 /* NSString+OCSQLTools.m in Sources */, 4C7295E8228DAD6200FA4E68 /* OCLogFileRecord.m in Sources */, DCA91F3021A0BDE400AEDFB4 /* OCSyncAction+FileProvider.m in Sources */, + DC47E4C927A5820D0020E8EF /* GAIdentity.m in Sources */, DCADC0532072DE6600DB8E83 /* OCSQLiteMigration.m in Sources */, DC0376F3271B1D4600151E8C /* OCLocaleFilterVariables.m in Sources */, + DC36EC7D27B5362800967483 /* OCConnection+OData.m in Sources */, DC545E7B203F7DD0006111FA /* OCUser.m in Sources */, DC576ECB226484F50087316D /* OCBackgroundTask.m in Sources */, DC2565F22260BE3700828AA5 /* OCCertificate+PrivacyLogging.m in Sources */, DCA35D6624CF72B500DBE2B0 /* OCDiagnosticContext.m in Sources */, + DC0BE5BF28F9427900CE2101 /* OCStatistic.m in Sources */, + DCFC9ED828003EFC005D9144 /* GAShared.m in Sources */, DC594B1221EF4B2900B882C4 /* OCAsyncSequentialQueue.m in Sources */, + DC41C7CE25EA627A0074F23B /* OCResourceRequestItemThumbnail.m in Sources */, DC22669B22817DC600FB29EE /* OCVault+Internal.m in Sources */, DC85571D2050196000189B9A /* OCItem+OCXMLObjectCreation.m in Sources */, DC7E0A702036F28B006111FA /* OCKeychain.m in Sources */, + DC826666281AC59D00F91F7D /* OCVFSCore.m in Sources */, + DC49B55428339BE200DAF13B /* NSArray+OCMapping.m in Sources */, DC4AFAB1206A8C1D00189B9A /* OCSQLiteStatement.m in Sources */, DCEEB2F6204802CF00189B9A /* OCIssue.m in Sources */, DC24F8F121E4F5BF00C9119C /* OCSQLiteDB+Internal.m in Sources */, @@ -4035,34 +5291,46 @@ DC1C7ABF253F3C65002F2B9F /* OCClassSettings+Validation.m in Sources */, DC20DE5121BFCEB00096000B /* OCLogToggle.m in Sources */, DC139CCD20DBBA8D0090175A /* OCChecksumAlgorithm.m in Sources */, + DC47E4CA27A5820D0020E8EF /* GAIdentitySet.m in Sources */, + DC9B4FD62941FF630037F8F8 /* OCCertificateStoreRecord.m in Sources */, DCDC62E224D8A04900585261 /* NSArray+OCSegmentedProcessing.m in Sources */, + DC47E4C827A5820D0020E8EF /* GATrash.m in Sources */, DC701478220AE696009D4FD9 /* OCHTTPPipelineTask.m in Sources */, DCEA7D982093556600F25223 /* OCCache.m in Sources */, DCE227D422D60D49000BE0A5 /* OCItemPolicy.m in Sources */, DC07C29D2124526000B815A4 /* OCExtensionContext.m in Sources */, DC434D0A20D5AA9E00740056 /* OCCore+CommandCreateFolder.m in Sources */, DC6ABF742534683800689C7B /* OCExtension+HostSimulation.m in Sources */, + DC47E4D527A5820D0020E8EF /* GADeleted.m in Sources */, + DCD09E3D2A1B573100BFF393 /* OCResourceSourceURL.m in Sources */, + DC47E4EB27A5820D0020E8EF /* GAODataErrorDetail.m in Sources */, DCEAA0B225CEB7290017F99B /* OCLockManager.m in Sources */, DCF1C6902631C296004D8B0F /* OCMeasurementEvent.m in Sources */, DCDA307221412A0100DB61A9 /* OCSyncAction.m in Sources */, + DCED67D827F1A7B200686E4F /* OCCore+DataSources.m in Sources */, DCC83308242E1B4600153F8C /* OCCore+MessageAutoresolver.m in Sources */, DC2F63682239509E0063C2DA /* OCCore+Sharing.m in Sources */, + DC2A127728D05DD30088A2B7 /* OCItem+OCTypeAlias.m in Sources */, DC00DB1E219B120300C82737 /* OCHTTPDAVMultistatusResponse.m in Sources */, DC02835C209098D7005B6334 /* OCImage.m in Sources */, DC708CCF2141306100FE43CA /* OCSyncActionCopyMove.m in Sources */, + DC9219E42964CB4500F538EE /* GAApplication.m in Sources */, DC19BFF221CBE28B007C20D1 /* OCWaitCondition.m in Sources */, DC27BBC42304A7CE002CC2F8 /* NSHTTPCookie+OCCookies.m in Sources */, DC51FD8A247562C20069AB79 /* OCCellularManager.m in Sources */, DC19BFD321CA6C15007C20D1 /* OCSyncIssueChoice.m in Sources */, DCE227D922D612EC000BE0A5 /* OCCore+ItemPolicies.m in Sources */, DC2CD12C22E8E7FD0099C665 /* OCItemPolicyProcessorDownloadExpiration.m in Sources */, + DC41C7FE25EA63A00074F23B /* OCResourceSourceItemLocalThumbnails.m in Sources */, DC1D4D3820DBD58E005A3DFC /* OCFile.m in Sources */, DC27A1A120CBE8BD008ACB6C /* OCCore+FileProvider.m in Sources */, DC1889852189F50500CFB3F9 /* OCLogFileWriter.m in Sources */, DC3E6E802609473200D7D847 /* OCBookmark+DBMigration.m in Sources */, + DCB4F6ED28324B90005AD181 /* OCDataSourceKVO.m in Sources */, DC8913652092088600028999 /* NSString+OCVersionCompare.m in Sources */, DC75D30C214BF1BA00B6FB62 /* NSString+OCFormatting.m in Sources */, DCADC0452072CCC900DB8E83 /* OCCoreItemListTask.m in Sources */, + DC622C4F29019515001D73A0 /* OCLocale+SystemLanguage.m in Sources */, DC576EC7226484E30087316D /* OCBackgroundManager.m in Sources */, DC381FC822C80BA400284699 /* OCCore+NameConflicts.m in Sources */, DC6ABF7A25365CB100689C7B /* OCHostSimulator+BuiltIn.m in Sources */, @@ -4073,13 +5341,18 @@ DCA35D7724D00B2900DBE2B0 /* OCHTTPPipelineTask+Diagnostic.m in Sources */, DCD8439B25E1BEE5008D9BBA /* NSDictionary+OCExpand.m in Sources */, DC139CC920DBB8440090175A /* OCChecksum.m in Sources */, + DCEAF04F2806201300980B6D /* OCResourceText.m in Sources */, DC0364FC20AAD75700F62732 /* OCCore+SyncEngine.m in Sources */, DC381FD622C9E77500284699 /* OCCore+DirectURL.m in Sources */, DCA35D5A24CF6B2000DBE2B0 /* OCSyncRecord+Diagnostic.m in Sources */, + DC41C7F025EA62E40074F23B /* OCResourceRequestAvatar.m in Sources */, + DCFE3B9E27A1A6E500939415 /* GAGraphData+Decoder.m in Sources */, DC7650092403DF5000201812 /* NSError+OCNetworkFailure.m in Sources */, DC4B11FF220996480062BCDD /* OCProgress.m in Sources */, + DC47E4C327A5820D0020E8EF /* GAODataError.m in Sources */, DC5B96D624916CF200733594 /* OCConnection+Upload.m in Sources */, DCC8F9E32028554E00EB6701 /* OCBookmark.m in Sources */, + DC47E4D127A5820D0020E8EF /* GADrive.m in Sources */, DC680586212EC27B006C3B1F /* OCExtension+License.m in Sources */, DCE48DD9220E1C7B00839E97 /* OCHTTPPipelineTaskCache.m in Sources */, DC701480220B0650009D4FD9 /* OCHTTPResponse.m in Sources */, @@ -4088,18 +5361,25 @@ DC6B0473268D1950003FDEC1 /* OCBookmark+Prepopulation.m in Sources */, DC6DEEB624C59BE300E3772E /* OCHTTPPolicyManager.m in Sources */, DC6CC3102642A0720040ECAC /* OCAuthenticationBrowserSessionMIBrowser.m in Sources */, + DCED67D327F1A48000686E4F /* OCDataTypes.m in Sources */, DCB0A45A21B7F76800FAC4E9 /* NSError+OCDAVError.m in Sources */, + DCB330C729EF2F0F00BFF393 /* OCIdentity+DataItem.m in Sources */, + DCE62EAE2771ED5700E3193F /* OCResourceImage.m in Sources */, DCDBEE342048A8BC00189B9A /* OCConnection+Authentication.m in Sources */, DC8556F2204DEB9200189B9A /* OCXMLNode.m in Sources */, DC3FE4BB229BD424002E009C /* OCCoreDirectoryUpdateJob.m in Sources */, DCC8F9E72028556500EB6701 /* OCConnection.m in Sources */, DCC8F9FC2028586900EB6701 /* OCAuthenticationMethod.m in Sources */, + DC47E4EC27A5820D0020E8EF /* GAImage.m in Sources */, DC19BFEA21CBACB0007C20D1 /* OCProcessSession.m in Sources */, DC8556F7204F361100189B9A /* OCLogger.m in Sources */, DC24F8E921E2B3EF00C9119C /* OCWaitConditionIssue.m in Sources */, + DC47E4CF27A5820D0020E8EF /* GAFolderView.m in Sources */, DCE17BC226B5A7E400B7C7DD /* OCHTTPRequest+Stream.m in Sources */, + DCFE682528D865BD00091D2A /* NSDictionary+OCFormEncoding.m in Sources */, DCAEB06E21FA63D80067E147 /* OCSyncRecordActivity.m in Sources */, DCE784FD2232748100733F01 /* OCHTTPResponse+DAVError.m in Sources */, + DC47E4DC27A5820D0020E8EF /* GAODataErrorMain.m in Sources */, DC179CD620948DF30018DF7F /* NSProgress+OCExtensions.m in Sources */, DCC6567920CA696A00110A97 /* OCBookmarkManager.m in Sources */, DCCE493A2684BC1B005961D8 /* OCDAVRawResponse.m in Sources */, @@ -4111,20 +5391,26 @@ DC4A2C5F20D4608100A47260 /* OCIssueChoice.m in Sources */, DCC8FA34202B443D00EB6701 /* OCEventTarget.m in Sources */, DCFF1AB121655C8800ABE40A /* OCItem+OCFileURLMetadata.m in Sources */, + DC47E4E627A5820D0020E8EF /* GAPasswordProfile.m in Sources */, + DCC4F3FC27D75AA600ABF4C9 /* OCDataRenderer.m in Sources */, DC434D0F20D68C3000740056 /* NSString+OCPath.m in Sources */, DCC8F9F32028559600EB6701 /* OCItem.m in Sources */, DCA35D4E24CF685B00DBE2B0 /* OCDiagnosticNode.m in Sources */, - DCDD9B19222989E50052A001 /* OCRecipient.m in Sources */, + DCB330DE29F142FB00BFF393 /* OCShareRole+OCDataItem.m in Sources */, + DCDD9B19222989E50052A001 /* OCIdentity.m in Sources */, DCADC04E2072D54200DB8E83 /* OCSQLiteTableSchema.m in Sources */, DC35969B2240EC0A00C4D6E6 /* OCQueryCondition+Item.m in Sources */, DC708CE1214135D100FE43CA /* OCSyncActionDelete.m in Sources */, DCDB76132739D30500EE7A06 /* OCServerLocator.m in Sources */, + DCF00BF627E28A77001F2AFC /* OCDataSourceSubscription+Internal.m in Sources */, DC2D646621C3D63000EB26FD /* OCCore+Thumbnails.m in Sources */, DC188998218B09CC00CFB3F9 /* OCLogFileSource.m in Sources */, DC72E42F2063DBF900189B9A /* OCClassSettingsFlatSourceManagedConfiguration.m in Sources */, + DC0CE18C28C63B2E009ABDFB /* OCAppProviderFileType.m in Sources */, DC66C6B520540DBD00189B9A /* NSDate+OCDateParser.m in Sources */, DC6DEEB224C5990D00E3772E /* OCHTTPPolicy+PipelinePolicyHandler.m in Sources */, DC179CD2209475C20018DF7F /* UIImage+OCTools.m in Sources */, + DCDBB5FC25248B0F00FAD707 /* OCResourceRequest.m in Sources */, DC2AA57122DD1339001D5C39 /* OCItemPolicyProcessorAvailableOffline.m in Sources */, DC14CC4B21067320006DDA69 /* OCCore+ItemList.m in Sources */, DCDB76252739D51200EE7A06 /* OCServerLocatorLookupTable.m in Sources */, @@ -4133,13 +5419,21 @@ DCD7AA452580E5A5000CD155 /* NSURLSessionTask+Debug.m in Sources */, DC6BFFF723206215005FA5CB /* OCEventQueue.m in Sources */, DC39DC4C2041A2FB00189B9A /* NSError+OCError.m in Sources */, + DC9219EB2964CB4500F538EE /* GAEducationOrganization.m in Sources */, + DC9C19E327839E440021222E /* OCResourceSourceStorage.m in Sources */, DC5A794F21E5FAF20045BCAA /* OCConnection+Signals.m in Sources */, + DC9219EC2964CB4500F538EE /* GAAppRoleAssignment.m in Sources */, DC241E6F229549E200AEE068 /* OCAuthenticationMethodOpenIDConnect.m in Sources */, DC6CC30826428DD50040ECAC /* OCAuthenticationBrowserSessionCustomScheme.m in Sources */, DC6CF8252195D7C90013B9F9 /* OCSyncActionUpdate.m in Sources */, + DC47E4E727A5820D0020E8EF /* GADirectoryObject.m in Sources */, DCC599F522EEE65700499B29 /* OCCore+Claims.m in Sources */, + DC47E4DA27A5820D0020E8EF /* GAOpenGraphFile.m in Sources */, + DC47E4D027A5820D0020E8EF /* GARoot.m in Sources */, + DC47E4F127A7E2050020E8EF /* OCDrive.m in Sources */, DC19BFDF21CB99D1007C20D1 /* OCIssue+SyncIssue.m in Sources */, DC3CE0492429FCDF00AB8B88 /* OCMessageQueue.m in Sources */, + DCE2F04427FB928B00E9E136 /* NSArray+OCFiltering.m in Sources */, DC5966A32276DB5D004CB28D /* OCSyncLane.m in Sources */, DCC8FA162029EB9400EB6701 /* OCHTTPRequest.m in Sources */, ); @@ -4149,6 +5443,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DCF00C1927E698A4001F2AFC /* DataSourceTests.m in Sources */, DC6DB88221C26F4D00189B21 /* XCTestCase+Tagging.m in Sources */, DC594B1D21EFD25100B882C4 /* CoreManagerTests.m in Sources */, DC72E4212063BE9400189B9A /* HostSimulatorTests.m in Sources */, @@ -4299,7 +5594,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4326,7 +5620,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4347,7 +5640,6 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = Example/Ocean/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4366,7 +5658,6 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = Example/Ocean/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4391,7 +5682,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudMocking/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4418,7 +5708,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ownCloudMocking/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4437,8 +5726,8 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = ownCloudMockingTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4457,8 +5746,8 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4AP2STM4H5; INFOPLIST_FILE = ownCloudMockingTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4521,7 +5810,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -4531,7 +5820,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - TVOS_DEPLOYMENT_TARGET = 12.0; + TVOS_DEPLOYMENT_TARGET = 15.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4582,7 +5871,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -4591,7 +5880,7 @@ ); MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - TVOS_DEPLOYMENT_TARGET = 12.0; + TVOS_DEPLOYMENT_TARGET = 15.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4614,7 +5903,6 @@ GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; INFOPLIST_FILE = ownCloudSDK/Resources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4625,7 +5913,6 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos"; TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; @@ -4644,7 +5931,6 @@ GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; INFOPLIST_FILE = ownCloudSDK/Resources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4655,7 +5941,6 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos"; TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Release; }; diff --git a/ownCloudSDK.xcodeproj/xcshareddata/xcschemes/Ocean.xcscheme b/ownCloudSDK.xcodeproj/xcshareddata/xcschemes/Ocean.xcscheme index ad3da91b..abbb2641 100644 --- a/ownCloudSDK.xcodeproj/xcshareddata/xcschemes/Ocean.xcscheme +++ b/ownCloudSDK.xcodeproj/xcshareddata/xcschemes/Ocean.xcscheme @@ -1,6 +1,6 @@ . + * + */ + +#import +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCAction; + +typedef NSString* OCActionIdentifier; +typedef NSString* OCActionVersion; + +typedef NSString* OCActionPropertyKey NS_TYPED_ENUM; +typedef NSDictionary* OCActionProperties; + +typedef NSString* OCActionRunOptionKey NS_TYPED_ENUM; +typedef NSDictionary* OCActionRunOptions; + +typedef void(^OCActionBlock)(OCAction *action, OCActionRunOptions _Nullable options, void(^completionHandler)(NSError * _Nullable error)); + +typedef NS_ENUM(NSInteger, OCActionType) { + OCActionTypeRegular, + OCActionTypeWarning, + OCActionTypeDestructive +} __attribute__((enum_extensibility(closed))); + +@interface OCAction : NSObject + +@property(strong, nullable) OCActionIdentifier identifier; //!< If set, returned as .dataItemReference. +@property(strong, nullable) OCActionVersion version; //!< If set, returned as .dataItemVersion. + +@property(assign) OCActionType type; + +@property(strong) OCActionProperties properties; + +@property(strong) NSString *title; +@property(strong, nullable) UIImage *icon; + +@property(copy, nullable) OCActionBlock actionBlock; + +- (instancetype)initWithTitle:(NSString *)title icon:(nullable UIImage *)icon action:(nullable OCActionBlock)actionBlock; + +- (void)runActionWithOptions:(nullable OCActionRunOptions)options completionHandler:(nullable void(^)(NSError * _Nullable error))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Actions/OCAction.m b/ownCloudSDK/Actions/OCAction.m new file mode 100644 index 00000000..0d6b6814 --- /dev/null +++ b/ownCloudSDK/Actions/OCAction.m @@ -0,0 +1,86 @@ +// +// OCAction.m +// ownCloudSDK +// +// Created by Felix Schwarz on 29.05.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 "OCAction.h" + +@interface OCAction () +{ + OCDataItemReference _reference; +} +@end + +@implementation OCAction + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _reference = NSUUID.UUID.UUIDString; + _properties = [NSMutableDictionary new]; + } + + return (self); +} + +- (instancetype)initWithTitle:(NSString *)title icon:(nullable UIImage *)icon action:(nullable OCActionBlock)actionBlock +{ + if ((self = [self init]) != nil) + { + _title = title; + _icon = icon; + self.actionBlock = actionBlock; + } + + return (self); +} + +- (void)runActionWithOptions:(OCActionRunOptions)options completionHandler:(void(^)(NSError * _Nullable error))completionHandler +{ + if (completionHandler == nil) + { + // Provide dummy completionHandler if none is provided + completionHandler = ^(NSError *error){ + }; + } + + if (_actionBlock != nil) + { + _actionBlock(self, options, completionHandler); + } + else + { + completionHandler(nil); + } +} + +- (OCDataItemReference)dataItemReference +{ + return ((_identifier != nil) ? _identifier : _reference); +} + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeAction); +} + +- (OCDataItemVersion)dataItemVersion +{ + return ((_version != nil) ? _version : _reference); +} + +@end diff --git a/ownCloudSDK/Activity/OCActivityUpdate.h b/ownCloudSDK/Activity/OCActivityUpdate.h index 70dfb3c7..ac4ae696 100644 --- a/ownCloudSDK/Activity/OCActivityUpdate.h +++ b/ownCloudSDK/Activity/OCActivityUpdate.h @@ -26,7 +26,7 @@ typedef NS_ENUM(NSUInteger,OCActivityUpdateType) OCActivityUpdateTypePublish, OCActivityUpdateTypeProperty, OCActivityUpdateTypeUnpublish -}; +} __attribute__((enum_extensibility(closed))); @interface OCActivityUpdate : NSObject { diff --git a/ownCloudSDK/App Identity/OCAppIdentity.h b/ownCloudSDK/App Identity/OCAppIdentity.h index 16572615..e5b6ac68 100644 --- a/ownCloudSDK/App Identity/OCAppIdentity.h +++ b/ownCloudSDK/App Identity/OCAppIdentity.h @@ -95,6 +95,7 @@ typedef NSString* OCAppComponentIdentifier NS_TYPED_ENUM; extern OCAppComponentIdentifier OCAppComponentIdentifierApp; extern OCAppComponentIdentifier OCAppComponentIdentifierFileProviderExtension; +extern OCAppComponentIdentifier OCAppComponentIdentifierFileProviderUIExtension; extern OCAppComponentIdentifier OCAppComponentIdentifierShareExtension; NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/App Identity/OCAppIdentity.m b/ownCloudSDK/App Identity/OCAppIdentity.m index 0b39b089..8e18d513 100644 --- a/ownCloudSDK/App Identity/OCAppIdentity.m +++ b/ownCloudSDK/App Identity/OCAppIdentity.m @@ -63,8 +63,6 @@ - (instancetype)init self.appGroupIdentifier = bundleInfoDictionary[@"OCAppGroupIdentifier"]; _componentIdentifier = bundleInfoDictionary[@"OCAppComponentIdentifier"]; - - self.appGroupContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[self appGroupIdentifier]]; } } @@ -83,6 +81,14 @@ - (NSURL *)appGroupContainerURL if (_appGroupContainerURL == nil) { _appGroupContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:self.appGroupIdentifier]; + + if (_appGroupContainerURL == nil) + { + NSError *error = nil; + _appGroupContainerURL = [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:true error:&error]; + + OCLogWarning(@"Could not get appGroupContainerURL for groupID=%@, using app's documents folder as group container instead: %@ (error=%@)", self.appGroupIdentifier, _appGroupContainerURL, error); + } } return (_appGroupContainerURL); @@ -92,8 +98,8 @@ - (NSURL *)appGroupLogsContainerURL { if (_appGroupLogsContainerURL == nil) { - _appGroupLogsContainerURL = [[self appGroupContainerURL] URLByAppendingPathComponent:@"logs"]; - if (![[NSFileManager defaultManager] fileExistsAtPath:[_appGroupLogsContainerURL path]]) + _appGroupLogsContainerURL = [self.appGroupContainerURL URLByAppendingPathComponent:@"logs"]; + if (![[NSFileManager defaultManager] fileExistsAtPath:_appGroupLogsContainerURL.path]) { [[NSFileManager defaultManager] createDirectoryAtURL:_appGroupLogsContainerURL withIntermediateDirectories:NO @@ -241,4 +247,5 @@ + (BOOL)supportsFileProvider OCAppComponentIdentifier OCAppComponentIdentifierApp = @"app"; OCAppComponentIdentifier OCAppComponentIdentifierFileProviderExtension = @"fileProviderExtension"; +OCAppComponentIdentifier OCAppComponentIdentifierFileProviderUIExtension = @"fileProviderUIExtension"; OCAppComponentIdentifier OCAppComponentIdentifierShareExtension = @"shareExtension"; diff --git a/ownCloudSDK/App Providers/OCAppProvider.h b/ownCloudSDK/App Providers/OCAppProvider.h new file mode 100644 index 00000000..c97c6f39 --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProvider.h @@ -0,0 +1,62 @@ +// +// OCAppProvider.h +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 + +typedef NSString* OCAppProviderAPIVersion; +typedef NSString* OCAppProviderKey NS_TYPED_ENUM; +typedef NSArray *> *OCAppProviderAppList; + +@class OCAppProviderApp; +@class OCAppProviderFileType; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCAppProvider : NSObject + +@property(assign) BOOL enabled; + +@property(strong,nullable) OCAppProviderAPIVersion version; +@property(nonatomic,assign) BOOL isSupported; //!< Computed property indicating if the API version is supported by the SDK + +@property(strong,nullable) NSString *appsURLPath; //!< relative URL providing a list of available apps and their corresponding types +@property(strong,nullable) NSString *openURLPath; //!< relative URL to open a file (GET/POST/…) +@property(strong,nullable) NSString *openWebURLPath; //!< relative URL to open a file using web (GET only) +@property(strong,nullable) NSString *createURLPath; //!< relative URL to create a new file (newURL, needs to be named createURL due to compiler-enforced Cocoa naming conventions) + +@property(strong,nullable,nonatomic) OCAppProviderAppList appList; //!< Raw app list as returned from the server. Setting this property parses the list and generates OCAppProviderApp and OCAppProviderFileType instances + +@property(strong,nullable,readonly) NSArray *apps; //!< OCAppProviderApp instances created from the raw .appList +@property(strong,nullable,readonly) NSArray *types; //!< OCAppProviderFileType instances created from the raw .appList + +@end + +// Keys as described at https://owncloud.dev/services/app-registry/apps/#listing-available-apps--mime-types +extern OCAppProviderKey OCAppProviderKeyMIMEType; //!< corresponds to key "mime_type" (string) +extern OCAppProviderKey OCAppProviderKeyExtension; //!< corresponds to key "ext" (string) +extern OCAppProviderKey OCAppProviderKeyName; //!< corresponds to key "name" (string) +extern OCAppProviderKey OCAppProviderKeyIcon; //!< corresponds to key "icon" (string) +extern OCAppProviderKey OCAppProviderKeyDescription; //!< corresponds to key "description" (string) +extern OCAppProviderKey OCAppProviderKeyAllowCreation; //!< corresponds to key "allow_creation" (bool) +extern OCAppProviderKey OCAppProviderKeyDefaultApplication; //!< corresponds to key "default_application" (string) +extern OCAppProviderKey OCAppProviderKeyAppProviders; //!< corresponds to key "app_providers" (array of dictionaries) + +extern OCAppProviderKey OCAppProviderKeyAppProviderName; //!< corresponds to key app_providers[].name (string) +extern OCAppProviderKey OCAppProviderKeyAppProviderIcon; //!< corresponds to key app_providers[].icon (URL string) + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/App Providers/OCAppProvider.m b/ownCloudSDK/App Providers/OCAppProvider.m new file mode 100644 index 00000000..7a8a340b --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProvider.m @@ -0,0 +1,163 @@ +// +// OCAppProvider.m +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCAppProvider.h" +#import "OCMacros.h" +#import "OCAppProviderApp.h" +#import "OCAppProviderFileType.h" +#import "NSArray+OCFiltering.h" + +@implementation OCAppProvider + +- (BOOL)isSupported +{ + return ( + // Require version 1.1.X + [self.version hasPrefix:@"1.1."] && + + // Require open_web_url + (self.openWebURLPath != nil) && + + // Require new_url + (self.createURLPath != nil) + ); +} + +- (void)setAppList:(OCAppProviderAppList)inAppList +{ + OCAppProviderAppList newAppList; + + // Response format documented at https://owncloud.dev/services/app-registry/apps/ + if ((newAppList = OCTypedCast(inAppList,NSArray)) != nil) + { + NSMutableArray *apps = [NSMutableArray new]; + NSMutableArray *types = [NSMutableArray new]; + + for (NSDictionary *inAppDict in newAppList) + { + NSDictionary *appDict; + + if ((appDict = OCTypedCast(inAppDict, NSDictionary)) != nil) + { + OCMIMEType typeMIMEType = OCTypedCast(appDict[OCAppProviderKeyMIMEType], NSString); + OCFileExtension typeExtension = OCTypedCast(appDict[OCAppProviderKeyExtension], NSString); + NSString *typeName = OCTypedCast(appDict[OCAppProviderKeyName], NSString); + NSString *typeIconURLString = OCTypedCast(appDict[OCAppProviderKeyIcon], NSString); + NSString *typeDescription = OCTypedCast(appDict[OCAppProviderKeyDescription], NSString); + NSNumber *typeAllowCreation = OCTypedCast(appDict[OCAppProviderKeyAllowCreation], NSNumber); + OCAppProviderAppName typeDefaultAppName = OCTypedCast(appDict[OCAppProviderKeyDefaultApplication], NSString); + NSArray *> *typeAppProviders = OCTypedCast(appDict[OCAppProviderKeyAppProviders], NSArray); + + if ((typeMIMEType != nil) && (typeAppProviders.count > 0)) + { + OCAppProviderFileType *type = [OCAppProviderFileType new]; + + type.provider = self; + + type.mimeType = typeMIMEType; + type.extension = typeExtension; + type.name = typeName; + if (typeIconURLString != nil) + { + type.iconURL = [[NSURL alloc] initWithString:typeIconURLString]; + } + type.typeDescription = typeDescription; + type.allowCreation = typeAllowCreation.boolValue; + type.defaultAppName = typeDefaultAppName; + + for (NSDictionary *inAppProviderDict in typeAppProviders) + { + NSDictionary *appProviderDict; + + if ((appProviderDict = OCTypedCast(inAppProviderDict, NSDictionary)) != nil) + { + OCAppProviderAppName appName = OCTypedCast(appProviderDict[OCAppProviderKeyAppProviderName], NSString); + NSString *appIconURLString = OCTypedCast(appProviderDict[OCAppProviderKeyAppProviderIcon], NSString); + NSURL *appIconURL = (appIconURLString != nil) ? [[NSURL alloc] initWithString:appIconURLString] : nil; + OCAppProviderApp *app = nil; + + if (appName != nil) + { + // Look for app with same name and iconURL + app = [apps firstObjectMatching:^BOOL(OCAppProviderApp * _Nonnull existingApp) { + return ([existingApp.name isEqual:appName] && [existingApp.iconURL isEqual:appIconURL]); + }]; + } + + if (app == nil) + { + // Create new app + app = [OCAppProviderApp new]; + + app.provider = self; + + app.name = appName; + app.iconURL = appIconURL; + + [apps addObject:app]; + } + + if ((typeDefaultAppName != nil) && [app.name isEqual:typeDefaultAppName]) + { + type.defaultApp = app; + } + + [app addSupportedType:type]; + } + } + + [types addObject:type]; + } + } + } + + _apps = apps; + _types = types; + _appList = newAppList; + } +} + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p, enabled: %d%@, supported: %d%@%@%@%@%@%@>", NSStringFromClass(self.class), self, + self.enabled, + OCExpandVar(version), + self.isSupported, + OCExpandVar(appsURLPath), + OCExpandVar(openURLPath), + OCExpandVar(openWebURLPath), + OCExpandVar(createURLPath), + OCExpandVar(apps), + OCExpandVar(types) + ]); +} + +@end + +OCAppProviderKey OCAppProviderKeyMIMEType = @"mime_type"; +OCAppProviderKey OCAppProviderKeyExtension = @"ext"; +OCAppProviderKey OCAppProviderKeyName = @"name"; +OCAppProviderKey OCAppProviderKeyIcon = @"icon"; +OCAppProviderKey OCAppProviderKeyDescription = @"description"; +OCAppProviderKey OCAppProviderKeyAllowCreation = @"allow_creation"; +OCAppProviderKey OCAppProviderKeyDefaultApplication = @"default_application"; +OCAppProviderKey OCAppProviderKeyAppProviders = @"app_providers"; + +OCAppProviderKey OCAppProviderKeyAppProviderName = @"name"; +OCAppProviderKey OCAppProviderKeyAppProviderIcon = @"icon"; diff --git a/ownCloudSDK/App Providers/OCAppProviderApp.h b/ownCloudSDK/App Providers/OCAppProviderApp.h new file mode 100644 index 00000000..b068f1db --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProviderApp.h @@ -0,0 +1,52 @@ +// +// OCAppProviderApp.h +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCAppProviderFileType.h" +#import "OCItem.h" +#import "OCResourceRequest.h" + +@class OCAppProvider; + +typedef NSString* OCAppProviderViewMode NS_TYPED_ENUM; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCAppProviderApp : NSObject + +@property(weak,nullable) OCAppProvider *provider; + +@property(strong,nullable) OCAppProviderAppName name; +@property(strong,nullable) NSURL *iconURL; + +@property(strong,nullable,nonatomic,readonly) OCResourceRequest *iconResourceRequest; +@property(strong,nullable,nonatomic,readonly) UIImage *icon; + +@property(strong,nullable) NSArray *supportedTypes; + +- (void)addSupportedType:(OCAppProviderFileType *)type; + +- (BOOL)supportsItem:(OCItem *)item; + +@end + +extern OCAppProviderViewMode OCAppProviderViewModeView; //!< user can view in the opening app (download is not possible) +extern OCAppProviderViewMode OCAppProviderViewModeRead; //!< user can view and download from the opening app +extern OCAppProviderViewMode OCAppProviderViewModeWrite; //!< user can edit and download in the opening app + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/App Providers/OCAppProviderApp.m b/ownCloudSDK/App Providers/OCAppProviderApp.m new file mode 100644 index 00000000..c2bb46f6 --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProviderApp.m @@ -0,0 +1,103 @@ +// +// OCAppProviderApp.m +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCAppProviderApp.h" +#import "OCAppProviderFileType.h" +#import "OCMacros.h" +#import "OCResourceRequestURLItem.h" + +@interface OCAppProviderApp () +{ + OCResourceRequest *_iconResourceRequest; +} +@end + +@implementation OCAppProviderApp + +- (void)addSupportedType:(OCAppProviderFileType *)type +{ + if (_supportedTypes == nil) + { + _supportedTypes = [NSMutableArray new]; + } + + NSMutableArray *types = nil; + + if ((types = OCTypedCast(_supportedTypes, NSMutableArray)) == nil) + { + NSMutableArray *mutableTypes = (_supportedTypes != nil) ? [[NSMutableArray alloc] initWithArray:_supportedTypes] : [NSMutableArray new]; + + types = mutableTypes; + _supportedTypes = mutableTypes; + } + + [types addObject:type]; +} + +- (BOOL)supportsItem:(OCItem *)item +{ + OCMIMEType itemMimeType = item.mimeType.lowercaseString; + OCFileExtension itemFileExtension = item.name.pathExtension.lowercaseString; + + if (itemFileExtension.length == 0) + { + itemFileExtension = nil; + } + + for (OCAppProviderFileType *fileType in _supportedTypes) + { + if ([fileType.mimeType.lowercaseString isEqual:itemMimeType] || + [fileType.extension.lowercaseString isEqual:itemFileExtension]) + { + return (YES); + } + } + + return (NO); +} + +- (OCResourceRequest *)iconResourceRequest +{ + if ((_iconResourceRequest == nil) && (_iconURL != nil)) + { + _iconResourceRequest = [OCResourceRequestURLItem requestURLItem:_iconURL identifier:nil version:OCResourceRequestURLItem.daySpecificVersion structureDescription:@"icon" waitForConnectivity:YES changeHandler:nil]; + } + + return (_iconResourceRequest); +} + +- (UIImage *)icon +{ + return (OCTypedCast(self.iconResourceRequest.resource, OCResourceImage).image.image); +} + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, + OCExpandVar(name), + OCExpandVar(iconURL), + OCExpandVar(supportedTypes) + ]); +} + +@end + +OCAppProviderViewMode OCAppProviderViewModeView = @"view"; +OCAppProviderViewMode OCAppProviderViewModeRead = @"read"; +OCAppProviderViewMode OCAppProviderViewModeWrite = @"write"; diff --git a/ownCloudSDK/App Providers/OCAppProviderFileType.h b/ownCloudSDK/App Providers/OCAppProviderFileType.h new file mode 100644 index 00000000..1af20bfe --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProviderFileType.h @@ -0,0 +1,50 @@ +// +// OCAppProviderFileType.h +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCTypes.h" +#import "OCResourceRequest.h" + +@class OCAppProvider; +@class OCAppProviderApp; + +typedef NSString* OCAppProviderAppName; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCAppProviderFileType : NSObject + +@property(weak,nullable) OCAppProvider *provider; + +@property(strong,nullable) OCMIMEType mimeType; +@property(strong,nullable) OCFileExtension extension; + +@property(strong,nullable) NSString *name; +@property(strong,nullable) NSURL *iconURL; +@property(strong,nullable) NSString *typeDescription; + +@property(strong,nullable,nonatomic,readonly) OCResourceRequest *iconResourceRequest; +@property(strong,nullable,nonatomic,readonly) UIImage *icon; + +@property(assign) BOOL allowCreation; +@property(strong,nullable) OCAppProviderAppName defaultAppName; +@property(weak,nullable) OCAppProviderApp *defaultApp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/App Providers/OCAppProviderFileType.m b/ownCloudSDK/App Providers/OCAppProviderFileType.m new file mode 100644 index 00000000..5f1cb6dc --- /dev/null +++ b/ownCloudSDK/App Providers/OCAppProviderFileType.m @@ -0,0 +1,60 @@ +// +// OCAppProviderFileType.m +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCAppProviderFileType.h" +#import "OCMacros.h" +#import "OCResourceRequestURLItem.h" + +@interface OCAppProviderFileType () +{ + OCResourceRequest *_iconResourceRequest; +} +@end + +@implementation OCAppProviderFileType + +- (OCResourceRequest *)iconResourceRequest +{ + if ((_iconResourceRequest == nil) && (_iconURL != nil)) + { + _iconResourceRequest = [OCResourceRequestURLItem requestURLItem:_iconURL identifier:nil version:OCResourceRequestURLItem.weekSpecificVersion structureDescription:@"icon" waitForConnectivity:YES changeHandler:nil]; + } + + return (_iconResourceRequest); +} + +- (UIImage *)icon +{ + return (OCTypedCast(self.iconResourceRequest.resource, OCResourceImage).image.image); +} + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@, allowCreation: %d>", NSStringFromClass(self.class), self, + OCExpandVar(mimeType), + OCExpandVar(extension), + OCExpandVar(name), + OCExpandVar(iconURL), + OCExpandVar(typeDescription), + OCExpandVar(defaultAppName), + _allowCreation + ]); +} + +@end diff --git a/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.h b/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.h index 323005f0..fbf6fafa 100644 --- a/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.h +++ b/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.h @@ -30,6 +30,7 @@ typedef dispatch_block_t _Nullable (^OCAuthenticationBrowserSessionCustomSchemeB @property(class,nonatomic,copy,nullable) OCAuthenticationBrowserSessionCustomSchemeBusyPresenter busyPresenter; + (BOOL)handleOpenURL:(NSURL *)url; ++ (BOOL)openURL:(NSURL*)url options:(NSDictionary *)options completionHandler:(void (^ __nullable)(BOOL success))completion; @property(readonly,strong,nonatomic,nullable) NSString *plainCustomScheme; @property(readonly,strong,nonatomic,nullable) NSString *secureCustomScheme; diff --git a/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.m b/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.m index bc529836..7b3e0563 100644 --- a/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.m +++ b/ownCloudSDK/Authentication/Browser Session/Custom Scheme/OCAuthenticationBrowserSessionCustomScheme.m @@ -98,6 +98,23 @@ + (BOOL)handleOpenURL:(NSURL *)url return (NO); } ++ (BOOL)openURL:(NSURL*)url options:(NSDictionary *)options completionHandler:(void (^ __nullable)(BOOL success))completion +{ + Class uiApplicationClass = NSClassFromString(@"UIApplication"); + id sharedApplication = [uiApplicationClass valueForKey:@"sharedApplication"]; + + if (sharedApplication != nil) + { + dispatch_async(dispatch_get_main_queue(), ^{ // Make this thread-safe + [sharedApplication openURL:url options:options completionHandler:completion]; + }); + + return (YES); + } + + return (NO); +} + - (NSString *)plainCustomScheme { return ([self classSettingForOCClassSettingsKey:OCClassSettingsKeyItemBrowserSessionCustomSchemePlain]); diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.h b/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.h index 19d0e0a8..d9c429cd 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.h +++ b/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.h @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN @interface OCAuthenticationMethod (OCTools) ++ (nullable NSString *)localizedNameForAuthenticationMethodIdentifier:(OCAuthenticationMethodIdentifier)identifier; + + (NSString *)basicAuthorizationValueForUsername:(NSString *)username passphrase:(NSString *)passPhrase; + (nullable NSArray *)detectionRequestsBasedOnWWWAuthenticateMethod:(NSString *)wwwAuthenticateMethod forConnection:(OCConnection *)connection; diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.m b/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.m index c33068dc..b04fbeb8 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.m +++ b/ownCloudSDK/Authentication/OCAuthenticationMethod+OCTools.m @@ -20,6 +20,11 @@ @implementation OCAuthenticationMethod (OCTools) ++ (nullable NSString *)localizedNameForAuthenticationMethodIdentifier:(OCAuthenticationMethodIdentifier)identifier +{ + return ([OCAuthenticationMethod registeredAuthenticationMethodForIdentifier:identifier].name); +} + + (NSString *)basicAuthorizationValueForUsername:(NSString *)username passphrase:(NSString *)passPhrase { return ([NSString stringWithFormat:@"Basic %@", [[[NSString stringWithFormat:@"%@:%@", username, passPhrase] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]]); diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethod.h b/ownCloudSDK/Authentication/OCAuthenticationMethod.h index 903ae374..d91a2f67 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethod.h +++ b/ownCloudSDK/Authentication/OCAuthenticationMethod.h @@ -40,7 +40,7 @@ typedef NS_ENUM(NSUInteger, OCAuthenticationMethodType) { OCAuthenticationMethodTypePassphrase, //!< Authentication method is password based (=> UI should show username and password entry field) OCAuthenticationMethodTypeToken //!< Authentication method is token based (=> UI should show no username and password entry field) -}; +} __attribute__((enum_extensibility(closed))); @interface OCAuthenticationMethod : NSObject { @@ -71,8 +71,8 @@ typedef NS_ENUM(NSUInteger, OCAuthenticationMethodType) @property(readonly,nonatomic,nullable) NSDate *authenticationDataKnownInvalidDate; //!< The date the .authenticationData was last known to be invalid. Reset to nil when -flushCachedAuthenticationSecret is called. #pragma mark - Authentication Method Detection -+ (nullable NSArray *)detectionRequestsForConnection:(OCConnection *)connection; //!< Provides a list of URLs whose content is needed to determine whether this authentication method is supported -+ (void)detectAuthenticationMethodSupportForConnection:(OCConnection *)connection withServerResponses:(NSDictionary *)serverResponses options:(OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(OCAuthenticationMethodIdentifier identifier, BOOL supported))completionHandler; //!< Detects authentication method support using collected responses (for URL provided by -detectionRequestsForConnection:) and then returns result via the completionHandler. ++ (nullable NSArray *)detectionRequestsForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options; //!< Provides a list of URLs whose content is needed to determine whether this authentication method is supported ++ (void)detectAuthenticationMethodSupportForConnection:(OCConnection *)connection withServerResponses:(NSDictionary *)serverResponses options:(OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(OCAuthenticationMethodIdentifier identifier, BOOL supported))completionHandler; //!< Detects authentication method support using collected responses (for URL provided by -detectionRequestsForConnection:options:) and then returns result via the completionHandler. #pragma mark - Authentication Data ID computation + (nullable OCAuthenticationDataID)authenticationDataIDForAuthenticationData:(nullable NSData *)data; //!< Returns the OCAuthenticationDataID for the passed authenticationData (usually from OCBookmark). @@ -113,6 +113,11 @@ extern OCAuthenticationMethodKey OCAuthenticationMethodPassphraseKey; //!< For p extern OCAuthenticationMethodKey OCAuthenticationMethodPresentingViewControllerKey; //!< The UIViewController to use when presenting a view controller (for f.ex. token-based authentication mechanisms like OAuth2) (value type: UIViewController*) extern OCAuthenticationMethodKey OCAuthenticationMethodAllowURLProtocolUpgradesKey; //!< Allow OCConnection to modify the OCBookmark with an upgraded URL, where an upgrade means that the hostname and base path must be identical and the protocol may only change from http to https. Any other change will be rejected and produce an OCErrorAuthorizationRedirect with userInfo[OCAuthorizationMethodAlternativeServerURLKey] for user approval (=> if the user approves, the app would update the URL in the bookmark accordingly and start authentication anew). This key is currently supported for extern OCAuthenticationMethodKey OCAuthenticationMethodRequiredUsernameKey; //!< For token-based authentication methods: only generate bookmark data tokens if they allow logging in as the provided username / user ID. Return an OCErrorAuthorizationNotMatchingRequiredUserID error otherwise. +extern OCAuthenticationMethodKey OCAuthenticationMethodWebFingerAccountLookupURLKey; //!< If a WebFinger lookup provided the base URL for auth detection, this contains the URL from which an authenticated request can lookup available servers and other information. +extern OCAuthenticationMethodKey OCAuthenticationMethodWebFingerAlternativeIDPKey; //!< If a WebFinger lookup provided the base URL for auth detection, that URL is provided here. +extern OCAuthenticationMethodKey OCAuthenticationMethodAuthenticationRefererURL; //!< If a WebFinger lookup provided the IDPs URL, the URL of the WebFinger server to be passed as Referer to the IDP, so an IDP can know what instance it is being asked to authenticate for - and can reject authentication if necessary. +extern OCAuthenticationMethodKey OCAuthenticationMethodSkipWWWAuthenticateChecksKey; //!< If provided and true, skips WWW-Authenticate checks of the WebDAV endpoint when checking for available authentication methods. +extern OCAuthenticationMethodKey OCAuthenticationMethodAllowedMethods; //!< If provided, an array of allowed OCAuthenticationMethodIdentifiers extern NSString *OCAuthorizationMethodAlternativeServerURLKey; //!< Key for alternative server URL in -[NSError userInfo]. extern NSString *OCAuthorizationMethodAlternativeServerURLOriginURLKey; //!< Key for the URL from where the alternative server URL was requested. diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethod.m b/ownCloudSDK/Authentication/OCAuthenticationMethod.m index 72d288d2..59c156f2 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethod.m +++ b/ownCloudSDK/Authentication/OCAuthenticationMethod.m @@ -173,7 +173,7 @@ - (void)_authenticationDataChangedRemotely:(BOOL)remotely } #pragma mark - Authentication Method Detection -+ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection ++ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options { return(nil); } @@ -392,6 +392,11 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCAuthenticationMethodKey OCAuthenticationMethodPresentingViewControllerKey = @"presentingViewController"; OCAuthenticationMethodKey OCAuthenticationMethodAllowURLProtocolUpgradesKey = @"allowURLProtocolUpgrades"; OCAuthenticationMethodKey OCAuthenticationMethodRequiredUsernameKey = @"requiredUsername"; +OCAuthenticationMethodKey OCAuthenticationMethodWebFingerAccountLookupURLKey = @"webFingerAccountLookupURL"; +OCAuthenticationMethodKey OCAuthenticationMethodWebFingerAlternativeIDPKey = @"webFingerAlternativeIDP"; +OCAuthenticationMethodKey OCAuthenticationMethodAuthenticationRefererURL = @"authenticationRefererURL"; +OCAuthenticationMethodKey OCAuthenticationMethodSkipWWWAuthenticateChecksKey = @"skipWWWAuthenticateChecks"; +OCAuthenticationMethodKey OCAuthenticationMethodAllowedMethods = @"allowedMethods"; NSString *OCAuthorizationMethodAlternativeServerURLKey = @"alternativeServerURL"; NSString *OCAuthorizationMethodAlternativeServerURLOriginURLKey = @"alternativeServerURLOriginURL"; diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethodBasicAuth.m b/ownCloudSDK/Authentication/OCAuthenticationMethodBasicAuth.m index 70eff0e9..4d49f7ae 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethodBasicAuth.m +++ b/ownCloudSDK/Authentication/OCAuthenticationMethodBasicAuth.m @@ -20,6 +20,7 @@ #import "OCAuthenticationMethod+OCTools.h" #import "NSError+OCError.h" #import "OCHTTPRequest.h" +#import "OCMacros.h" static NSString *OCAuthenticationMethodBasicAuthAuthenticationHeaderValueKey = @"BasicAuthString"; @@ -123,14 +124,27 @@ + (NSString *)passPhraseFromAuthenticationData:(NSData *)authenticationData } #pragma mark - Authentication Method Detection -+ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection ++ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options { + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey], NSNumber).boolValue) + { + // Skip if WWW-Authenticate checks are not allowed + return @[]; + } + return ([self detectionRequestsBasedOnWWWAuthenticateMethod:@"Basic" forConnection:connection]); } + (void)detectAuthenticationMethodSupportForConnection:(OCConnection *)connection withServerResponses:(NSDictionary *)serverResponses options:(OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(OCAuthenticationMethodIdentifier identifier, BOOL supported))completionHandler { - return ([self detectAuthenticationMethodSupportBasedOnWWWAuthenticateMethod:@"Basic" forConnection:connection withServerResponses:serverResponses completionHandler:completionHandler]); + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey], NSNumber).boolValue) + { + // Skip if WWW-Authenticate checks are not allowed + completionHandler(self.identifier, NO); + return; + } + + [self detectAuthenticationMethodSupportBasedOnWWWAuthenticateMethod:@"Basic" forConnection:connection withServerResponses:serverResponses completionHandler:completionHandler]; } #pragma mark - Authentication / Deauthentication ("Login / Logout") diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.h b/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.h index acfa1eb4..f4ec1ea8 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.h +++ b/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.h @@ -39,13 +39,13 @@ typedef NS_ENUM(NSInteger, OCAuthenticationOAuth2TokenRequestType) @property(strong,nullable) OCPKCE *pkce; //!< pre-configured PKCE object to use for Proof Key for Code Exchange #pragma mark - Subclassing points -- (nullable NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection; -- (nullable NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection; +- (nullable NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options; +- (nullable NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options; - (NSString *)redirectURIForConnection:(OCConnection *)connection; - (NSDictionary *)prepareAuthorizationRequestParameters:(NSDictionary *)parameters forConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions)options; - (NSDictionary *)tokenRefreshParametersForRefreshToken:(NSString *)refreshToken connection:(OCConnection *)connection; - (NSDictionary *)postProcessAuthenticationDataDict:(NSDictionary *)authDataDict; -- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection completionHandler:(void(^)(NSError *error))completionHandler; +- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(NSError * _Nullable error))completionHandler; - (nullable NSString *)scope; - (nullable NSString *)prompt; - (NSString *)clientID; diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.m b/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.m index 977f0c30..bb7618da 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.m +++ b/ownCloudSDK/Authentication/OCAuthenticationMethodOAuth2.m @@ -179,19 +179,19 @@ + (NSString *)name } #pragma mark - Subclassing points -- (NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection +- (NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options { - return ([connection URLForEndpointPath:[self classSettingForOCClassSettingsKey:OCAuthenticationMethodOAuth2AuthorizationEndpoint]]); + return ([connection URLForEndpointPath:[self classSettingForOCClassSettingsKey:OCAuthenticationMethodOAuth2AuthorizationEndpoint] withAlternativeURL:options[OCAuthenticationMethodWebFingerAlternativeIDPKey]]); } -+ (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection ++ (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options { - return ([connection URLForEndpointPath:[self classSettingForOCClassSettingsKey:OCAuthenticationMethodOAuth2TokenEndpoint]]); + return ([connection URLForEndpointPath:[self classSettingForOCClassSettingsKey:OCAuthenticationMethodOAuth2TokenEndpoint] withAlternativeURL:options[OCAuthenticationMethodWebFingerAlternativeIDPKey]]); } -- (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection +- (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options { - return ([self.class tokenEndpointURLForConnection:connection]); + return ([self.class tokenEndpointURLForConnection:connection options:options]); } - (NSString *)redirectURIForConnection:(OCConnection *)connection @@ -199,7 +199,7 @@ - (NSString *)redirectURIForConnection:(OCConnection *)connection return ([self classSettingForOCClassSettingsKey:OCAuthenticationMethodOAuth2RedirectURI]); } -- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection completionHandler:(void(^)(NSError *error))completionHandler +- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options completionHandler:(nonnull void (^)(NSError * _Nullable))completionHandler { completionHandler(OCError(OCErrorFeatureNotImplemented)); } @@ -269,10 +269,16 @@ - (NSString *)clientSecret } #pragma mark - Authentication Method Detection -+ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection ++ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options { + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey], NSNumber).boolValue) + { + // Skip if WWW-Authenticate checks are not allowed + return @[]; + } + NSArray *detectionRequests = [self detectionRequestsBasedOnWWWAuthenticateMethod:@"Bearer" forConnection:connection]; - NSURL *tokenEndpointURL = [self tokenEndpointURLForConnection:connection]; // Add token endpoint for detection / differenciation between OC-OAuth2 and other bearer-based auth methods (like OIDC) + NSURL *tokenEndpointURL = [self tokenEndpointURLForConnection:connection options:options]; // Add token endpoint for detection / differenciation between OC-OAuth2 and other bearer-based auth methods (like OIDC) detectionRequests = [detectionRequests arrayByAddingObject:[OCHTTPRequest requestWithURL:tokenEndpointURL]]; @@ -283,7 +289,14 @@ + (void)detectAuthenticationMethodSupportForConnection:(OCConnection *)connectio { NSURL *tokenEndpointURL; - if ((tokenEndpointURL = [self tokenEndpointURLForConnection:connection]) != nil) + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey], NSNumber).boolValue) + { + // Skip if WWW-Authenticate checks are not allowed + completionHandler(self.identifier, NO); + return; + } + + if ((tokenEndpointURL = [self tokenEndpointURLForConnection:connection options:options]) != nil) { OCHTTPRequest *tokenEndpointRequest; @@ -381,7 +394,7 @@ - (void)generateBookmarkAuthenticationDataWithConnection:(OCConnection *)connect parameters = [self prepareAuthorizationRequestParameters:parameters forConnection:connection options:options]; - authorizationRequestURL = [[self authorizationEndpointURLForConnection:connection] urlByAppendingQueryParameters:parameters replaceExisting:NO]; + authorizationRequestURL = [[self authorizationEndpointURLForConnection:connection options:options] urlByAppendingQueryParameters:parameters replaceExisting:NO]; dispatch_async(dispatch_get_main_queue(), ^{ void (^oauth2CompletionHandler)(NSURL *callbackURL, NSError *error) = ^(NSURL *callbackURL, NSError *error) { @@ -814,12 +827,12 @@ - (void)sendTokenRequestToConnection:(OCConnection *)connection withParameters:( parameters = sanitizedParameters; // Check for endpoint - NSURL *tokenEndpointURL = [self tokenEndpointURLForConnection:connection]; + NSURL *tokenEndpointURL = [self tokenEndpointURLForConnection:connection options:options]; if (tokenEndpointURL == nil) { // No token endpoint URL known => retrieve it first. - [self retrieveEndpointInformationForConnection:connection completionHandler:^(NSError * _Nonnull error) { + [self retrieveEndpointInformationForConnection:connection options:options completionHandler:^(NSError * _Nonnull error) { if (error == nil) { [self sendTokenRequestToConnection:connection withParameters:parameters options:options requestType:requestType completionHandler:completionHandler]; @@ -923,7 +936,7 @@ - (void)sendTokenRequestToConnection:(OCConnection *)connection withParameters:( bearerString = [NSString stringWithFormat:@"Bearer %@", jsonResponseDict[@"access_token"]]; // Finalize - void (^CompleteWithJSONResponseDict)(NSDictionary *jsonResponseDict) = ^(NSDictionary *jsonResponseDict) { + void (^CompleteWithJSONResponseDict)(NSDictionary *jsonResponseDict, BOOL tolerateMissingUserID) = ^(NSDictionary *jsonResponseDict, BOOL tolerateMissingUserID) { NSError *error = nil; NSDictionary *authenticationDataDict; NSData *authenticationData; @@ -941,7 +954,7 @@ - (void)sendTokenRequestToConnection:(OCConnection *)connection withParameters:( error = OCErrorWithDescription(OCErrorAuthorizationNotMatchingRequiredUserID, ([NSString stringWithFormat:OCLocalized(@"You logged in as user %@, but must log in as user %@. Please retry."), newUserID, requiredUserID])); } } - else + else if (!tolerateMissingUserID) { error = OCErrorWithDescription(OCErrorAuthorizationNotMatchingRequiredUserID, ([NSString stringWithFormat:OCLocalized(@"Login as user %@ required. Please retry."), requiredUserID])); } @@ -989,28 +1002,38 @@ - (void)sendTokenRequestToConnection:(OCConnection *)connection withParameters:( if (jsonResponseDict[@"user_id"] == nil) { // Retrieve user_id if it wasn't provided with the token request response - [connection retrieveLoggedInUserWithRequestCustomization:^(OCHTTPRequest * _Nonnull request) { - request.requiredSignals = nil; - [request setValue:bearerString forHeaderField:OCHTTPHeaderFieldNameAuthorization]; - } completionHandler:^(NSError * _Nullable error, OCUser * _Nullable loggedInUser) { - if (error == nil) - { - NSMutableDictionary *jsonResponseUpdated = [jsonResponseDict mutableCopy]; + if (options[OCAuthenticationMethodWebFingerAccountLookupURLKey] != nil) + { + // user_id can't be retrieved at this time as final instance is still to be determined - will be fetched on first full -connect: + CompleteWithJSONResponseDict(jsonResponseDict, YES); + } + else + { + // Retrieve user_id from the user endpoint + [connection retrieveLoggedInUserWithRequestCustomization:^(OCHTTPRequest * _Nonnull request) { + request.requiredSignals = nil; + [request setValue:bearerString forHeaderField:OCHTTPHeaderFieldNameAuthorization]; + } completionHandler:^(NSError * _Nullable error, OCUser * _Nullable loggedInUser) { + if (error == nil) + { + NSMutableDictionary *jsonResponseUpdated = [jsonResponseDict mutableCopy]; - jsonResponseUpdated[@"user_id"] = loggedInUser.userName; + jsonResponseUpdated[@"user_id"] = loggedInUser.userName; - CompleteWithJSONResponseDict(jsonResponseUpdated); - } - else - { - completionHandler(error, nil, nil); - } - }]; + CompleteWithJSONResponseDict(jsonResponseUpdated, NO); + } + else + { + // Return error + completionHandler(error, nil, nil); + } + }]; + } } else { // user_id was already provided - we're all set! - CompleteWithJSONResponseDict(jsonResponseDict); + CompleteWithJSONResponseDict(jsonResponseDict, NO); } } } diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.h b/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.h index a18e86e6..f3687125 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.h +++ b/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.h @@ -32,6 +32,7 @@ extern OCAuthenticationMethodIdentifier OCAuthenticationMethodIdentifierOpenIDCo extern OCClassSettingsKey OCAuthenticationMethodOpenIDConnectRedirectURI; extern OCClassSettingsKey OCAuthenticationMethodOpenIDConnectScope; +extern OCClassSettingsKey OCAuthenticationMethodOpenIDConnectPrompt; extern OCClassSettingsKey OCAuthenticationMethodOpenIDRegisterClient; extern OCClassSettingsKey OCAuthenticationMethodOpenIDRegisterClientNameTemplate; extern OCClassSettingsKey OCAuthenticationMethodOpenIDFallbackOnClientRegistrationFailure; diff --git a/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.m b/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.m index e30a2eb2..b4c29f4a 100644 --- a/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.m +++ b/ownCloudSDK/Authentication/OCAuthenticationMethodOpenIDConnect.m @@ -32,7 +32,7 @@ static OIDCDictKeyPath OIDCKeyPathClientID = @"clientRegistrationClientID"; static OIDCDictKeyPath OIDCKeyPathClientSecret = @"clientRegistrationClientSecret"; -@interface OCAuthenticationMethodOpenIDConnect () +@implementation OCAuthenticationMethodOpenIDConnect { NSDictionary *_clientRegistrationResponse; // JSON response from client registration NSURL *_clientRegistrationEndpointURL; // URL the client registration was last performed at @@ -42,9 +42,6 @@ @interface OCAuthenticationMethodOpenIDConnect () NSString *_clientID; NSString *_clientSecret; } -@end - -@implementation OCAuthenticationMethodOpenIDConnect #pragma mark - Class settings + (void)load @@ -55,6 +52,7 @@ + (void)load [self registerOCClassSettingsDefaults:@{ OCAuthenticationMethodOpenIDConnectRedirectURI : @"oc://ios.owncloud.com", OCAuthenticationMethodOpenIDConnectScope : @"openid offline_access email profile", + OCAuthenticationMethodOpenIDConnectPrompt : @"select_account consent", OCAuthenticationMethodOpenIDRegisterClient : @(YES), OCAuthenticationMethodOpenIDRegisterClientNameTemplate : @"ownCloud/{{os.name}} {{app.version}}", OCAuthenticationMethodOpenIDFallbackOnClientRegistrationFailure : @(YES) @@ -69,6 +67,11 @@ + (void)load OCClassSettingsMetadataKeyDescription : @"OpenID Connect Scope", OCClassSettingsMetadataKeyCategory : @"OIDC" }, + OCAuthenticationMethodOpenIDConnectPrompt : @{ + OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, + OCClassSettingsMetadataKeyDescription : @"OpenID Connect Prompt", + OCClassSettingsMetadataKeyCategory : @"OIDC" + }, OCAuthenticationMethodOpenIDRegisterClient : @{ OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, OCClassSettingsMetadataKeyDescription : @"Use OpenID Connect Dynamic Client Registration if the `.well-known/openid-configuration` provides a `registration_endpoint`. If this option is enabled and a registration endpoint is available, `oa2-client-id` and `oa2-client-secret` will be ignored.", @@ -104,7 +107,7 @@ + (NSString *)name } #pragma mark - OAuth2 extensions -- (NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection +- (NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options { NSString *authorizationEndpointURLString; @@ -116,7 +119,7 @@ - (NSURL *)authorizationEndpointURLForConnection:(OCConnection *)connection return (nil); } -- (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection; +- (NSURL *)tokenEndpointURLForConnection:(OCConnection *)connection options:(OCAuthenticationMethodDetectionOptions)options { NSString *tokenEndpointURLString; @@ -169,16 +172,22 @@ - (NSString *)tokenRequestAuthorizationHeaderForType:(OCAuthenticationOAuth2Toke return ([super tokenRequestAuthorizationHeaderForType:requestType connection:connection]); } -- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection completionHandler:(void(^)(NSError *error))completionHandler +- (void)retrieveEndpointInformationForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options completionHandler:(nonnull void (^)(NSError * _Nullable))completionHandler { NSURL *openidConfigURL; - if ((openidConfigURL = [self.class _openIDConfigurationURLForConnection:connection]) != nil) + if ((openidConfigURL = [self.class _openIDConfigurationURLForConnection:connection options:options]) != nil) { OCHTTPRequest *openidConfigRequest = [OCHTTPRequest requestWithURL:openidConfigURL]; openidConfigRequest.redirectPolicy = OCHTTPRequestRedirectPolicyHandleLocally; + NSURL *refererForIDPURL; + if ((refererForIDPURL = options[OCAuthenticationMethodAuthenticationRefererURL]) != nil) + { + [openidConfigRequest setValue:refererForIDPURL.absoluteString forHeaderField:@"Referer"]; + } + [connection sendRequest:openidConfigRequest ephermalCompletionHandler:^(OCHTTPRequest * _Nonnull request, OCHTTPResponse * _Nullable response, NSError * _Nullable error) { NSError *jsonError; @@ -253,7 +262,7 @@ - (NSString *)scope - (NSString *)prompt { - return (@"consent"); + return ([self classSettingForOCClassSettingsKey:OCAuthenticationMethodOpenIDConnectPrompt]); } #pragma mark - Dynamic Client Registration @@ -581,21 +590,49 @@ - (void)_clearClientRegistrationData } #pragma mark - Authentication Method Detection -+ (NSURL *)_openIDConfigurationURLForConnection:(OCConnection *)connection ++ (NSURL *)_openIDConfigurationURLForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options { - return ([connection URLForEndpoint:OCConnectionEndpointIDWellKnown options:@{ OCConnectionEndpointURLOptionWellKnownSubPath : @"openid-configuration" }]); + OCAuthenticationMethodDetectionOptions detectionOptions = @{ OCConnectionEndpointURLOptionWellKnownSubPath : @"openid-configuration" }; + + if (options != nil) + { + NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:options]; + [mutableOptions addEntriesFromDictionary:detectionOptions]; + detectionOptions = mutableOptions; + } + + return ([connection URLForEndpoint:OCConnectionEndpointIDWellKnown options:detectionOptions]); } -+ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection ++ (NSArray *)detectionRequestsForConnection:(OCConnection *)connection options:(nullable OCAuthenticationMethodDetectionOptions)options { NSURL *openidConfigURL; - NSArray *oAuth2DetectionURLs; + NSArray *oidcDetectionURLs = nil; + + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey],NSNumber).boolValue) + { + // Do not perform "Bearer" check requests to determine OIDC support + oidcDetectionURLs = @[]; + } + else + { + // Do not use super method here because OAuth2 verifies additional URLs to specifically determine OAuth2 availability + oidcDetectionURLs = [self detectionRequestsBasedOnWWWAuthenticateMethod:@"Bearer" forConnection:connection]; + } - if ((oAuth2DetectionURLs = [self detectionRequestsBasedOnWWWAuthenticateMethod:@"Bearer" forConnection:connection]) != nil) // Do not use super method here because OAuth2 verifies additional URLs to specifically determine OAuth2 availability + if (oidcDetectionURLs != nil) { - if ((openidConfigURL = [self _openIDConfigurationURLForConnection:connection]) != nil) + if ((openidConfigURL = [self _openIDConfigurationURLForConnection:connection options:options]) != nil) { - return ([oAuth2DetectionURLs arrayByAddingObject:[OCHTTPRequest requestWithURL:openidConfigURL]]); + OCHTTPRequest *openidConfigRequest = [OCHTTPRequest requestWithURL:openidConfigURL]; + + NSURL *refererForIDPURL; + if ((refererForIDPURL = options[OCAuthenticationMethodAuthenticationRefererURL]) != nil) + { + [openidConfigRequest setValue:refererForIDPURL.absoluteString forHeaderField:@"Referer"]; + } + + return ([oidcDetectionURLs arrayByAddingObject:openidConfigRequest]); } } @@ -604,56 +641,69 @@ + (NSURL *)_openIDConfigurationURLForConnection:(OCConnection *)connection + (void)detectAuthenticationMethodSupportForConnection:(OCConnection *)connection withServerResponses:(NSDictionary *)serverResponses options:(OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(OCAuthenticationMethodIdentifier identifier, BOOL supported))completionHandler { - [super detectAuthenticationMethodSupportForConnection:connection withServerResponses:serverResponses options:options completionHandler:^(OCAuthenticationMethodIdentifier identifier, BOOL supported) { + if (OCTypedCast(options[OCAuthenticationMethodSkipWWWAuthenticateChecksKey],NSNumber).boolValue) + { + // No "Bearer" check requests were performed, so perform just a validity check for the OIDC configuration response + [self detectOIDCSupportForConnection:connection withServerResponses:serverResponses options:options completionHandler:completionHandler]; + } + else + { + // Since OIDC is a superset of OAuth2, ensure criteria for OAuth2 are also met + [super detectAuthenticationMethodSupportForConnection:connection withServerResponses:serverResponses options:options completionHandler:^(OCAuthenticationMethodIdentifier identifier, BOOL supported) { + if (supported) + { + // OAuth2 supported => continue + [self detectOIDCSupportForConnection:connection withServerResponses:serverResponses options:options completionHandler:completionHandler]; + } + else + { + // OAuth2 not supported => OIDC requirement not met + completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, NO); + } + }]; + } +} - if (supported) ++ (void)detectOIDCSupportForConnection:(OCConnection *)connection withServerResponses:(NSDictionary *)serverResponses options:(OCAuthenticationMethodDetectionOptions)options completionHandler:(void(^)(OCAuthenticationMethodIdentifier identifier, BOOL supported))completionHandler +{ + NSURL *wellKnownURL; + BOOL completeWithNotSupported = YES; + + if ((wellKnownURL = [self _openIDConfigurationURLForConnection:connection options:options]) != nil) + { + OCHTTPRequest *wellKnownRequest; + + if ((wellKnownRequest = [serverResponses objectForKey:wellKnownURL]) != nil) { - // OAuth2 supported => continue - NSURL *wellKnownURL; - BOOL completeWithNotSupported = YES; + OCHTTPResponse *response = wellKnownRequest.httpResponse; - if ((wellKnownURL = [self _openIDConfigurationURLForConnection:connection]) != nil) + if (response.status.isSuccess) { - OCHTTPRequest *wellKnownRequest; - - if ((wellKnownRequest = [serverResponses objectForKey:wellKnownURL]) != nil) + if ([response.contentType hasSuffix:@"/json"]) { - OCHTTPResponse *response = wellKnownRequest.httpResponse; + NSError *error = nil; - if (response.status.isSuccess) + if ([response bodyConvertedDictionaryFromJSONWithError:&error] != nil) { - if ([response.contentType hasSuffix:@"/json"]) - { - NSError *error = nil; - - if ([response bodyConvertedDictionaryFromJSONWithError:&error] != nil) - { - // OIDC supported - completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, YES); - completeWithNotSupported = NO; - } - else - { - OCLogError(@"Error decoding OIDC configuration JSON: %@", OCLogPrivate(error)); - } - } + // OIDC supported + completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, YES); + completeWithNotSupported = NO; + } + else + { + OCLogError(@"Error decoding OIDC configuration JSON: %@", OCLogPrivate(error)); } } } - - // Fallback completion handler call - if (completeWithNotSupported) - { - // OIDC not supported - completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, NO); - } - } - else - { - // OAuth2 not supported => OIDC requirement not met - completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, NO); } - }]; + } + + // Fallback completion handler call + if (completeWithNotSupported) + { + // OIDC not supported + completionHandler(OCAuthenticationMethodIdentifierOpenIDConnect, NO); + } } #pragma mark - Requests @@ -696,7 +746,7 @@ - (void)sendTokenRequestToConnection:(OCConnection *)connection withParameters:( - (void)generateBookmarkAuthenticationDataWithConnection:(OCConnection *)connection options:(OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions)options completionHandler:(void(^)(NSError *error, OCAuthenticationMethodIdentifier authenticationMethodIdentifier, NSData *authenticationData))completionHandler { - [self retrieveEndpointInformationForConnection:connection completionHandler:^(NSError * _Nonnull error) { + [self retrieveEndpointInformationForConnection:connection options:options completionHandler:^(NSError * _Nonnull error) { if (error == nil) { [super generateBookmarkAuthenticationDataWithConnection:connection options:options completionHandler:completionHandler]; @@ -714,6 +764,7 @@ - (void)generateBookmarkAuthenticationDataWithConnection:(OCConnection *)connect OCClassSettingsKey OCAuthenticationMethodOpenIDConnectRedirectURI = @"oidc-redirect-uri"; OCClassSettingsKey OCAuthenticationMethodOpenIDConnectScope = @"oidc-scope"; +OCClassSettingsKey OCAuthenticationMethodOpenIDConnectPrompt = @"oidc-prompt"; OCClassSettingsKey OCAuthenticationMethodOpenIDRegisterClient = @"oidc-register-client"; OCClassSettingsKey OCAuthenticationMethodOpenIDRegisterClientNameTemplate = @"oidc-register-client-name-template"; OCClassSettingsKey OCAuthenticationMethodOpenIDFallbackOnClientRegistrationFailure = @"oidc-fallback-on-client-registration-failure"; diff --git a/ownCloudSDK/Bookmark/OCBookmark+DataItem.h b/ownCloudSDK/Bookmark/OCBookmark+DataItem.h new file mode 100644 index 00000000..a9987715 --- /dev/null +++ b/ownCloudSDK/Bookmark/OCBookmark+DataItem.h @@ -0,0 +1,28 @@ +// +// OCBookmark+DataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.11.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 "OCBookmark.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCBookmark (DataItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Bookmark/OCBookmark+DataItem.m b/ownCloudSDK/Bookmark/OCBookmark+DataItem.m new file mode 100644 index 00000000..3e522240 --- /dev/null +++ b/ownCloudSDK/Bookmark/OCBookmark+DataItem.m @@ -0,0 +1,44 @@ +// +// OCBookmark+DataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.11.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 "OCBookmark+DataItem.h" +#import "OCDataTypes.h" +#import "OCResource.h" +#import "OCMacros.h" + +@implementation OCBookmark (DataItem) + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeBookmark); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.uuid.UUIDString); +} + +- (OCDataItemVersion)dataItemVersion +{ + OCResource *avatarResource = OCTypedCast(self.avatar, OCResource); + NSString *avatarVersion = ((avatarResource != nil) ? avatarResource.version : @""); + + return ([NSString stringWithFormat:@"%@%@%@%@%@%@%@", self.name, self.url, self.originURL, self.userName, self.userDisplayName, self.authenticationDataID, avatarVersion]); +} + +@end diff --git a/ownCloudSDK/Bookmark/OCBookmark+Diagnostics.m b/ownCloudSDK/Bookmark/OCBookmark+Diagnostics.m index f23ab47c..903d8617 100644 --- a/ownCloudSDK/Bookmark/OCBookmark+Diagnostics.m +++ b/ownCloudSDK/Bookmark/OCBookmark+Diagnostics.m @@ -23,11 +23,26 @@ #import "OCMacros.h" #import "OCBookmarkManager.h" #import "OCCoreManager.h" +#import "OCCertificateStoreRecord.h" @implementation OCBookmark (Diagnostics) - (NSArray *)diagnosticNodesWithContext:(OCDiagnosticContext *)context { + NSMutableArray *certificateChildren = [NSMutableArray new]; + __weak OCBookmark *weakSelf = self; + + for (OCCertificateStoreRecord *record in self.certificateStore.allRecords) + { + [certificateChildren addObject:[OCDiagnosticNode withLabel:record.hostname children:@[ + [OCDiagnosticNode withLabel:OCLocalized(@"Certificate Date") content:record.lastModifiedDate.description], + [OCDiagnosticNode withLabel:OCLocalized(@"Invalidate") action:^(OCDiagnosticContext * _Nullable context) { + OCCertificate *invalidCertificate = [OCCertificate certificateWithCertificateData:[NSData new] hostName:record.hostname]; + [weakSelf.certificateStore storeCertificate:invalidCertificate forHostname:record.hostname]; + }] + ]]]; + } + return (@[ [OCDiagnosticNode withLabel:OCLocalized(@"UUID") content:self.uuid.UUIDString], [OCDiagnosticNode withLabel:OCLocalized(@"Name") content:self.name], @@ -43,10 +58,7 @@ @implementation OCBookmark (Diagnostics) } }], - [OCDiagnosticNode withLabel:OCLocalized(@"Certificate Date") content:self.certificateModificationDate.description], - [OCDiagnosticNode withLabel:OCLocalized(@"Invalidate Certificate") action:^(OCDiagnosticContext * _Nullable context) { - self.certificate = [OCCertificate certificateWithCertificateData:[NSData new] hostName:self.url.host]; - }], + [OCDiagnosticNode withLabel:OCLocalized(@"Certificates") children:certificateChildren], [OCDiagnosticNode withLabel:OCLocalized(@"User Name") content:self.userName], [OCDiagnosticNode withLabel:OCLocalized(@"Auth Method") content:self.authenticationMethodIdentifier], diff --git a/ownCloudSDK/Bookmark/OCBookmark.h b/ownCloudSDK/Bookmark/OCBookmark.h index a4a9c3ad..ac187051 100644 --- a/ownCloudSDK/Bookmark/OCBookmark.h +++ b/ownCloudSDK/Bookmark/OCBookmark.h @@ -20,7 +20,9 @@ #import "OCAuthenticationMethod.h" #import "OCCertificate.h" #import "OCDatabase+Versions.h" +#import "OCViewProvider.h" #import "OCUser.h" +#import "OCCertificateStore.h" typedef NSUUID* OCBookmarkUUID; typedef NSString* OCBookmarkUUIDString; @@ -33,6 +35,8 @@ typedef NS_ENUM(NSUInteger, OCBookmarkAuthenticationDataStorage) typedef NSString* OCBookmarkUserInfoKey NS_TYPED_ENUM; +typedef NSString* OCBookmarkCapability NS_TYPED_ENUM; + NS_ASSUME_NONNULL_BEGIN @interface OCBookmark : NSObject @@ -42,16 +46,18 @@ NS_ASSUME_NONNULL_BEGIN @property(strong,nullable) NSString *name; //!< Name of the server @property(strong,nullable) NSURL *url; //!< URL to use to connect to the server +@property(strong,nullable) NSURL *originURL; //!< URL originally provided by the user, which then redirected to .url. In case .url becomes invalid, the originURL can be used to find the new server. If originURL is set, UI should present it prominently - while also displaying .url near it. + @property(strong,nullable) NSString *serverLocationUserName; //!< User name to use for server location -@property(strong,nullable) NSURL *originURL; //!< URL originally provided by the user, which then redirected to .url. In case .url becomes invalid, the originURL can be used to find the new server. If originURL is set, UI should present it prominently - while also displaying .url near it. +@property(strong,nullable,nonatomic) id avatar; //!< Object that can provide a view to display the avatar of the user @property(readonly,nullable) NSString *userName; //!< Convenience method for accessing the userName stored in the authenticationData. Use .user.userName instead if possible. @property(strong,nullable) NSString *userDisplayName; //!< Display name of a user. Please use .user.userDisplayName instead. @property(strong,nullable) OCUser *user; //!< User object of the bookmark's account owner. Available / kept up-to-date after every login. -@property(strong,nullable) OCCertificate *certificate; //!< Certificate last used by the server this bookmark refers to -@property(strong,nullable) NSDate *certificateModificationDate; //!< Date the certificate stored in this bookmark was last modified. +@property(strong,nullable) OCCertificateStore *certificateStore; //!< Certificate store +@property(readonly,nullable) OCCertificate *primaryCertificate; //!< Primary certificate for the bookmark (usually the certificate for url.host - or the only certificate in the store) @property(strong,nullable) OCAuthenticationMethodIdentifier authenticationMethodIdentifier; //!< Identifies the authentication method to use @property(strong,nonatomic,nullable) NSData *authenticationData; //!< OCAuthenticationMethod's data (opaque) needed to log into the server. Backed by keychain or memory depending on .authenticationDataStorage. @@ -74,6 +80,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)setValuesFrom:(OCBookmark *)sourceBookmark; //!< Replaces all values in the receiving bookmark with those in the source bookmark. - (void)setLastUserName:(nullable NSString *)userName; //!< Replaces the internally stored fallback user name returned by .userName for when no authentication data is available. +#pragma mark - Capabilities +@property(strong,nullable,nonatomic) NSSet *capabilities; +- (void)addCapability:(OCBookmarkCapability)capability; +- (void)removeCapability:(OCBookmarkCapability)capability; +- (BOOL)hasCapability:(OCBookmarkCapability)capability; + #pragma mark - Certificate approval - (NSNotificationName)certificateUserApprovalUpdateNotificationName; //!< Notification that gets sent if the bookmark's certificate user-approved status changed - (void)postCertificateUserApprovalUpdateNotification; //!< Posts a .certificateUserApprovalUpdateNotificationName notification @@ -84,6 +96,9 @@ extern OCBookmarkUserInfoKey OCBookmarkUserInfoKeyStatusInfo; //!< .userInfo ke extern OCBookmarkUserInfoKey OCBookmarkUserInfoKeyAllowHTTPConnection; //!< .userInfo key with a NSDate value. To be set to the date that the user was informed and allowed the usage of HTTP. To be removed otherwise. extern OCBookmarkUserInfoKey OCBookmarkUserInfoKeyBookmarkCreation; //!< .userInfo key with a NSDictionary holding information on the creation of the bookmark. +extern OCBookmarkCapability OCBookmarkCapabilityDrives; //!< This bookmark is drive-based. +extern OCBookmarkCapability OCBookmarkCapabilityFavorites; //!< This bookmark supports favorites. + extern NSNotificationName OCBookmarkAuthenticationDataChangedNotification; //!< Name of notification that is sent whenever a bookmark's authenticationData is changed. The object of the notification is the bookmark. Sent only if .authenticationDataStorage is OCBookmarkAuthenticationDataStorageKeychain. extern NSNotificationName OCBookmarkUpdatedNotification; //!< Name of notification that can be sent by third parties after completing an update to a bookmark. diff --git a/ownCloudSDK/Bookmark/OCBookmark.m b/ownCloudSDK/Bookmark/OCBookmark.m index 6de4a4e9..432c67c2 100644 --- a/ownCloudSDK/Bookmark/OCBookmark.m +++ b/ownCloudSDK/Bookmark/OCBookmark.m @@ -21,6 +21,7 @@ #import "OCBookmark+IPNotificationNames.h" #import "OCEvent.h" #import "OCAppIdentity.h" +#import "OCResourceTextPlaceholder.h" #import "NSData+OCHash.h" #if TARGET_OS_IOS @@ -32,6 +33,9 @@ @interface OCBookmark () OCIPCNotificationName _coreUpdateNotificationName; OCIPCNotificationName _bookmarkAuthUpdateNotificationName; + id _avatar; + NSData *_avatarData; + NSString *_lastUsername; } @end @@ -43,8 +47,7 @@ @implementation OCBookmark @synthesize name = _name; @synthesize url = _url; -@synthesize certificate = _certificate; -@synthesize certificateModificationDate = _certificateModificationDate; +@synthesize certificateStore = _certificateStore; @synthesize authenticationMethodIdentifier = _authenticationMethodIdentifier; @synthesize authenticationData = _authenticationData; @@ -87,6 +90,8 @@ - (instancetype)init [observerBookmark considerAuthenticationDataFlush]; }]; + _certificateStore = [OCCertificateStore new]; + #if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(considerAuthenticationDataFlush) name:UIApplicationWillResignActiveNotification object:nil]; #endif /* TARGET_OS_IOS */ @@ -210,13 +215,138 @@ - (NSString *)userName if ((authMethod = [OCAuthenticationMethod registeredAuthenticationMethodForIdentifier:self.authenticationMethodIdentifier]) != nil) { - return ([authMethod userNameFromAuthenticationData:self.authenticationData]); + NSString *userName = nil; + + if ((userName = [authMethod userNameFromAuthenticationData:self.authenticationData]) != nil) + { + return (userName); + } } } return (_lastUsername); } +- (OCCertificate *)primaryCertificate +{ + OCCertificate *primaryCertificate = (_url.host != nil) ? [_certificateStore certificateForHostname:_url.host lastModified:NULL] : nil; + + if ((primaryCertificate == nil) && (_certificateStore != nil)) + { + NSArray *records = _certificateStore.allRecords; + + if (records.count == 1) + { + primaryCertificate = records.firstObject.certificate; + } + } + + return (primaryCertificate); +} + +#pragma mark - Avatar +- (id)avatar +{ + @synchronized(self) + { + if ((_avatar == nil) && (_avatarData != nil)) + { + @try + { + _avatar = [NSKeyedUnarchiver unarchiveObjectWithData:_avatarData]; + } + @catch (NSException *exception) + { + } + } + + if (_avatar == nil) + { + NSString *userName; + + if ((userName = ((self.userDisplayName != nil) ? self.userDisplayName : self.userName)) != nil) + { + OCResourceTextPlaceholder *placeholder = [OCResourceTextPlaceholder new]; + placeholder.text = [OCUser localizedInitialsForName:userName]; + + return ((id)placeholder); + } + } + } + + return (_avatar); +} + +- (void)setAvatar:(id)avatar +{ + @synchronized(self) + { + NSData *avatarData = nil; + + if (avatar != nil) + { + @try + { + NSError *error = nil; + avatarData = [NSKeyedArchiver archivedDataWithRootObject:avatar requiringSecureCoding:YES error:&error]; + + if (error != nil) + { + OCLogError(@"Error serializing %@: %@", avatar, error); + } + } + @catch (NSException *exception) + { + } + } + + _avatar = (avatarData != nil) ? avatar : nil; + _avatarData = avatarData; + } +} + +#pragma mark - Capabilities +- (void)addCapability:(OCBookmarkCapability)capability +{ + if (capability == nil) + { + OCLogError(@"Attempt to add nil capability to bookmark %@", self); + return; + } + + if (_capabilities == nil) + { + _capabilities = [[NSSet alloc] initWithObjects:capability, nil]; + } + else + { + _capabilities = [_capabilities setByAddingObject:capability]; + } +} + +- (void)removeCapability:(OCBookmarkCapability)capability +{ + if (capability == nil) + { + OCLogError(@"Attempt to add nil capability to bookmark %@", self); + return; + } + + if (_capabilities != nil) + { + NSMutableSet *capabilities = [_capabilities mutableCopy]; + + [capabilities removeObject:capability]; + + _capabilities = [_capabilities copy]; + } +} + +- (BOOL)hasCapability:(OCBookmarkCapability)capability +{ + return ([_capabilities containsObject:capability]); +} + #pragma mark - Certificate approval - (NSNotificationName)certificateUserApprovalUpdateNotificationName { @@ -240,14 +370,18 @@ - (void)setValuesFrom:(OCBookmark *)sourceBookmark _originURL = sourceBookmark.originURL; - _certificate = sourceBookmark.certificate; - _certificateModificationDate = sourceBookmark.certificateModificationDate; + _capabilities = sourceBookmark.capabilities; + + _certificateStore = sourceBookmark.certificateStore; _authenticationMethodIdentifier = sourceBookmark.authenticationMethodIdentifier; _authenticationData = sourceBookmark.authenticationData; _authenticationDataStorage = sourceBookmark.authenticationDataStorage; _authenticationValidationDate = sourceBookmark.authenticationValidationDate; + _avatar = sourceBookmark->_avatar; + _avatarData = sourceBookmark->_avatarData; + _lastUsername = sourceBookmark->_lastUsername; _userDisplayName = sourceBookmark->_userDisplayName; @@ -279,12 +413,27 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _originURL = [decoder decodeObjectOfClass:NSURL.class forKey:@"originURL"]; - _certificate = [decoder decodeObjectOfClass:OCCertificate.class forKey:@"certificate"]; - _certificateModificationDate = [decoder decodeObjectOfClass:NSDate.class forKey:@"certificateModificationDate"]; + _capabilities = [decoder decodeObjectOfClasses:[NSSet setWithObjects:NSSet.class, NSString.class, nil] forKey:@"capabilities"]; + + _certificateStore = [decoder decodeObjectOfClass:OCCertificateStore.class forKey:@"certificateStore"]; + + if (_certificateStore == nil) + { + // Migrate legacy certificate + certificateModificationDate to certificateStore + OCCertificate *certificate = [decoder decodeObjectOfClass:OCCertificate.class forKey:@"certificate"]; + NSDate *certificateModificationDate = [decoder decodeObjectOfClass:NSDate.class forKey:@"certificateModificationDate"]; + + if ((certificate != nil) && (certificateModificationDate != nil)) + { + _certificateStore = [[OCCertificateStore alloc] initWithMigrationOfCertificate:certificate forHostname:_url.host lastModifiedDate:certificateModificationDate]; + } + } _authenticationMethodIdentifier = [decoder decodeObjectOfClass:NSString.class forKey:@"authenticationMethodIdentifier"]; _authenticationValidationDate = [decoder decodeObjectOfClass:NSDate.class forKey:@"authenticationValidationDate"]; + _avatarData = [decoder decodeObjectOfClass:NSData.class forKey:@"avatarData"]; + _databaseVersion = [decoder decodeIntegerForKey:@"databaseVersion"]; _lastUsername = [decoder decodeObjectOfClass:NSString.class forKey:@"lastUsername"]; @@ -311,12 +460,15 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_originURL forKey:@"originURL"]; - [coder encodeObject:_certificate forKey:@"certificate"]; - [coder encodeObject:_certificateModificationDate forKey:@"certificateModificationDate"]; + [coder encodeObject:_capabilities forKey:@"capabilities"]; + + [coder encodeObject:_certificateStore forKey:@"certificateStore"]; [coder encodeObject:_authenticationMethodIdentifier forKey:@"authenticationMethodIdentifier"]; [coder encodeObject:_authenticationValidationDate forKey:@"authenticationValidationDate"]; + [coder encodeObject:_avatarData forKey:@"avatarData"]; + [coder encodeInteger:_databaseVersion forKey:@"databaseVersion"]; [coder encodeObject:_lastUsername forKey:@"lastUsername"]; @@ -343,8 +495,8 @@ - (NSString *)description ((_databaseVersion!=OCDatabaseVersionUnknown) ? [@", databaseVersion: " stringByAppendingString:@(_databaseVersion).stringValue] : @""), ((_url!=nil) ? [@", url: " stringByAppendingString:_url.absoluteString] : @""), ((_originURL!=nil) ? [@", originURL: " stringByAppendingString:_originURL.absoluteString] : @""), - ((_certificate!=nil) ? [@", certificate: " stringByAppendingString:_certificate.description] : @""), - ((_certificateModificationDate!=nil) ? [@", certificateModificationDate: " stringByAppendingString:_certificateModificationDate.description] : @""), + ((_capabilities!=nil) ? [@", capabilities: " stringByAppendingString:_capabilities.description] : @""), + ((_certificateStore.allRecords.count>0) ? [@", certificates: " stringByAppendingString:_certificateStore.allRecords.description] : @""), ((_authenticationMethodIdentifier!=nil) ? [@", authenticationMethodIdentifier: " stringByAppendingString:_authenticationMethodIdentifier] : @""), ((authData!=nil) ? [@", authenticationData: " stringByAppendingFormat:@"%lu bytes", authData.length] : @""), ((_authenticationValidationDate!=nil) ? [@", authenticationValidationDate: " stringByAppendingString:_authenticationValidationDate.description] : @""), @@ -400,5 +552,8 @@ + (OCIPCNotificationName)bookmarkAuthUpdateNotificationName OCBookmarkUserInfoKey OCBookmarkUserInfoKeyAllowHTTPConnection = @"OCAllowHTTPConnection"; OCBookmarkUserInfoKey OCBookmarkUserInfoKeyBookmarkCreation = @"bookmark-creation"; +OCBookmarkCapability OCBookmarkCapabilityDrives = @"drives"; +OCBookmarkCapability OCBookmarkCapabilityFavorites = @"favorites"; + NSNotificationName OCBookmarkAuthenticationDataChangedNotification = @"OCBookmarkAuthenticationDataChanged"; NSNotificationName OCBookmarkUpdatedNotification = @"OCBookmarkUpdatedNotification"; diff --git a/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.h b/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.h new file mode 100644 index 00000000..490f7a40 --- /dev/null +++ b/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.h @@ -0,0 +1,31 @@ +// +// NSArray+OCFiltering.h +// ownCloudSDK +// +// Created by Felix Schwarz on 04.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface NSArray (OCFiltering) + +- (NSArray *)filteredArrayUsingBlock:(BOOL(^)(ObjectType object, BOOL * _Nonnull stop))filter; //!< Returns a new array with all objects passing the provided filter block. Iteration can be stopped at any time by setting *stop to true. + +- (nullable ObjectType)firstObjectMatching:(BOOL(^)(ObjectType object))matcher; //!< Returns the first object matching the provided matcher block + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.m b/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.m new file mode 100644 index 00000000..4687c6cf --- /dev/null +++ b/ownCloudSDK/Categories/Foundation/NSArray+OCFiltering.m @@ -0,0 +1,50 @@ +// +// NSArray+OCFiltering.m +// ownCloudSDK +// +// Created by Felix Schwarz on 04.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 "NSArray+OCFiltering.h" + +@implementation NSArray (OCFiltering) + +- (NSArray *)filteredArrayUsingBlock:(BOOL(^)(id object, BOOL *stop))filter +{ + NSMutableIndexSet *selectedIndexSet = [NSMutableIndexSet new]; + + [self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (filter(obj, stop)) + { + [selectedIndexSet addIndex:idx]; + } + }]; + + return ([self objectsAtIndexes:selectedIndexSet]); +} + +- (nullable id)firstObjectMatching:(BOOL(^)(id object))matcher +{ + for (id object in self) + { + if (matcher(object)) + { + return (object); + } + } + + return (nil); +} + +@end diff --git a/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.h b/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.h new file mode 100644 index 00000000..14df87a4 --- /dev/null +++ b/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.h @@ -0,0 +1,34 @@ +// +// NSArray+OCMapping.h +// ownCloudSDK +// +// Created by Felix Schwarz on 17.05.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 + +NS_ASSUME_NONNULL_BEGIN + +typedef id _Nullable (^OCObjectToKeyMapper)(id obj); +typedef id _Nullable (^OCObjectToObjectMapper)(id obj); + +@interface NSArray (OCMapping) + +- (NSMutableDictionary *)dictionaryUsingMapper:(OCObjectToKeyMapper)mapper; +- (NSMutableSet *)setUsingMapper:(OCObjectToObjectMapper)mapper; +- (NSMutableArray *)arrayUsingMapper:(OCObjectToObjectMapper)mapper; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.m b/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.m new file mode 100644 index 00000000..4dd4ed4f --- /dev/null +++ b/ownCloudSDK/Categories/Foundation/NSArray+OCMapping.m @@ -0,0 +1,74 @@ +// +// NSArray+OCMapping.m +// ownCloudSDK +// +// Created by Felix Schwarz on 17.05.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 "NSArray+OCMapping.h" + +@implementation NSArray (OCMapping) + +- (NSMutableDictionary *)dictionaryUsingMapper:(OCObjectToKeyMapper)mapper +{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:self.count]; + + for (id obj in self) + { + id objKey; + + if ((objKey = mapper(obj)) != nil) + { + [dict setObject:obj forKey:objKey]; + } + } + + return (dict); +} + +- (NSMutableSet *)setUsingMapper:(OCObjectToObjectMapper)mapper; +{ + NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity:self.count]; + + for (id obj in self) + { + id objKey; + + if ((objKey = mapper(obj)) != nil) + { + [set addObject:objKey]; + } + } + + return (set); +} + +- (NSMutableArray *)arrayUsingMapper:(OCObjectToObjectMapper)mapper +{ + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:self.count]; + + for (id obj in self) + { + id objKey; + + if ((objKey = mapper(obj)) != nil) + { + [array addObject:objKey]; + } + } + + return (array); +} + +@end diff --git a/ownCloudSDK/Categories/Foundation/NSArray+ObjCRuntime.m b/ownCloudSDK/Categories/Foundation/NSArray+ObjCRuntime.m index 93b5b0fc..9f526bb6 100644 --- a/ownCloudSDK/Categories/Foundation/NSArray+ObjCRuntime.m +++ b/ownCloudSDK/Categories/Foundation/NSArray+ObjCRuntime.m @@ -43,7 +43,7 @@ @implementation NSArray (OCObjCRuntime) { Class *classList; - if ((classList = (Class *)malloc(classCount * sizeof(Class))) != NULL) + if ((classList = (Class *)calloc(classCount, sizeof(Class))) != NULL) { classCount = objc_getClassList(classList, classCount); diff --git a/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.h b/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.h index b7ab0c06..594ea9dd 100644 --- a/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.h +++ b/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.h @@ -28,9 +28,12 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)dateParsedFromCompactUTCString:(NSString *)dateString error:(NSError * _Nullable *)error; - (nullable NSString *)compactUTCString; - (nullable NSString *)compactUTCStringDateOnly; +- (nullable NSString *)compactISO8601String; - (nullable NSString *)compactLocalTimeZoneString; +- (nullable NSString *)localizedStringWithTemplate:(NSString *)dateTemplate locale:(nullable NSLocale *)locale; + @end NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.m b/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.m index 6e3a3cb5..1f69d88c 100644 --- a/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.m +++ b/ownCloudSDK/Categories/Foundation/NSDate+OCDateParser.m @@ -54,6 +54,43 @@ + (NSDateFormatter *)_ocDateFormatterCompactUTC return (dateFormatter); } ++ (NSISO8601DateFormatter *)_ocDateFormatterISO8601WithFractionalSeconds +{ + static NSISO8601DateFormatter *dateFormatter; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + if ((dateFormatter = [NSISO8601DateFormatter new]) != nil) + { + dateFormatter.formatOptions = NSISO8601DateFormatWithInternetDateTime | + NSISO8601DateFormatWithDashSeparatorInDate | + NSISO8601DateFormatWithColonSeparatorInTime | + NSISO8601DateFormatWithColonSeparatorInTimeZone | + NSISO8601DateFormatWithFractionalSeconds; + } + }); + + return (dateFormatter); +} + ++ (NSISO8601DateFormatter *)_ocDateFormatterISO8601 +{ + static NSISO8601DateFormatter *dateFormatter; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + if ((dateFormatter = [NSISO8601DateFormatter new]) != nil) + { + dateFormatter.formatOptions = NSISO8601DateFormatWithInternetDateTime | + NSISO8601DateFormatWithDashSeparatorInDate | + NSISO8601DateFormatWithColonSeparatorInTime | + NSISO8601DateFormatWithColonSeparatorInTimeZone; + } + }); + + return (dateFormatter); +} + + (NSDateFormatter *)_ocDateFormatterCompactLocalTimeZone { static NSDateFormatter *dateFormatter; @@ -83,7 +120,19 @@ - (NSString *)davDateString + (instancetype)dateParsedFromCompactUTCString:(NSString *)dateString error:(NSError **)error { - return ([[self _ocDateFormatterCompactUTC] dateFromString:dateString]); + NSDate *date; + + date = [[self _ocDateFormatterCompactUTC] dateFromString:dateString]; + + if (date == nil) { + date = [[self _ocDateFormatterISO8601] dateFromString:dateString]; + } + + if (date == nil) { + date = [[self _ocDateFormatterISO8601WithFractionalSeconds] dateFromString:dateString]; + } + + return (date); } - (NSString *)compactUTCString @@ -103,9 +152,44 @@ - (NSString *)compactUTCStringDateOnly return (nil); } +- (NSString *)compactISO8601String +{ + NSString *dateString = [[[self class] _ocDateFormatterISO8601] stringFromDate:self]; + + if (dateString.length >= 10) + { + return (dateString); + } + + return (nil); +} + - (NSString *)compactLocalTimeZoneString { return ([[[self class] _ocDateFormatterCompactLocalTimeZone] stringFromDate:self]); } +- (nullable NSString *)localizedStringWithTemplate:(NSString *)dateTemplate locale:(nullable NSLocale *)locale +{ + if (dateTemplate != nil) + { + NSString *localizedFormat; + + if (locale == nil) + { + locale = NSLocale.currentLocale; + } + + if ((localizedFormat = [NSDateFormatter dateFormatFromTemplate:dateTemplate options:0 locale:locale]) != nil) + { + NSDateFormatter *formatter = [NSDateFormatter new]; + formatter.dateFormat = localizedFormat; + + return ([formatter stringFromDate:self]); + } + } + + return (nil); +} + @end diff --git a/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.h b/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.h index 395eaab6..b343c316 100644 --- a/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.h +++ b/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.h @@ -18,14 +18,20 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface NSURL (OCURLQueryParameterExtensions) -- (NSURL *)urlByModifyingQueryParameters:(NSMutableArray *(^)(NSMutableArray *queryItems))queryItemsAction; +- (nullable NSURL *)urlByModifyingQueryParameters:(nullable NSMutableArray *(^)(NSMutableArray *queryItems))queryItemsAction; + +- (nullable NSURL *)urlByAppendingQueryParameters:(NSDictionary *)parameters replaceExisting:(BOOL)replaceExisting; -- (NSURL *)urlByAppendingQueryParameters:(NSDictionary *)parameters replaceExisting:(BOOL)replaceExisting; +@property(readonly,nullable) NSDictionary *queryParameters; -- (NSDictionary *)queryParameters; +@property(readonly) NSString *hostAndPort; -- (NSString *)hostAndPort; +@property(readonly,nullable) NSURL *rootURL; //!< Returns just scheme + host, f.ex. "https://owncloud.com/" for "https://owncloud.com/about/" @end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.m b/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.m index dd541de3..f66bada7 100644 --- a/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.m +++ b/ownCloudSDK/Categories/Foundation/NSURL+OCURLQueryParameterExtensions.m @@ -116,4 +116,19 @@ - (NSString *)hostAndPort return (self.host); } +- (NSURL *)rootURL +{ + NSURLComponents *urlComponents; + + if ((urlComponents = [[NSURLComponents alloc] initWithURL:self resolvingAgainstBaseURL:YES]) != nil) + { + urlComponents.path = @"/"; + urlComponents.queryItems = nil; + + return (urlComponents.URL); + } + + return (nil); +} + @end diff --git a/ownCloudSDK/Connection/Capabilities/OCCapabilities.h b/ownCloudSDK/Connection/Capabilities/OCCapabilities.h index 13334d19..2819ce7b 100644 --- a/ownCloudSDK/Connection/Capabilities/OCCapabilities.h +++ b/ownCloudSDK/Connection/Capabilities/OCCapabilities.h @@ -20,6 +20,7 @@ #import "OCChecksumAlgorithm.h" #import "OCShare.h" #import "OCTUSHeader.h" +#import "OCAppProvider.h" NS_ASSUME_NONNULL_BEGIN @@ -57,6 +58,14 @@ typedef NSNumber* OCCapabilityBool; @property(readonly,nullable,nonatomic) NSArray *davReports; @property(readonly,nullable,nonatomic) OCCapabilityBool davPropfindSupportsDepthInfinity; +#pragma mark - Spaces +@property(readonly,nullable,nonatomic) OCCapabilityBool spacesEnabled; +@property(readonly,nullable,nonatomic) NSString *spacesVersion; + +#pragma mark - App Providers +@property(readonly,nullable,nonatomic) NSArray *appProviders; +@property(readonly,nullable,nonatomic) OCAppProvider *latestSupportedAppProvider; //!< Convenience method to return the latest supported and available app provider + #pragma mark - TUS @property(readonly,nonatomic) BOOL tusSupported; @property(readonly,nullable,nonatomic) OCTUSCapabilities tusCapabilities; @@ -74,6 +83,7 @@ typedef NSNumber* OCCapabilityBool; @property(readonly,nullable,nonatomic) NSArray *blacklistedFiles; @property(readonly,nullable,nonatomic) OCCapabilityBool supportsUndelete; @property(readonly,nullable,nonatomic) OCCapabilityBool supportsVersioning; +@property(readonly,nullable,nonatomic) OCCapabilityBool supportsFavorites; #pragma mark - Sharing @property(readonly,nullable,nonatomic) OCCapabilityBool sharingAPIEnabled; @@ -114,6 +124,8 @@ typedef NSNumber* OCCapabilityBool; @property(readonly,nullable,nonatomic) OCCapabilityBool federatedSharingIncoming; @property(readonly,nullable,nonatomic) OCCapabilityBool federatedSharingOutgoing; +@property(readonly,nonatomic) BOOL federatedSharingSupported; + #pragma mark - Notifications @property(readonly,nullable,nonatomic) NSArray *notificationEndpoints; diff --git a/ownCloudSDK/Connection/Capabilities/OCCapabilities.m b/ownCloudSDK/Connection/Capabilities/OCCapabilities.m index 22557245..726e2d21 100644 --- a/ownCloudSDK/Connection/Capabilities/OCCapabilities.m +++ b/ownCloudSDK/Connection/Capabilities/OCCapabilities.m @@ -29,6 +29,9 @@ @interface OCCapabilities() OCTUSHeader *_tusCapabilitiesHeader; NSArray *_tusVersions; NSArray *_tusExtensions; + + NSArray *_appProviders; + OCAppProvider *_latestSupportedAppProvider; } @end @@ -250,6 +253,87 @@ - (OCCapabilityBool)davPropfindSupportsDepthInfinity return (OCTypedCast(_capabilities[@"dav"][@"propfind"][@"depth_infinity"], NSNumber)); } +#pragma mark - Spaces +- (OCCapabilityBool)spacesEnabled +{ + return (OCTypedCast(_capabilities[@"spaces"][@"enabled"], NSNumber)); +} + +- (NSString *)spacesVersion +{ + return (OCTypedCast(_capabilities[@"spaces"][@"version"], NSString)); +} + +#pragma mark - App Providers +- (NSArray *)appProviders +{ + if (_appProviders == nil) + { + NSArray *> *jsonAppProviders; + NSMutableArray *appProviders = [NSMutableArray new]; + + if ((jsonAppProviders = OCTypedCast(_capabilities[@"files"][@"app_providers"], NSArray)) != nil) + { + for (id jsonAppProviderEntry in jsonAppProviders) + { + NSDictionary *jsonAppProviderDict; + + if ((jsonAppProviderDict = OCTypedCast(jsonAppProviderEntry, NSDictionary)) != nil) + { + NSNumber *enabledNumber = OCTypedCast(jsonAppProviderDict[@"enabled"], NSNumber); + NSString *versionString = OCTypedCast(jsonAppProviderDict[@"version"], NSString); + NSString *appsURLString = OCTypedCast(jsonAppProviderDict[@"apps_url"], NSString); + NSString *openURLString = OCTypedCast(jsonAppProviderDict[@"open_url"], NSString); + NSString *openWebURLString = OCTypedCast(jsonAppProviderDict[@"open_web_url"], NSString); + NSString *newURLString = OCTypedCast(jsonAppProviderDict[@"new_url"], NSString); + + if ((enabledNumber != nil) && (versionString != nil)) + { + OCAppProvider *appProvider = [OCAppProvider new]; + + appProvider.enabled = enabledNumber.boolValue; + appProvider.version = versionString; + appProvider.appsURLPath = appsURLString; + appProvider.openURLPath = openURLString; + appProvider.openWebURLPath = openWebURLString; + appProvider.createURLPath = newURLString; + + [appProviders addObject:appProvider]; + } + } + } + } + + if (appProviders.count > 0) + { + _appProviders = appProviders; + } + } + + return (_appProviders); +} + +- (OCAppProvider *)latestSupportedAppProvider +{ + if (_latestSupportedAppProvider == nil) + { + OCAppProvider *latestSupportedAppProvider = nil; + + for (OCAppProvider *appProvider in self.appProviders) + { + if (appProvider.isSupported) + { + // Assume that versions are returned in ascending order (simple first implementation) + latestSupportedAppProvider = appProvider; + } + } + + _latestSupportedAppProvider = latestSupportedAppProvider; + } + + return (_latestSupportedAppProvider); +} + #pragma mark - TUS - (BOOL)tusSupported { @@ -349,6 +433,11 @@ - (OCCapabilityBool)supportsVersioning return (OCTypedCast(_capabilities[@"files"][@"versioning"], NSNumber)); } +- (OCCapabilityBool)supportsFavorites +{ + return (OCTypedCast(_capabilities[@"files"][@"favorites"], NSNumber)); +} + #pragma mark - Sharing - (OCCapabilityBool)sharingAPIEnabled { @@ -499,6 +588,17 @@ - (OCCapabilityBool)federatedSharingOutgoing return (OCTypedCast(_capabilities[@"files_sharing"][@"federation"][@"outgoing"], NSNumber)); } +- (BOOL)federatedSharingSupported +{ + if (self.spacesEnabled.boolValue) + { + // ocis bug: can't depend on federatedSharingIncoming and federatedSharingOutgoing: https://github.com/owncloud/ocis/issues/4788 + return (NO); + } + + return (self.federatedSharingIncoming.boolValue || self.federatedSharingOutgoing.boolValue); +} + #pragma mark - Notifications - (NSArray *)notificationEndpoints { diff --git a/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.h b/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.h new file mode 100644 index 00000000..03149cef --- /dev/null +++ b/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.h @@ -0,0 +1,42 @@ +// +// OCConnection+GraphAPI.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "OCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCDrive; + +typedef void(^OCRetrieveDriveListCompletionHandler)(NSError * _Nullable error, NSArray * _Nullable drives); + +@interface OCConnection (GraphAPI) + +#pragma mark - Drives +@property(strong,nullable) NSArray *drives; //!< Current list of known drives + +- (nullable OCDrive *)driveWithID:(OCDriveID)driveID; //!< Returns the OCDrive instance for the provided ID if it is already known locally +- (void)driveWithID:(OCDriveID)driveID completionHandler:(void(^)(OCDrive * _Nullable drive))completionHandler; //!< Returns the OCDrive instance for the provided ID. Will attempt to retrieve it from the server if not known locally. + +- (nullable NSProgress *)retrieveDriveListWithCompletionHandler:(OCRetrieveDriveListCompletionHandler)completionHandler; //!< Retrieves a list of drives and updates .drives + +@end + +extern OCConnectionEndpointID OCConnectionEndpointIDGraphMeDrives; +extern OCConnectionEndpointID OCConnectionEndpointIDGraphDrives; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.m b/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.m new file mode 100644 index 00000000..9a292dc7 --- /dev/null +++ b/ownCloudSDK/Connection/GraphAPI/OCConnection+GraphAPI.m @@ -0,0 +1,121 @@ +// +// OCConnection+GraphAPI.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "OCConnection+GraphAPI.h" +#import "OCConnection+OData.h" +#import "GAIdentitySet.h" +#import "GADrive.h" +#import "GAODataError.h" +#import "GAODataErrorMain.h" +#import "GAGraphData+Decoder.h" +#import "OCMacros.h" + +@implementation OCConnection (GraphAPI) + +- (NSArray *)drives +{ + @synchronized (_drivesByID) + { + return (_drives); + } +} + +- (void)setDrives:(NSArray *)drives +{ + NSMutableDictionary *drivesByID = [NSMutableDictionary new]; + + for (OCDrive *drive in drives) + { + if (drive.identifier != nil) + { + drivesByID[drive.identifier] = drive; + } + } + + @synchronized (_drivesByID) + { + _drives = drives; + [_drivesByID setDictionary:drivesByID]; + } +} + +- (OCDrive *)driveWithID:(OCDriveID)driveID +{ + @synchronized (_drivesByID) + { + return (_drivesByID[driveID]); + } +} + +- (void)driveWithID:(OCDriveID)driveID completionHandler:(void (^)(OCDrive * _Nullable))completionHandler +{ + OCDrive *drive; + + if ((drive = [self driveWithID:driveID]) != nil) + { + completionHandler(drive); + } + else + { + [self retrieveDriveListWithCompletionHandler:^(NSError * _Nullable error, NSArray * _Nullable drives) { + completionHandler([self driveWithID:driveID]); + }]; + } +} + +- (nullable NSProgress *)retrieveDriveListWithCompletionHandler:(OCRetrieveDriveListCompletionHandler)completionHandler +{ + return ([self requestODataAtURL:[self URLForEndpoint:OCConnectionEndpointIDGraphMeDrives options:nil] requireSignals:[NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable] selectEntityID:nil selectProperties:nil filterString:nil entityClass:GADrive.class completionHandler:^(NSError * _Nullable error, id _Nullable response) { + NSMutableArray *ocDrives = nil; + + if (error == nil) + { + // Convert GADrives to OCDrives + NSArray *gaDrives; + + if ((gaDrives = OCTypedCast(response, NSArray)) != nil) + { + ocDrives = [NSMutableArray new]; + + for (GADrive *drive in gaDrives) + { + OCDrive *ocDrive; + + if ((ocDrive = [OCDrive driveFromGADrive:drive]) != nil) + { + [ocDrives addObject:ocDrive]; + } + } + + if (ocDrives.count > 0) + { + self.drives = ocDrives; + } + } + } + + OCLogDebug(@"Drives response: drives=%@, error=%@", ocDrives, error); + + completionHandler(error, (ocDrives.count > 0) ? ocDrives : nil); + }]); +} + +@end + +OCConnectionEndpointID OCConnectionEndpointIDGraphMeDrives = @"meDrives"; +OCConnectionEndpointID OCConnectionEndpointIDGraphDrives = @"drives"; diff --git a/ownCloudSDK/Connection/NSError+OCISError.h b/ownCloudSDK/Connection/NSError+OCISError.h new file mode 100644 index 00000000..df91d43b --- /dev/null +++ b/ownCloudSDK/Connection/NSError+OCISError.h @@ -0,0 +1,31 @@ +// +// NSError+OCISError.h +// ownCloudSDK +// +// Created by Felix Schwarz on 19.09.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 "NSError+OCError.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface NSError (OCISError) + ++ (nullable NSError *)errorFromOCISErrorDictionary:(NSDictionary *)ocisErrorDict underlyingError:(nullable NSError *)underlyingError; + +@end + +extern NSErrorUserInfoKey OCOcisErrorCodeKey; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Connection/NSError+OCISError.m b/ownCloudSDK/Connection/NSError+OCISError.m new file mode 100644 index 00000000..c8247265 --- /dev/null +++ b/ownCloudSDK/Connection/NSError+OCISError.m @@ -0,0 +1,81 @@ +// +// NSError+OCISError.m +// ownCloudSDK +// +// Created by Felix Schwarz on 19.09.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 "NSError+OCISError.h" +#import "OCMacros.h" + +@implementation NSError (OCISError) + ++ (nullable NSError *)errorFromOCISErrorDictionary:(NSDictionary *)ocisErrorDict underlyingError:(nullable NSError *)underlyingError +{ + NSError *error = nil; + + if ([ocisErrorDict isKindOfClass:NSDictionary.class]) + { + NSString *ocisCode; + + if ((ocisCode = OCTypedCast(ocisErrorDict[@"code"], NSString)) != nil) + { + NSMutableDictionary *errorUserInfo = [NSMutableDictionary new]; + NSString *message = nil; + OCError errorCode = OCErrorUnknown; + + errorUserInfo[OCOcisErrorCodeKey] = ocisCode; + + if ((message = OCTypedCast(ocisErrorDict[@"message"], NSString)) != nil) + { + errorUserInfo[NSLocalizedDescriptionKey] = message; + } + + // via https://owncloud.dev/services/app-registry/apps/ + if ([ocisCode isEqual:@"RESOURCE_NOT_FOUND"]) + { + errorCode = OCErrorResourceNotFound; + } + + // via https://owncloud.dev/services/app-registry/apps/ + if ([ocisCode isEqual:@"INVALID_PARAMETER"]) + { + errorCode = OCErrorInvalidParameter; + } + + if ([ocisCode isEqual:@"TOO_EARLY"]) + { + errorCode = OCErrorItemProcessing; + } + + if (underlyingError != nil) + { + errorUserInfo[NSUnderlyingErrorKey] = underlyingError; + } + + error = [NSError errorWithDomain:OCErrorDomain code:errorCode userInfo:errorUserInfo]; + } + } + + if (error == nil) + { + error = underlyingError; + } + + return (error); +} + +@end + +NSErrorUserInfoKey OCOcisErrorCodeKey = @"ocisErrorCode"; diff --git a/ownCloudSDK/Connection/OCConnection+AppProviders.m b/ownCloudSDK/Connection/OCConnection+AppProviders.m new file mode 100644 index 00000000..c25d6eb5 --- /dev/null +++ b/ownCloudSDK/Connection/OCConnection+AppProviders.m @@ -0,0 +1,424 @@ +// +// OCConnection+AppProviders.m +// ownCloudSDK +// +// Created by Felix Schwarz on 05.09.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 "OCConnection.h" +#import "NSError+OCError.h" +#import "NSError+OCISError.h" +#import "OCMacros.h" +#import "OCAppProviderApp.h" +#import "NSDictionary+OCFormEncoding.h" +#import "OCLocale+SystemLanguage.h" + +@implementation OCConnection (AppProviders) + +#pragma mark - App List +- (nullable NSProgress *)retrieveAppProviderListWithCompletionHandler:(void(^)(NSError * _Nullable error, OCAppProvider * _Nullable appProvider))completionHandler +{ + OCAppProvider *appProvider; + NSProgress *progress = nil; + + if ((appProvider = self.capabilities.latestSupportedAppProvider) != nil) + { + NSURL *appListURL = [self URLForEndpoint:OCConnectionEndpointIDAppProviderList options:nil]; + OCHTTPRequest *request; + + if ((request = [OCHTTPRequest requestWithURL:appListURL]) != nil) + { + request.requiredSignals = [NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable]; + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + NSData *responseBody = response.bodyData; + + if ((error == nil) && response.status.isSuccess && (responseBody!=nil)) + { + NSDictionary *rawJSON; + + if ((rawJSON = [NSJSONSerialization JSONObjectWithData:responseBody options:0 error:&error]) != nil) + { + OCAppProviderAppList appList; + + if ((appList = OCTypedCast(rawJSON[@"mime-types"], NSArray)) != nil) + { + appProvider.appList = appList; + } + else + { + error = OCError(OCErrorResponseUnknownFormat); + } + } + } + else + { + if (error == nil) + { + error = response.status.error; + } + } + + completionHandler(error, (error == nil) ? appProvider : nil); + }]; + } + else + { + completionHandler(OCError(OCErrorInternal), nil); + } + } + else + { + completionHandler(OCError(OCErrorFeatureNotSupportedByServer), nil); + } + + return (progress); +} + +#pragma mark - Create App Document +- (nullable NSProgress *)createAppFileOfType:(OCAppProviderFileType *)appType in:(OCItem *)parentDirectoryItem withName:(NSString *)fileName completionHandler:(nonnull void (^)(NSError * _Nullable, OCFileID _Nullable, OCItem * _Nullable))completionHandler +{ + NSProgress *progress = nil; + NSURL *appListURL = [self URLForEndpoint:OCConnectionEndpointIDAppProviderNew options:nil]; + OCHTTPRequest *request; + + if ((request = [OCHTTPRequest requestWithURL:appListURL]) != nil) + { + request.method = OCHTTPMethodPOST; + request.requiredSignals = [NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable]; + request.parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: + parentDirectoryItem.fileID, @"parent_container_id", + fileName, @"filename", + nil]; + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + NSData *responseBody = response.bodyData; + OCFileID fileID = nil; + + if ((error == nil) && response.status.isSuccess && (responseBody!=nil)) + { + id rawJSON; + + if ((rawJSON = [NSJSONSerialization JSONObjectWithData:responseBody options:0 error:&error]) != nil) + { + NSDictionary *jsonResponse; + + if (!(((jsonResponse = OCTypedCast(rawJSON, NSDictionary)) != nil) && + ((fileID = OCTypedCast(jsonResponse[@"file_id"], NSString)) != nil))) + { + error = OCError(OCErrorResponseUnknownFormat); + } + } + } + else + { + if (error == nil) + { + error = response.status.error; + } + } + + completionHandler(error, fileID, nil); + }]; + } + else + { + completionHandler(OCError(OCErrorInternal), nil, nil); + } + + return (progress); +} + +#pragma mark - Open +- (nullable NSProgress *)openInApp:(OCItem *)item withApp:(nullable OCAppProviderApp *)app viewMode:(nullable OCAppProviderViewMode)viewMode completionHandler:(void(^)(NSError * _Nullable error, NSURL * _Nullable appURL, OCHTTPMethod _Nullable httpMethod, OCHTTPHeaderFields _Nullable headerFields, OCHTTPRequestParameters _Nullable parameters, NSMutableURLRequest * _Nullable urlRequest))completionHandler +{ + NSProgress *progress = nil; + NSURL *appListURL = [self URLForEndpoint:OCConnectionEndpointIDAppProviderOpen options:nil]; + OCHTTPRequest *request; + + if ((request = [OCHTTPRequest requestWithURL:appListURL]) != nil) + { + OCHTTPStaticHeaderFields staticHeaderFields = self.staticHeaderFields; + + request.method = OCHTTPMethodPOST; + request.requiredSignals = [NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable]; + request.parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: + item.fileID, @"file_id", + app.name, @"app_name", + nil]; + + NSString *primaryLanguage = OCLocale.sharedLocale.primaryUserLanguage; + + if (primaryLanguage != nil) + { + if (primaryLanguage.length > 2) + { + // Ensure ISO-639-1 (uses only 2 characters) by cutting off any differentiators (f.ex. "en-GB" becomes "en") + primaryLanguage = [primaryLanguage substringToIndex:2]; + } + + [request addParameters:@{ + @"lang" : primaryLanguage + }]; + } + + if (viewMode != nil) + { + [request addParameters:@{ + @"view_mode" : viewMode + }]; + } + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + NSData *responseBody = response.bodyData; + NSURL *appURL = nil; + OCHTTPMethod httpMethod = nil; + NSDictionary *jsonResponse = nil; + NSError *jsonError = nil; + OCHTTPRequestParameters requestParameters = nil; + OCHTTPHeaderFields requestHeaderFields = nil; + + if ((error == nil) && (responseBody != nil)) + { + id rawJSON = nil; + + if ((rawJSON = [NSJSONSerialization JSONObjectWithData:responseBody options:0 error:&jsonError]) != nil) + { + if ((jsonResponse = OCTypedCast(rawJSON, NSDictionary)) == nil) + { + error = OCErrorFromError(OCErrorResponseUnknownFormat, response.status.error); + } + } + } + + if ((error == nil) && response.status.isSuccess && (jsonResponse != nil)) + { + NSString *appURLString; + NSString *methodString; + + if ((appURLString = OCTypedCast(jsonResponse[@"app_url"], NSString)) != nil) + { + appURL = [NSURL URLWithString:appURLString]; + } + + if ((methodString = OCTypedCast(jsonResponse[@"method"], NSString)) != nil) + { + if ([methodString isEqual:@"GET"]) { httpMethod = OCHTTPMethodGET; } + if ([methodString isEqual:@"POST"]) { httpMethod = OCHTTPMethodPOST; } + } + + if (staticHeaderFields.count > 0) + { + requestHeaderFields = [[NSMutableDictionary alloc] initWithDictionary:staticHeaderFields]; + } + + if ([httpMethod isEqual:OCHTTPMethodGET]) + { + NSDictionary *rawHeaders; + + if ((rawHeaders = OCTypedCast(jsonResponse[@"headers"], NSDictionary)) != nil) + { + if (requestHeaderFields == nil) { requestHeaderFields = [NSMutableDictionary new]; } + + [rawHeaders enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + NSString *stringKey, *stringValue; + + if (((stringKey = OCTypedCast(key, NSString)) != nil) && ((stringValue = OCTypedCast(obj, NSString)) != nil)) + { + requestHeaderFields[stringKey] = stringValue; + } + else + { + OCLogWarning(@"App Provider Open response 'headers' contained non-string key or value: key=%@, value=%@", key, obj); + } + }]; + } + } + + if ([httpMethod isEqual:OCHTTPMethodPOST]) + { + NSDictionary *rawRequestParameters; + + if ((rawRequestParameters = OCTypedCast(jsonResponse[@"form_parameters"], NSDictionary)) != nil) + { + requestParameters = [NSMutableDictionary new]; + + [rawRequestParameters enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + NSString *stringKey, *stringValue; + + if (((stringKey = OCTypedCast(key, NSString)) != nil) && ((stringValue = OCTypedCast(obj, NSString)) != nil)) + { + requestParameters[stringKey] = stringValue; + } + else + { + OCLogWarning(@"App Provider Open response 'form_parameters' contained non-string key or value: key=%@, value=%@", key, obj); + } + }]; + } + } + } + else if (error == nil) + { + NSError *fallbackError = nil; + + switch (response.status.code) + { + case OCHTTPStatusCodeTOO_EARLY: + error = fallbackError = OCErrorWithDescription(OCErrorItemProcessing, OCLocalized(@"This file is currently being processed and is not yet available for use. Please try again shortly.")); + break; + + default: + fallbackError = response.status.error; + break; + } + + if (error == nil) + { + error = [NSError errorFromOCISErrorDictionary:jsonResponse underlyingError:fallbackError]; + } + } + + if (error == nil) + { + if (appURL == nil) + { + error = OCErrorWithDescription(OCErrorResponseUnknownFormat, @"Mandatory 'app_url' information missing or invalid."); + } + if (httpMethod == nil) + { + error = OCErrorWithDescription(OCErrorResponseUnknownFormat, @"Mandatory 'method' information missing or invalid."); + } + } + + if (error == nil) + { + NSMutableURLRequest *urlRequest = nil; + + if ((appURL != nil) && (httpMethod != nil)) // error-checked above already, this condition is only here to satisfy the static analyzer + { + // Apply URL + HTTP method + urlRequest = [NSMutableURLRequest requestWithURL:appURL]; + urlRequest.HTTPMethod = httpMethod; + } + + if (requestHeaderFields != nil) + { + // Apply HTTP headers + [urlRequest setAllHTTPHeaderFields:requestHeaderFields]; + } + if (requestParameters != nil) + { + // Encode and apply POST parameters + [urlRequest setHTTPBody:[requestParameters urlFormEncodedData]]; + [urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:OCHTTPHeaderFieldNameContentType]; + } + + completionHandler(error, appURL, httpMethod, requestHeaderFields, requestParameters, urlRequest); + } + else + { + completionHandler(error, nil, nil, nil, nil, nil); + } + }]; + } + else + { + completionHandler(OCError(OCErrorInternal), nil, nil, nil, nil, nil); + } + + return (progress); +} + +#pragma mark - Open in Web +- (nullable NSProgress *)openInWeb:(OCItem *)item withApp:(nullable OCAppProviderApp *)app completionHandler:(void(^)(NSError * _Nullable error, NSURL * _Nullable webURL))completionHandler +{ + NSProgress *progress = nil; + NSURL *appListURL = [self URLForEndpoint:OCConnectionEndpointIDAppProviderOpenWeb options:nil]; + OCHTTPRequest *request; + + if ((request = [OCHTTPRequest requestWithURL:appListURL]) != nil) + { + request.method = OCHTTPMethodPOST; + request.requiredSignals = [NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable]; + request.parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: + item.fileID, @"file_id", + app.name, @"app_name", + nil]; + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + NSData *responseBody = response.bodyData; + NSURL *webURL = nil; + NSDictionary *jsonResponse = nil; + NSError *jsonError = nil; + + if ((error == nil) && (responseBody != nil)) + { + id rawJSON = nil; + + if ((rawJSON = [NSJSONSerialization JSONObjectWithData:responseBody options:0 error:&jsonError]) != nil) + { + if ((jsonResponse = OCTypedCast(rawJSON, NSDictionary)) == nil) + { + error = OCErrorFromError(OCErrorResponseUnknownFormat, response.status.error); + } + } + } + + if ((error == nil) && response.status.isSuccess && (jsonResponse != nil)) + { + NSString *uri; + + if ((uri = OCTypedCast(jsonResponse[@"uri"], NSString)) != nil) + { + webURL = [NSURL URLWithString:uri]; + } + else + { + error = OCErrorWithDescription(OCErrorResponseUnknownFormat, @"Mandatory 'uri' information missing from response."); + } + } + else + { + NSError *fallbackError = nil; + + switch (response.status.code) + { + case OCHTTPStatusCodeTOO_EARLY: + error = fallbackError = OCErrorWithDescription(OCErrorItemProcessing, OCLocalized(@"This file is currently being processed and is not yet available for use. Please try again shortly.")); + break; + + default: + fallbackError = response.status.error; + break; + } + + if (error == nil) + { + error = [NSError errorFromOCISErrorDictionary:jsonResponse underlyingError:fallbackError]; + } + } + + completionHandler(error, (error == nil) ? webURL : nil); + }]; + } + else + { + completionHandler(OCError(OCErrorInternal), nil); + } + + return (progress); +} + +@end diff --git a/ownCloudSDK/Connection/OCConnection+Authentication.m b/ownCloudSDK/Connection/OCConnection+Authentication.m index f1635af4..21836f7a 100644 --- a/ownCloudSDK/Connection/OCConnection+Authentication.m +++ b/ownCloudSDK/Connection/OCConnection+Authentication.m @@ -44,7 +44,8 @@ - (void)requestSupportedAuthenticationMethodsWithOptions:(OCAuthenticationMethod NSMutableSet *detectionRequestAuthDetectionIDs = [NSMutableSet new]; NSMutableDictionary *> *detectionIDsByMethod = [NSMutableDictionary dictionary]; NSMutableDictionary *detectionRequestsByDetectionID = [NSMutableDictionary dictionary]; - + NSArray *allowedMethodIdentifiers = options[OCAuthenticationMethodAllowedMethods]; + if (completionHandler==nil) { return; } // Add OCAuthenticationMethodAllowURLProtocolUpgrades support @@ -77,9 +78,14 @@ - (void)requestSupportedAuthenticationMethodsWithOptions:(OCAuthenticationMethod if ((authMethodIdentifier = [authenticationMethodClass identifier]) != nil) { + if ((allowedMethodIdentifiers != nil) && ![allowedMethodIdentifiers containsObject:authMethodIdentifier]) + { + break; + } + NSArray *authMethodDetectionRequests; - if ((authMethodDetectionRequests = [authenticationMethodClass detectionRequestsForConnection:self]) != nil) + if ((authMethodDetectionRequests = [authenticationMethodClass detectionRequestsForConnection:self options:options]) != nil) { NSMutableArray *detectionIDs = [NSMutableArray new]; @@ -145,6 +151,11 @@ - (void)requestSupportedAuthenticationMethodsWithOptions:(OCAuthenticationMethod if ((authMethodIdentifier = [authenticationMethodClass identifier]) != nil) { + if ((allowedMethodIdentifiers != nil) && ![allowedMethodIdentifiers containsObject:authMethodIdentifier]) + { + break; + } + NSMutableDictionary *resultsByURL = [NSMutableDictionary dictionary]; // Compile pre-load results diff --git a/ownCloudSDK/Connection/OCConnection+Avatars.m b/ownCloudSDK/Connection/OCConnection+Avatars.m new file mode 100644 index 00000000..55ed3002 --- /dev/null +++ b/ownCloudSDK/Connection/OCConnection+Avatars.m @@ -0,0 +1,95 @@ +// +// OCConnection+Avatars.m +// ownCloudSDK +// +// Created by Felix Schwarz on 29.09.20. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2020, 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 "OCConnection.h" +#import "OCAvatar.h" +#import "NSError+OCError.h" +#import "OCHTTPResponse+DAVError.h" + +@implementation OCConnection (Avatars) + +/* + Endpoint notes: + - the response has either of the following codes: + - 200 OK: success (+ content and Content-Type) + - 304 NOT MODIFIED: the avatar hasn't changed (ETag is the same as provided via If-None-Match header) (+ content and Content-Type) + - 404 NOT FOUND: no avatar available + - the response comes with an ETag that only changes when the avatar is changed +*/ + +- (nullable NSProgress *)retrieveAvatarForUser:(OCUser *)user existingETag:(nullable OCFileETag)eTag withSize:(CGSize)size completionHandler:(void(^)(NSError * _Nullable error, BOOL unchanged, OCAvatar * _Nullable avatar))completionHandler +{ + OCHTTPRequest *request; + NSProgress *progress = nil; + NSURL *avatarURL = [[[self URLForEndpoint:OCConnectionEndpointIDAvatars options:nil] URLByAppendingPathComponent:user.userName] URLByAppendingPathComponent:[@((NSInteger)size.width).stringValue stringByAppendingString:@".png"] isDirectory:NO]; + + request = [OCHTTPRequest requestWithURL:avatarURL]; + request.requiredSignals = [NSSet setWithObject:OCConnectionSignalIDAuthenticationAvailable]; + + if (eTag != nil) + { + [request addHeaderFields:@{ + @"If-None-Match" : eTag + }]; + } + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + if (error != nil) + { + completionHandler(error, NO, nil); + return; + } + + switch (response.status.code) + { + case OCHTTPStatusCodeNOT_MODIFIED: + case OCHTTPStatusCodeOK: { + OCAvatar *avatar = [[OCAvatar alloc] init]; + + avatar.userIdentifier = user.userIdentifier; + avatar.eTag = response.headerFields[OCHTTPHeaderFieldNameETag]; + avatar.mimeType = response.contentType; + avatar.data = response.bodyData; + avatar.timestamp = [NSDate new]; + + completionHandler(nil, (response.status.code == OCHTTPStatusCodeNOT_MODIFIED), avatar); + } + break; + + case OCHTTPStatusCodeNOT_FOUND: + completionHandler(OCError(OCErrorResourceDoesNotExist), NO, nil); + break; + + default: { + NSError *responseError; + + if ((responseError = response.bodyParsedAsDAVError) == nil) + { + responseError = response.status.error; + } + + completionHandler(responseError, NO, nil); + } + break; + } + }]; + + return (progress); +} + +@end diff --git a/ownCloudSDK/Connection/OCConnection+Compatibility.m b/ownCloudSDK/Connection/OCConnection+Compatibility.m index 963455cc..822a1bf8 100644 --- a/ownCloudSDK/Connection/OCConnection+Compatibility.m +++ b/ownCloudSDK/Connection/OCConnection+Compatibility.m @@ -187,9 +187,9 @@ - (BOOL)runsServerVersionOrHigher:(NSString *)version return ([self.serverVersion compareVersionWith:version] >= NSOrderedSame); } -- (BOOL)supportsPreviewAPI +- (BOOL)useDriveAPI { - return ([self runsServerVersionOrHigher:@"10.0.9"]); + return (self.capabilities.spacesEnabled.boolValue || [self.bookmark hasCapability:OCBookmarkCapabilityDrives]); } #pragma mark - Checks @@ -214,6 +214,18 @@ - (NSError *)supportsServerVersion:(NSString *)serverVersion product:(NSString * { if ([serverVersion compareVersionWith:minimumVersion] == NSOrderedAscending) { + // ocis public beta special handling + + // NSArray *versionSegments = [serverVersion componentsSeparatedByString:@"."]; + // ocis returns four digit version numbers, with 2 being the major version + // if ((versionSegments.count == 4) && [versionSegments.firstObject isEqual:@"2"]) + + if ([serverVersion isEqual:@"0.0.0.0"] || [serverVersion isEqual:@"2.0.0.0"]) + { + // ocis public beta exception + return (nil); + } + return ([NSError errorWithDomain:OCErrorDomain code:OCErrorServerVersionNotSupported userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:OCLocalizedString(@"This server runs an unsupported version (%@). Version %@ or later is required by this app.", @""), longVersion, minimumVersion] }]); diff --git a/ownCloudSDK/Connection/OCConnection+Recipients.m b/ownCloudSDK/Connection/OCConnection+Recipients.m index b3377ab7..24c838d9 100644 --- a/ownCloudSDK/Connection/OCConnection+Recipients.m +++ b/ownCloudSDK/Connection/OCConnection+Recipients.m @@ -27,11 +27,11 @@ @implementation OCConnection (Recipients) -- (NSMutableArray *)_recipientsFromJSONArray:(NSArray *> *)jsonArray matchType:(OCRecipientMatchType)matchType addToArray:(NSMutableArray *)recipientsArray +- (NSMutableArray *)_recipientsFromJSONArray:(NSArray *> *)jsonArray matchType:(OCRecipientMatchType)matchType addToArray:(NSMutableArray *)recipientsArray { for (NSDictionary *recipientDict in jsonArray) { - OCRecipient *recipient = nil; + OCIdentity *recipient = nil; NSString *label = recipientDict[@"label"]; @@ -45,15 +45,15 @@ @implementation OCConnection (Recipients) switch ((OCShareType)shareTypeID.integerValue) { case OCShareTypeUserShare: - recipient = [[OCRecipient recipientWithUser:[OCUser userWithUserName:shareWith displayName:label isRemote:NO]] withSearchResultName:shareWithAdditionalInfo]; + recipient = [[OCIdentity identityWithUser:[OCUser userWithUserName:shareWith displayName:label isRemote:NO]] withSearchResultName:shareWithAdditionalInfo]; break; case OCShareTypeRemote: - recipient = [[OCRecipient recipientWithUser:[OCUser userWithUserName:shareWith displayName:label isRemote:YES]] withSearchResultName:shareWithAdditionalInfo]; + recipient = [[OCIdentity identityWithUser:[OCUser userWithUserName:shareWith displayName:label isRemote:YES]] withSearchResultName:shareWithAdditionalInfo]; break; case OCShareTypeGroupShare: - recipient = [[OCRecipient recipientWithGroup:[OCGroup groupWithIdentifier:shareWith name:label]] withSearchResultName:shareWithAdditionalInfo]; + recipient = [[OCIdentity identityWithGroup:[OCGroup groupWithIdentifier:shareWith name:label]] withSearchResultName:shareWithAdditionalInfo]; break; default: @@ -115,7 +115,7 @@ - (nullable NSProgress *)retrieveRecipientsForItemType:(OCItemType)itemType ofSh progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { NSError *jsonError = nil; NSDictionary *jsonDictionary; - NSMutableArray *recipients = nil; + NSMutableArray *recipients = nil; if ((jsonDictionary = [response bodyConvertedDictionaryFromJSONWithError:&jsonError]) != nil) { diff --git a/ownCloudSDK/Connection/OCConnection+Setup.m b/ownCloudSDK/Connection/OCConnection+Setup.m index b4afdc8a..da2de166 100644 --- a/ownCloudSDK/Connection/OCConnection+Setup.m +++ b/ownCloudSDK/Connection/OCConnection+Setup.m @@ -21,18 +21,33 @@ #import "NSURL+OCURLNormalization.h" #import "OCServerLocator.h" #import "OCMacros.h" +#import "OCAuthenticationMethodOpenIDConnect.h" +#import "OCServerInstance.h" +#import "OCHTTPPipelineManager.h" +#import "NSURL+OCURLQueryParameterExtensions.h" + +@interface OCAuthenticationMethod (RequestAuthentication) ++ (OCHTTPRequest *)authorizeRequest:(OCHTTPRequest *)request withAuthenticationMethodIdentifier:(OCAuthenticationMethodIdentifier)authenticationMethodIdentifier authenticationData:(NSData *)authenticationData; +@end @implementation OCConnection (Setup) #pragma mark - Prepare for setup -- (void)prepareForSetupWithOptions:(NSDictionary *)options completionHandler:(void(^)(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods))completionHandler +- (void)prepareForSetupWithOptions:(NSDictionary *)options completionHandler:(void(^)(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions generationOptions))completionHandler { /* Setup preparation steps overview: 1) Perform server location if username is provided and flag for it is passed - 2) Query [url]/status.php. + 2) Query [url]/.well-known/webfinger?resource=https%3A%2F%2Furl + - Error -> ignore + - Success (Example: `{"subject":"acct:me","links":[{"rel":"http://openid.net/specs/connect/1.0/issuer","href":"https://ocis.ocis-wopi.latest.owncloud.works"}]}` ) + - links[rel=http://openid.net/specs/connect/1.0/issuer]['href'] in JSON response? + -> use as new base URL, OpenID Connect should be found via [new base URL]/.well-known/openid-configuration by requestSupportedAuthenticationMethodsWithOptions… + -> skip step 3 + + 3) Query [url]/status.php. - Redirect? Create issue, follow redirect, restart at 1). - Error (no OC status.php content): - check if [url] last path component is "owncloud". @@ -40,9 +55,9 @@ - (void)prepareForSetupWithOptions:(NSDictionary - if YES: load [url] directly and check if there's a redirection: - if YES: create a redirect issue, use redirection URL as [url] and repeat step 1 - if NO: create error issue - - Success (OC status.php content): proceed to step 2 + - Success (OC status.php content): proceed to step 4 - 3) Send PROPFIND to [finalurl]/remote.php/dav/files to determine available authentication mechanisms (=> use -requestSupportedAuthenticationMethodsWithOptions:.. for this) + 4) Send authentication method provided requests and parse the responses to determine available authentication mechanism via -requestSupportedAuthenticationMethodsWithOptions:.. */ // Since this is far easier to implement when making requests synchronously, move the whole method to a private method and execute it asynchronously on a global queue @@ -51,13 +66,16 @@ - (void)prepareForSetupWithOptions:(NSDictionary }); } -- (void)_prepareForSetupWithOptions:(NSDictionary *)options completionHandler:(void(^)(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods))completionHandler +- (void)_prepareForSetupWithOptions:(NSDictionary *)options completionHandler:(void(^)(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions generationOptions))completionHandler { NSString *statusEndpointPath = [self classSettingForOCClassSettingsKey:OCConnectionEndpointIDStatus]; NSMutableArray *issues = [NSMutableArray new]; NSMutableSet *certificatesUsedInIssues = [NSMutableSet new]; __block NSUInteger requestCount=0, maxRequestCount = 30; OCServerLocatorIdentifier serverLocatorIdentifier = OCServerLocator.useServerLocatorIdentifier; + NSURL *webFingerAccountInfoURL = nil; + NSURL *webFingerAlternativeIDPBaseURL = nil; + NSURL *refererForIDPURL = nil; // Tools void (^AddIssue)(OCIssue *issue) = ^(OCIssue *issue) { @@ -136,8 +154,7 @@ - (void)_prepareForSetupWithOptions:(NSDictionary_bookmark.certificate = certificate; - self->_bookmark.certificateModificationDate = [NSDate date]; + [self->_bookmark.certificateStore storeCertificate:certificate forHostname:request.hostname]; } }]); } @@ -148,8 +165,7 @@ - (void)_prepareForSetupWithOptions:(NSDictionary_bookmark.certificate = certificate; - self->_bookmark.certificateModificationDate = [NSDate date]; + [self->_bookmark.certificateStore storeCertificate:certificate forHostname:request.hostname]; } }]); @@ -168,11 +184,15 @@ - (void)_prepareForSetupWithOptions:(NSDictionary *allowedAuthenticationMethodIdentifiers = [self classSettingForOCClassSettingsKey:OCConnectionAllowedAuthenticationMethodIDs]; + + // - check that OIDC is allowed - and only perform WebFinger if it is + if ((allowedAuthenticationMethodIdentifiers == nil) || // default: all allowed + ((allowedAuthenticationMethodIdentifiers != nil) && [allowedAuthenticationMethodIdentifiers containsObject:OCAuthenticationMethodIdentifierOpenIDConnect])) // list provided, only continue if OIDC is part of it + { + webfingerRootURL = url.rootURL; + } + + // - query [url]/.well-known/webfinger?resource=https%3A%2F%2Furl - WebFinger https://github.com/owncloud/enterprise/issues/5579 + if (webfingerRootURL != nil) + { + NSURL *webFingerLookupURL = [[url URLByAppendingPathComponent:@".well-known/webfinger" isDirectory:NO] urlByAppendingQueryParameters:@{ + @"resource" : webfingerRootURL.absoluteString + } replaceExisting:YES]; + + NSURL *webFingerAccountMeURL = [[url URLByAppendingPathComponent:@".well-known/webfinger" isDirectory:NO] urlByAppendingQueryParameters:@{ + @"resource" : [NSString stringWithFormat:@"acct:me@%@", url.host] + } replaceExisting:YES]; + + if (webFingerLookupURL != nil) + { + NSDictionary *jsonDict = nil; + NSError *error = nil; + NSURL *redirectionURL = nil; + OCHTTPRequest *request = nil; + + if ((error = MakeJSONRequest(webFingerLookupURL, YES, &request, &redirectionURL, &jsonDict)) == nil) + { + if (jsonDict != nil) + { + // Example: {"subject":"acct:me@host","links":[{"rel":"http://openid.net/specs/connect/1.0/issuer","href":"https://ocis.ocis-wopi.latest.owncloud.works"}]} + // Require subject = rootURL.absoluteString + if ([jsonDict[@"subject"] isEqual:webfingerRootURL.absoluteString]) + { + NSArray *> *linksArray; + + // Look for first link with relation http://openid.net/specs/connect/1.0/issuer + if ((linksArray = OCTypedCast(jsonDict[@"links"], NSArray)) != nil) + { + for (id link in linksArray) + { + NSDictionary *linkDict; + + if ((linkDict = OCTypedCast(link, NSDictionary)) != nil) + { + if ([linkDict[@"rel"] isEqual:@"http://openid.net/specs/connect/1.0/issuer"]) + { + NSString *urlString; + + if ((urlString = OCTypedCast(linkDict[@"href"],NSString)) != nil) + { + if ((successURL = [NSURL URLWithString:urlString]) != nil) + { + completed = YES; // Skip status.php check step + webFingerAccountInfoURL = webFingerAccountMeURL; + webFingerAlternativeIDPBaseURL = successURL; + refererForIDPURL = url; + break; + } + } + } + } + } + } + } + } + } + else + { + OCLogDebug(@"Error performing webfinger lookup: %@", error); + } + } + } + // Query [url]/status.php while (!completed) { @@ -539,6 +636,26 @@ - (void)_prepareForSetupWithOptions:(NSDictionary *detectionOptions = [NSMutableDictionary new]; + + if (webFingerAccountInfoURL != nil) + { + detectionOptions[OCAuthenticationMethodWebFingerAccountLookupURLKey] = webFingerAccountInfoURL; + detectionOptions[OCAuthenticationMethodAuthenticationRefererURL] = refererForIDPURL; + detectionOptions[OCAuthenticationMethodSkipWWWAuthenticateChecksKey] = @(YES); + // detectionOptions[OCAuthenticationMethodAllowedMethods] = @[ OCAuthenticationMethodIdentifierOpenIDConnect ]; // implemented, but not used/needed at the moment + } + + if (webFingerAlternativeIDPBaseURL != nil) + { + detectionOptions[OCAuthenticationMethodWebFingerAlternativeIDPKey] = webFingerAlternativeIDPBaseURL; + } + + if (detectionOptions.count == 0) + { + detectionOptions = nil; + } + __block NSArray *supportedMethodIdentifiers = nil; if (successURL != nil) @@ -550,14 +667,14 @@ - (void)_prepareForSetupWithOptions:(NSDictionary *supportedMethods) { + [self requestSupportedAuthenticationMethodsWithOptions:detectionOptions completionHandler:^(NSError *error, NSArray *supportedMethods) { supportedAuthError = error; supportedMethodIdentifiers = supportedMethods; @@ -602,8 +719,144 @@ - (void)_prepareForSetupWithOptions:(NSDictionary * _Nullable availableInstances))completionHandler +{ + NSURL *webFingerAccountLookupURL; + + if ((webFingerAccountLookupURL = options[OCAuthenticationMethodWebFingerAccountLookupURLKey]) != nil) + { + // Send authenticated request to webfinger account lookup URL to retrieve list of available servers + OCHTTPRequest *request = [OCHTTPRequest requestWithURL:webFingerAccountLookupURL]; + + request = [OCAuthenticationMethod authorizeRequest:request withAuthenticationMethodIdentifier:authenticationMethodIdentifier authenticationData:authenticationData]; + + request.ephermalResultHandler = ^(OCHTTPRequest * _Nonnull request, OCHTTPResponse * _Nullable response, NSError * _Nullable error) { + if (error != nil) + { + completionHandler(error, nil); + return; + } + + if (response.status.isSuccess) + { + NSError *jsonError = nil; + NSDictionary *jsonDict = nil; + NSMutableArray *serverInstances = nil; + + if ((jsonDict = [response bodyConvertedDictionaryFromJSONWithError:&jsonError]) != nil) + { + NSArray *> *linksArray; + + // Look for first link with relation http://openid.net/specs/connect/1.0/issuer + if ((linksArray = OCTypedCast(jsonDict[@"links"], NSArray)) != nil) + { + serverInstances = [NSMutableArray new]; + + for (id link in linksArray) + { + NSDictionary *linkDict; + + if ((linkDict = OCTypedCast(link, NSDictionary)) != nil) + { + if ([linkDict[@"rel"] isEqual:@"http://webfinger.owncloud/rel/server-instance"]) + { + NSString *urlString; + NSURL *serverURL = nil; + NSDictionary *titlesByLanguageCode = nil; + + if ((urlString = OCTypedCast(linkDict[@"href"],NSString)) != nil) + { + if ((serverURL = [NSURL URLWithString:urlString]) == nil) + { + OCLogDebug(@"Server instance href '%@' could not be converted into server URL.", urlString); // This debug message also serves to retain self in this block, to avoid the OCConnection from being deallocated before the completion handler is called + } + } + + if ((titlesByLanguageCode = OCTypedCast(linkDict[@"titles"],NSDictionary)) != nil) + { + // Ensure that "titles" dictionary contains only strings, reject it otherwise + __block BOOL isNotAllStrings = NO; + + [titlesByLanguageCode enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { + if (![key isKindOfClass:NSString.class] || ![obj isKindOfClass:NSString.class]) + { + isNotAllStrings = YES; + *stop = YES; + } + }]; + + if (isNotAllStrings) + { + titlesByLanguageCode = nil; + } + } + + if (serverURL != nil) + { + OCServerInstance *instance = [[OCServerInstance alloc] initWithURL:serverURL]; + + instance.titlesByLanguageCode = titlesByLanguageCode; + + [serverInstances addObject:instance]; + } + } + } + } + } + } + + completionHandler((serverInstances == nil) ? OCError(OCErrorWebFingerLacksServerInstanceRelation) : nil, serverInstances); + } + else + { + completionHandler(response.status.error, nil); + } + }; + + if ((request != nil) && (self.ephermalPipeline != nil)) + { + [self.ephermalPipeline enqueueRequest:request forPartitionID:self.partitionID isFinal:YES]; + } + else + { + completionHandler(OCError(OCErrorInternal), nil); + } + } + else if (_bookmark.url != nil) + { + // Return instance based on bookmark URL + OCServerInstance *instance = [[OCServerInstance alloc] initWithURL:_bookmark.url]; + completionHandler(nil, @[ instance ]); } + else + { + // Return nil + completionHandler(OCError(OCErrorInternal), nil); + } +} + +@end + + +#pragma mark - Single request authentication +@implementation OCAuthenticationMethod (RequestAuthentication) ++ (OCHTTPRequest *)authorizeRequest:(OCHTTPRequest *)request withAuthenticationMethodIdentifier:(OCAuthenticationMethodIdentifier)authenticationMethodIdentifier authenticationData:(NSData *)authenticationData +{ + OCBookmark *bookmark = [OCBookmark new]; + bookmark.authenticationDataStorage = OCBookmarkAuthenticationDataStorageMemory; + bookmark.authenticationMethodIdentifier = authenticationMethodIdentifier; + bookmark.authenticationData = authenticationData; + + OCConnection *connection = [[OCConnection alloc] initWithBookmark:bookmark]; + + return ([connection.authenticationMethod authorizeRequest:request forConnection:connection]); } @end diff --git a/ownCloudSDK/Connection/OCConnection+Sharing.m b/ownCloudSDK/Connection/OCConnection+Sharing.m index 29bc5ff9..bec92094 100644 --- a/ownCloudSDK/Connection/OCConnection+Sharing.m +++ b/ownCloudSDK/Connection/OCConnection+Sharing.m @@ -37,6 +37,7 @@ #import "OCHTTPDAVRequest.h" #import "NSString+OCPath.h" #import "NSURL+OCPrivateLink.h" +#import "NSError+OCNetworkFailure.h" @interface OCSharingResponseStatus : NSObject @@ -112,7 +113,7 @@ + (NSString *)xmlElementNameForObjectCreation @implementation OCConnection (Sharing) #pragma mark - Retrieval -- (NSArray *)_parseSharesResponse:(OCHTTPResponse *)response data:(NSData *)responseData error:(NSError **)outError status:(OCSharingResponseStatus **)outStatus statusErrorMapper:(NSError*(^)(OCSharingResponseStatus *status))statusErrorMapper +- (NSArray *)_parseSharesResponse:(OCHTTPResponse *)response data:(NSData *)responseData category:(OCShareCategory)shareCategory error:(NSError **)outError status:(OCSharingResponseStatus **)outStatus statusErrorMapper:(NSError*(^)(OCSharingResponseStatus *status))statusErrorMapper { OCXMLParser *parser = nil; NSError *error; @@ -121,7 +122,8 @@ @implementation OCConnection (Sharing) { if ((parser = [[OCXMLParser alloc] initWithData:responseData]) != nil) { - [parser addObjectCreationClasses:@[ [OCShare class], [OCShareSingle class], [OCSharingResponseStatus class] ]]; + parser.options[@"_shareCategory"] = @(shareCategory); + [parser addObjectCreationClasses:@[ OCShare.class, OCShareSingle.class, OCSharingResponseStatus.class ]]; if ([parser parse]) { @@ -171,6 +173,7 @@ - (nullable NSProgress *)retrieveSharesWithScope:(OCShareScope)scope forItem:(nu OCHTTPRequest *request; NSProgress *progress = nil; NSURL *url = [self URLForEndpoint:OCConnectionEndpointIDShares options:nil]; + OCShareCategory shareCategory = OCShareCategoryUnknown; request = [OCHTTPRequest new]; request.requiredSignals = self.propFindSignals; @@ -190,28 +193,43 @@ - (nullable NSProgress *)retrieveSharesWithScope:(OCShareScope)scope forItem:(nu break; } + if ((item != nil) && self.useDriveAPI) + { + // Add the file ID to allow the server to determine the item's location (path, of course, isn't sufficient there) + if (item.fileID != nil) + { + [request setValue:item.fileID forParameter:@"space_ref"]; + } + } + switch (scope) { case OCShareScopeSharedByUser: // No options to set + shareCategory = OCShareCategoryByMe; break; case OCShareScopeSharedWithUser: + shareCategory = OCShareCategoryWithMe; + [request setValue:@"true" forParameter:@"shared_with_me"]; [request setValue:@"all" forParameter:@"state"]; break; case OCShareScopePendingCloudShares: + shareCategory = OCShareCategoryWithMe; url = [[self URLForEndpoint:OCConnectionEndpointIDRemoteShares options:nil] URLByAppendingPathComponent:@"pending"]; break; case OCShareScopeAcceptedCloudShares: + shareCategory = OCShareCategoryWithMe; url = [self URLForEndpoint:OCConnectionEndpointIDRemoteShares options:nil]; break; case OCShareScopeItem: case OCShareScopeItemWithReshares: case OCShareScopeSubItems: + shareCategory = OCShareCategoryByMe; if (item == nil) { OCLogError(@"item required for retrieval of shares with scope=%lu", scope); @@ -246,7 +264,7 @@ - (nullable NSProgress *)retrieveSharesWithScope:(OCShareScope)scope forItem:(nu if (error == nil) { - shares = [self _parseSharesResponse:response data:response.bodyData error:&error status:&status statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + shares = [self _parseSharesResponse:response data:response.bodyData category:shareCategory error:&error status:&status statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -318,7 +336,7 @@ - (nullable NSProgress *)retrieveShareWithID:(OCShareID)shareID options:(nullabl if (!((response.error != nil) && ![response.error.domain isEqual:OCHTTPStatusErrorDomain])) { - shares = [self _parseSharesResponse:response data:response.bodyData error:&error status:&status statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + shares = [self _parseSharesResponse:response data:response.bodyData category:OCShareCategoryUnknown error:&error status:&status statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -355,7 +373,9 @@ - (nullable OCProgress *)createShare:(OCShare *)share options:(nullable OCShareO OCHTTPRequest *request; OCProgress *requestProgress = nil; - request = [OCHTTPRequest requestWithURL:[self URLForEndpoint:OCConnectionEndpointIDShares options:nil]]; + request = [OCHTTPRequest requestWithURL:[self URLForEndpoint:OCConnectionEndpointIDShares options:@{ + OCConnectionOptionDriveID : OCDriveIDWrap(share.itemLocation.driveID) + }]]; request.method = OCHTTPMethodPOST; request.requiredSignals = self.propFindSignals; @@ -366,13 +386,19 @@ - (nullable OCProgress *)createShare:(OCShare *)share options:(nullable OCShareO [request setValue:[NSString stringWithFormat:@"%ld", share.type] forParameter:@"shareType"]; - [request setValue:share.itemPath forParameter:@"path"]; + [request setValue:share.itemLocation.path forParameter:@"path"]; [request setValue:[NSString stringWithFormat:@"%ld", share.permissions] forParameter:@"permissions"]; + if ((share.itemFileID != nil) && self.useDriveAPI) + { + // Add the file ID to allow the server to determine the item's location (path, of course, isn't sufficient there) + [request setValue:share.itemFileID forParameter:@"space_ref"]; + } + if (share.expirationDate != nil) { - [request setValue:share.expirationDate.compactUTCStringDateOnly forParameter:@"expireDate"]; + [request setValue:(self.useDriveAPI ? share.expirationDate.compactISO8601String : share.expirationDate.compactUTCStringDateOnly) forParameter:@"expireDate"]; } if (share.type != OCShareTypeLink) @@ -394,7 +420,7 @@ - (nullable OCProgress *)createShare:(OCShare *)share options:(nullable OCShareO requestProgress = request.progress; requestProgress.progress.eventType = OCEventTypeCreateShare; - requestProgress.progress.localizedDescription = [NSString stringWithFormat:OCLocalized(@"Creating share for %@…"), share.itemPath.lastPathComponent]; + requestProgress.progress.localizedDescription = [NSString stringWithFormat:OCLocalized(@"Creating share for %@…"), share.itemLocation.path.lastPathComponent]; return (requestProgress); } @@ -405,7 +431,11 @@ - (void)_handleCreateShareResult:(OCHTTPRequest *)request error:(NSError *)error if ((event = [OCEvent eventForEventTarget:request.eventTarget type:OCEventTypeCreateShare uuid:request.identifier attributes:nil]) != nil) { - if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) + if (error.isNetworkFailureError) + { + event.error = OCErrorWithDescriptionFromError(OCErrorNotAvailableOffline, OCLocalized(@"Sharing requires an active connection."), error); + } + else if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) { event.error = request.error; } @@ -413,7 +443,7 @@ - (void)_handleCreateShareResult:(OCHTTPRequest *)request error:(NSError *)error { NSArray *shares = nil; - shares = [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + shares = [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData category:OCShareCategoryByMe error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -507,14 +537,21 @@ - (nullable OCProgress *)updateShare:(OCShare *)share afterPerformingChanges:(vo if (OCNANotEqual(share.expirationDate, previousExpirationDate)) { - if (share.type == OCShareTypeLink) - { - changedValuesByPropertyNames[@"expireDate"] = (share.expirationDate != nil) ? share.expirationDate.compactUTCStringDateOnly : @""; - } - else + NSString *expirationDate = @""; + + if (share.expirationDate != nil) { - returnLinkShareOnlyError = YES; + if (self.useDriveAPI) + { + expirationDate = share.expirationDate.compactISO8601String; + } + else + { + expirationDate = share.expirationDate.compactUTCStringDateOnly; + } } + + changedValuesByPropertyNames[@"expireDate"] = expirationDate; } if (share.permissions != previousPermissions) @@ -602,7 +639,11 @@ - (void)_handleUpdateShareResult:(OCHTTPRequest *)request error:(NSError *)error if ((event = [OCEvent eventForEventTarget:request.eventTarget type:OCEventTypeUpdateShare uuid:request.identifier attributes:nil]) != nil) { - if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) + if (error.isNetworkFailureError) + { + event.error = OCErrorWithDescriptionFromError(OCErrorNotAvailableOffline, OCLocalized(@"Sharing requires an active connection."), error); + } + else if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) { event.error = request.error; } @@ -611,7 +652,7 @@ - (void)_handleUpdateShareResult:(OCHTTPRequest *)request error:(NSError *)error NSArray *shares = nil; NSError *parseError = nil; - shares = [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData error:&parseError status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + shares = [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData category:OCShareCategoryByMe error:&parseError status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -695,7 +736,9 @@ - (nullable OCProgress *)deleteShare:(OCShare *)share resultTarget:(OCEventTarge return (nil); } - request = [OCHTTPRequest requestWithURL:[[self URLForEndpoint:OCConnectionEndpointIDShares options:nil] URLByAppendingPathComponent:share.identifier]]; + request = [OCHTTPRequest requestWithURL:[[self URLForEndpoint:OCConnectionEndpointIDShares options:@{ + OCConnectionOptionDriveID : OCDriveIDWrap(share.itemLocation.driveID) + }] URLByAppendingPathComponent:share.identifier]]; request.method = OCHTTPMethodDELETE; request.requiredSignals = self.propFindSignals; request.forceCertificateDecisionDelegation = YES; @@ -707,7 +750,7 @@ - (nullable OCProgress *)deleteShare:(OCShare *)share resultTarget:(OCEventTarge requestProgress = request.progress; requestProgress.progress.eventType = OCEventTypeDeleteShare; - requestProgress.progress.localizedDescription = [NSString stringWithFormat:OCLocalized(@"Deleting share for %@…"), share.itemPath.lastPathComponent]; + requestProgress.progress.localizedDescription = [NSString stringWithFormat:OCLocalized(@"Deleting share for %@…"), share.itemLocation.path.lastPathComponent]; return (requestProgress); } @@ -718,13 +761,17 @@ - (void)_handleDeleteShareResult:(OCHTTPRequest *)request error:(NSError *)error if ((event = [OCEvent eventForEventTarget:request.eventTarget type:OCEventTypeDeleteShare uuid:request.identifier attributes:nil]) != nil) { - if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) + if (error.isNetworkFailureError) + { + event.error = OCErrorWithDescriptionFromError(OCErrorNotAvailableOffline, OCLocalized(@"Sharing requires an active connection."), error); + } + else if ((request.error != nil) && ![request.error.domain isEqual:OCHTTPStatusErrorDomain]) { event.error = request.error; } else { - [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData category:OCShareCategoryByMe error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -775,11 +822,15 @@ - (nullable OCProgress *)makeDecisionOnShare:(OCShare *)share accept:(BOOL)accep { case OCShareTypeUserShare: case OCShareTypeGroupShare: - endpointURL = [self URLForEndpoint:OCConnectionEndpointIDShares options:nil]; + endpointURL = [self URLForEndpoint:OCConnectionEndpointIDShares options:@{ + OCConnectionOptionDriveID : OCDriveIDWrap(share.itemLocation.driveID) + }]; break; case OCShareTypeRemote: - endpointURL = [self URLForEndpoint:OCConnectionEndpointIDRemoteShares options:nil]; + endpointURL = [self URLForEndpoint:OCConnectionEndpointIDRemoteShares options:@{ + OCConnectionOptionDriveID : OCDriveIDWrap(share.itemLocation.driveID) + }]; break; default: break; @@ -823,7 +874,7 @@ - (void)_handleMakeDecisionOnShareResult:(OCHTTPRequest *)request error:(NSError } else { - [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { + [self _parseSharesResponse:request.httpResponse data:request.httpResponse.bodyData category:OCShareCategoryWithMe error:&error status:NULL statusErrorMapper:^NSError *(OCSharingResponseStatus *status) { NSError *error = nil; switch (status.statusCode.integerValue) @@ -873,7 +924,7 @@ - (nullable NSProgress *)retrievePrivateLinkForItem:(OCItem *)item completionHan NSURL *endpointURL; OCHTTPDAVRequest *davRequest; - if ((endpointURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil]) == nil) + if ((endpointURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCDriveIDWrap(item.driveID) }]) == nil) { // WebDAV root could not be generated (likely due to lack of username) completionHandler(OCError(OCErrorInternal), nil); @@ -893,7 +944,7 @@ - (nullable NSProgress *)retrievePrivateLinkForItem:(OCItem *)item completionHan NSArray *errors = nil; NSArray *items = nil; - if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID withErrors:&errors]) != nil) + if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID driveID:nil withErrors:&errors]) != nil) { NSURL *privateLink; @@ -960,17 +1011,17 @@ - (nullable NSProgress *)retrievePathForPrivateLink:(NSURL *)privateLink complet NSArray *errors = nil; NSArray *items = nil; - if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID withErrors:&errors]) != nil) + if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID driveID:nil withErrors:&errors]) != nil) { - NSString *path; + OCLocation *location; - if ((path = items.firstObject.path) != nil) + if ((location = items.firstObject.location) != nil) { // OC Server will return "/Documents" for the documents folder => make sure to normalize the path to follow OCPath conventions in that case // The value of D:resourcetype is not correct when requested with the same (resolution) request. OC server will return d:collection for files, too. // Perform standard depth 0 PROPFIND on path to determine type - [strongSelf retrieveItemListAtPath:path depth:0 options:@{ OCConnectionOptionIsNonCriticalKey : @(YES) } completionHandler:^(NSError * _Nullable error, NSArray * _Nullable items) { + [strongSelf retrieveItemListAtLocation:location depth:0 options:@{ OCConnectionOptionIsNonCriticalKey : @(YES) } completionHandler:^(NSError * _Nullable error, NSArray * _Nullable items) { OCPath normalizedPath = nil; if (error == nil) @@ -982,15 +1033,15 @@ - (nullable NSProgress *)retrievePathForPrivateLink:(NSURL *)privateLink complet switch (item.type) { case OCItemTypeFile: - normalizedPath = path.normalizedFilePath; + normalizedPath = location.path.normalizedFilePath; break; case OCItemTypeCollection: - normalizedPath = path.normalizedDirectoryPath; + normalizedPath = location.path.normalizedDirectoryPath; break; default: - normalizedPath = path; + normalizedPath = location.path; break; } } diff --git a/ownCloudSDK/Connection/OCConnection+Tools.m b/ownCloudSDK/Connection/OCConnection+Tools.m index 805988a0..a00b7a37 100644 --- a/ownCloudSDK/Connection/OCConnection+Tools.m +++ b/ownCloudSDK/Connection/OCConnection+Tools.m @@ -17,6 +17,7 @@ */ #import "OCConnection.h" +#import "OCConnection+GraphAPI.h" @implementation OCConnection (Tools) @@ -45,6 +46,22 @@ - (NSString *)pathForEndpoint:(OCConnectionEndpointID)endpoint endpointPath = nil; } } + else if ([endpoint isEqual:OCConnectionEndpointIDAppProviderList]) + { + endpointPath = self.capabilities.latestSupportedAppProvider.appsURLPath; + } + else if ([endpoint isEqual:OCConnectionEndpointIDAppProviderNew]) + { + endpointPath = self.capabilities.latestSupportedAppProvider.createURLPath; + } + else if ([endpoint isEqual:OCConnectionEndpointIDAppProviderOpen]) + { + endpointPath = self.capabilities.latestSupportedAppProvider.openURLPath; + } + else if ([endpoint isEqual:OCConnectionEndpointIDAppProviderOpenWeb]) + { + endpointPath = self.capabilities.latestSupportedAppProvider.openWebURLPath; + } else { endpointPath = [self classSettingForOCClassSettingsKey:endpoint]; @@ -59,10 +76,58 @@ - (NSString *)pathForEndpoint:(OCConnectionEndpointID)endpoint - (NSURL *)URLForEndpoint:(OCConnectionEndpointID)endpoint options:(NSDictionary *)options { NSString *endpointPath; - + NSURL *alternativeBaseURL = nil; + + if (options[OCAuthenticationMethodWebFingerAlternativeIDPKey] != nil) + { + if ([endpoint isEqual:OCConnectionEndpointIDWellKnown]) + { + alternativeBaseURL = options[OCAuthenticationMethodWebFingerAlternativeIDPKey]; + } + } + + if ([endpoint isEqual:OCConnectionEndpointIDPreview]) + { + endpoint = OCConnectionEndpointIDWebDAVRoot; + + /* + // Temporary fix for https://github.com/owncloud/ocis/issues/3558 + + OCDriveID driveID; + + driveID = options[OCConnectionEndpointURLOptionDriveID]; + driveID = OCDriveIDUnwrap(driveID); + + if (driveID != nil) + { + NSURL *webdavRootURL; + + if ((webdavRootURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:options]) != nil) + { + // Insert "remote.php" at beginning of URL path + // - works: + // https://localhost:9200/remote.php/dav/spaces/480d46a8-4b90-41ca-83bc-a22382197355/IMG_0005.JPG?scalingup=0&preview=1&a=1&c=35b2f49cf8d94c365e6b47da2c2a561e&x=36&y=36 + // - doesn't work: + // https://localhost:9200 /dav/spaces/d9cc2f67-fff8-4c67-81e9-072fc8db8628/.space/readme.md?y=180&preview=1&scalingup=0&x=180&c=%22462b38a361467dfb230981813803d9bf%22&a=1 + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:webdavRootURL resolvingAgainstBaseURL:YES]; + + components.path = [@"/remote.php" stringByAppendingString:components.path]; + + webdavRootURL = components.URL; + } + + return (webdavRootURL); + } + else + { + endpoint = OCConnectionEndpointIDWebDAVRoot; + } + */ + } + if ((endpointPath = [self pathForEndpoint:endpoint]) != nil) { - NSURL *url = [self URLForEndpointPath:endpointPath]; + NSURL *url = [self URLForEndpointPath:endpointPath withAlternativeURL:alternativeBaseURL]; if ([endpoint isEqualToString:OCConnectionEndpointIDWellKnown]) { @@ -74,6 +139,30 @@ - (NSURL *)URLForEndpoint:(OCConnectionEndpointID)endpoint options:(NSDictionary } } + if ([endpoint isEqualToString:OCConnectionEndpointIDWebDAVRoot]) + { + OCDriveID driveID; + + if (((driveID = options[OCConnectionEndpointURLOptionDriveID]) != nil) && ![driveID isKindOfClass:NSNull.class]) + { + OCDrive *drive; + + if ((drive = [self driveWithID:driveID]) == nil) + { + OCTLogError(@[@"Drives"], @"Path for WebDAV endpoint for driveID %@ could not be generated: unknown drive", driveID); + return (nil); + } + + if (drive.davRootURL == nil) + { + OCTLogError(@[@"Drives"], @"Path for WebDAV endpoint for drive %@ could not be generated: unknown davRootURL", drive); + return (nil); + } + + url = drive.davRootURL; + } + } + if ([endpoint isEqualToString:OCConnectionEndpointIDWebDAV] && (options == nil)) { // Ensure WebDAV endpoint path is slash-terminated @@ -93,12 +182,12 @@ - (NSURL *)URLForEndpoint:(OCConnectionEndpointID)endpoint options:(NSDictionary return (nil); } -- (NSURL *)URLForEndpointPath:(OCPath)endpointPath +- (NSURL *)URLForEndpointPath:(OCPath)endpointPath withAlternativeURL:(nullable NSURL *)alternativeURL { + NSURL *bookmarkURL = (alternativeURL != nil) ? alternativeURL : _bookmark.url; + if (endpointPath != nil) { - NSURL *bookmarkURL = _bookmark.url; - if ([endpointPath hasPrefix:@"/"]) // Absolute path { // Remove leading "/" @@ -114,9 +203,10 @@ - (NSURL *)URLForEndpointPath:(OCPath)endpointPath return ([[bookmarkURL URLByAppendingPathComponent:endpointPath] absoluteURL]); } - return (_bookmark.url); + return (bookmarkURL); } + #pragma mark - Base URL Extract - (NSURL *)extractBaseURLFromRedirectionTargetURL:(NSURL *)inRedirectionTargetURL originalURL:(NSURL *)inOriginalURL fallbackToRedirectionTargetURL:(BOOL)fallbackToRedirectionTargetURL { diff --git a/ownCloudSDK/Connection/OCConnection+Upload.m b/ownCloudSDK/Connection/OCConnection+Upload.m index 71802089..3a94c3dc 100644 --- a/ownCloudSDK/Connection/OCConnection+Upload.m +++ b/ownCloudSDK/Connection/OCConnection+Upload.m @@ -47,6 +47,14 @@ - (OCProgress *)uploadFileFromURL:(NSURL *)sourceURL withName:(NSString *)fileNa return(nil); } + if (self.useDriveAPI && (newParentDirectory.driveID == nil)) + { + // Drive ID required for accounts with Drive API + OCLogWarning(@"uploadFile: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:OCEventTypeUpload uuid:nil sender:self]; + return (nil); + } + if (fileName == nil) { if (replacedItem != nil) @@ -211,7 +219,7 @@ - (OCProgress *)_tusUploadFileFromURL:(NSURL *)sourceURL withName:(NSString *)fi OCTUSJob *tusJob; NSURL *creationURL; - if ((creationURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil] URLByAppendingPathComponent:parentItem.path]) != nil) + if ((creationURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(parentItem.driveID) }] URLByAppendingPathComponent:parentItem.path]) != nil) { if ((tusJob = [[OCTUSJob alloc] initWithHeader:parentTusHeader segmentFolderURL:segmentFolderURL fileURL:clonedSourceURL creationURL:creationURL]) != nil) { @@ -219,6 +227,7 @@ - (OCProgress *)_tusUploadFileFromURL:(NSURL *)sourceURL withName:(NSString *)fi tusJob.fileSize = fileSize; tusJob.fileModDate = modificationDate; tusJob.fileChecksum = checksum; + tusJob.fileDriveID = parentItem.driveID; tusJob.futureItemPath = [parentItem.path stringByAppendingPathComponent:fileName]; @@ -404,7 +413,7 @@ - (OCProgress *)_continueTusJob:(OCTUSJob *)tusJob lastTask:(NSString *)lastTask [tusJob destroy]; // Retrieve item information - [self retrieveItemListAtPath:tusJob.futureItemPath depth:0 options:@{ + [self retrieveItemListAtLocation:[[OCLocation alloc] initWithDriveID:tusJob.fileDriveID path:tusJob.futureItemPath] depth:0 options:@{ @"alternativeEventType" : @(OCEventTypeUpload), OCConnectionOptionRequiredSignalsKey : self.actionSignals } resultTarget:tusJob.eventTarget]; @@ -623,7 +632,7 @@ - (OCProgress *)_directUploadFileFromURL:(NSURL *)sourceURL withName:(NSString * OCProgress *requestProgress = nil; NSURL *uploadURL; - if ((uploadURL = [[[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil] URLByAppendingPathComponent:newParentDirectory.path] URLByAppendingPathComponent:fileName]) != nil) + if ((uploadURL = [[[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(newParentDirectory.driveID) }] URLByAppendingPathComponent:newParentDirectory.path] URLByAppendingPathComponent:fileName]) != nil) { OCHTTPRequest *request = [OCHTTPRequest requestWithURL:uploadURL]; @@ -747,7 +756,7 @@ - (void)_handleDirectUploadFileResult:(OCHTTPRequest *)request error:(NSError *) */ // Retrieve item information - [self retrieveItemListAtPath:[parentItem.path stringByAppendingPathComponent:fileName] depth:0 options:@{ + [self retrieveItemListAtLocation:[[OCLocation alloc] initWithDriveID:parentItem.driveID path:[parentItem.path stringByAppendingPathComponent:fileName]] depth:0 options:@{ @"alternativeEventType" : @(OCEventTypeUpload), OCConnectionOptionRequiredSignalsKey : self.actionSignals } resultTarget:request.eventTarget]; @@ -786,7 +795,7 @@ - (void)_handleDirectUploadFileResult:(OCHTTPRequest *)request error:(NSError *) // in this place. In order not to return an error if the file on the server equals the file to be uploaded, we first perform a PROPFIND // check and compare the checksums - [self retrieveItemListAtPath:[parentItem.path stringByAppendingPathComponent:fileName] depth:0 options:@{ + [self retrieveItemListAtLocation:[[OCLocation alloc] initWithDriveID:parentItem.driveID path:[parentItem.path stringByAppendingPathComponent:fileName]] depth:0 options:@{ @"alternativeEventType" : @(OCEventTypeUpload), OCConnectionOptionRequiredSignalsKey : self.actionSignals, diff --git a/ownCloudSDK/Connection/OCConnection.h b/ownCloudSDK/Connection/OCConnection.h index 43655b33..c4311d5b 100644 --- a/ownCloudSDK/Connection/OCConnection.h +++ b/ownCloudSDK/Connection/OCConnection.h @@ -33,12 +33,17 @@ #import "OCHTTPCookieStorage.h" #import "OCCapabilities.h" #import "OCRateLimiter.h" +#import "OCAvatar.h" +#import "OCDrive.h" +#import "OCAppProviderApp.h" @class OCBookmark; @class OCAuthenticationMethod; @class OCItem; @class OCConnection; @class OCXMLNode; +@class OCAppProviderFileType; +@class OCServerInstance; typedef NSString* OCConnectionEndpointID NS_TYPED_ENUM; typedef NSString* OCConnectionOptionKey NS_TYPED_ENUM; @@ -122,6 +127,9 @@ NS_ASSUME_NONNULL_BEGIN NSMutableDictionary *_usersByUserID; + NSArray *_drives; + NSMutableDictionary *_drivesByID; + NSMutableSet *_signals; NSSet *_actionSignals; NSSet *_propFindSignals; @@ -146,7 +154,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) OCBookmark *bookmark; @property(nullable,strong,nonatomic) OCAuthenticationMethod *authenticationMethod; -@property(nullable,strong) NSDictionary *staticHeaderFields; //!< Dictionary of header fields to add to every HTTP request +@property(nullable,strong) OCHTTPStaticHeaderFields staticHeaderFields; //!< Dictionary of header fields to add to every HTTP request @property(nullable,strong) OCChecksumAlgorithmIdentifier preferredChecksumAlgorithm; @@ -192,10 +200,8 @@ NS_ASSUME_NONNULL_BEGIN + (BOOL)shouldConsiderMaintenanceModeIndicationFromResponse:(OCHTTPResponse *)response; #pragma mark - Metadata actions -- (nullable NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth completionHandler:(void(^)(NSError * _Nullable error, NSArray * _Nullable items))completionHandler; //!< Retrieves the items at the specified path -- (nullable NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth options:(nullable NSDictionary *)options completionHandler:(void(^)(NSError * _Nullable error, NSArray * _Nullable items))completionHandler; //!< Retrieves the items at the specified path with options - -- (nullable NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth options:(nullable NSDictionary *)options resultTarget:(OCEventTarget *)eventTarget; //!< Retrieves the items at the specified path, with options to schedule on the background queue and with a "not before" date. +- (nullable NSProgress *)retrieveItemListAtLocation:(OCLocation *)location depth:(NSUInteger)depth options:(nullable NSDictionary *)options completionHandler:(void(^)(NSError * _Nullable error, NSArray * _Nullable items))completionHandler; //!< Retrieves the items at the specified path with options +- (nullable NSProgress *)retrieveItemListAtLocation:(OCLocation *)location depth:(NSUInteger)depth options:(nullable NSDictionary *)options resultTarget:(OCEventTarget *)eventTarget; //!< Retrieves the items at the specified path, with options to schedule on the background queue and with a "not before" date. #pragma mark - Actions - (nullable OCProgress *)createFolder:(NSString *)folderName inside:(OCItem *)parentItem options:(nullable NSDictionary *)options resultTarget:(OCEventTarget *)eventTarget; @@ -243,7 +249,10 @@ NS_ASSUME_NONNULL_BEGIN @interface OCConnection (Setup) #pragma mark - Prepare for setup -- (void)prepareForSetupWithOptions:(nullable NSDictionary *)options completionHandler:(void(^)(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods))completionHandler; //!< Helps in creation of a valid bookmark during setup. Provides found issues as OCIssue (type: group) that can be accepted or rejected. Individual issues can be used as source for line items. +- (void)prepareForSetupWithOptions:(nullable NSDictionary *)options completionHandler:(void(^)(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions))completionHandler; //!< Helps in creation of a valid bookmark during setup. Provides found issues as OCIssue (type: group) that can be accepted or rejected. Individual issues can be used as source for line items. + +#pragma mark - Retrieve instances +- (void)retrieveAvailableInstancesWithOptions:(nullable OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions)options authenticationMethodIdentifier:(OCAuthenticationMethodIdentifier)authenticationMethodIdentifier authenticationData:(NSData *)authenticationData completionHandler:(void(^)(NSError * _Nullable error, NSArray * _Nullable availableInstances))completionHandler; @end @@ -324,7 +333,7 @@ typedef void(^OCConnectionShareCompletionHandler)(NSError * _Nullable error, OCS @end #pragma mark - RECIPIENTS -typedef void(^OCConnectionRecipientsRetrievalCompletionHandler)(NSError * _Nullable error, NSArray * _Nullable recipients); +typedef void(^OCConnectionRecipientsRetrievalCompletionHandler)(NSError * _Nullable error, NSArray * _Nullable recipients); @interface OCConnection (Recipients) @@ -342,13 +351,38 @@ typedef void(^OCConnectionRecipientsRetrievalCompletionHandler)(NSError * _Nulla @end +#pragma mark - AVATARS +@interface OCConnection (Avatars) + +#pragma mark - Avatars +- (nullable NSProgress *)retrieveAvatarForUser:(OCUser *)user existingETag:(nullable OCFileETag)eTag withSize:(CGSize)size completionHandler:(void(^)(NSError * _Nullable error, BOOL unchanged, OCAvatar * _Nullable avatar))completionHandler; + +@end + +#pragma mark - APP PROVIDERS +@interface OCConnection (AppProviders) + +#pragma mark - App List +- (nullable NSProgress *)retrieveAppProviderListWithCompletionHandler:(void(^)(NSError * _Nullable error, OCAppProvider * _Nullable appProvider))completionHandler; + +#pragma mark - Create App Document +- (nullable NSProgress *)createAppFileOfType:(OCAppProviderFileType *)appType in:(OCItem *)parentDirectoryItem withName:(NSString *)fileName completionHandler:(void(^)(NSError * _Nullable error, OCFileID _Nullable fileID, OCItem * _Nullable item))completionHandler; + +#pragma mark - Open +- (nullable NSProgress *)openInApp:(OCItem *)item withApp:(nullable OCAppProviderApp *)app viewMode:(nullable OCAppProviderViewMode)viewMode completionHandler:(void(^)(NSError * _Nullable error, NSURL * _Nullable appURL, OCHTTPMethod _Nullable httpMethod, OCHTTPHeaderFields _Nullable headerFields, OCHTTPRequestParameters _Nullable parameters, NSMutableURLRequest * _Nullable urlRequest))completionHandler; + +#pragma mark - Open in Web +- (nullable NSProgress *)openInWeb:(OCItem *)item withApp:(nullable OCAppProviderApp *)app completionHandler:(void(^)(NSError * _Nullable error, NSURL * _Nullable webURL))completionHandler; + +@end + #pragma mark - TOOLS @interface OCConnection (Tools) #pragma mark - Endpoints - (nullable NSString *)pathForEndpoint:(OCConnectionEndpointID)endpoint; //!< Returns the path of an endpoint identified by its OCConnectionEndpointID - (nullable NSURL *)URLForEndpoint:(OCConnectionEndpointID)endpoint options:(nullable NSDictionary *)options; //!< Returns the URL of an endpoint identified by its OCConnectionEndpointID, allowing additional options (reserved for future use) -- (nullable NSURL *)URLForEndpointPath:(OCPath)endpointPath; //!< Returns the URL of the endpoint at the supplied endpointPath +- (nullable NSURL *)URLForEndpointPath:(OCPath)endpointPath withAlternativeURL:(nullable NSURL *)alternativeURL; //!< Returns the URL of the endpoint at the supplied endpointPath #pragma mark - Base URL Extract + (nullable NSURL *)extractBaseURLFromRedirectionTargetURL:(NSURL *)inRedirectionTargetURL originalURL:(NSURL *)inOriginalURL originalBaseURL:(NSURL *)inOriginalBaseURL fallbackToRedirectionTargetURL:(BOOL)fallbackToRedirectionTargetURL; @@ -366,18 +400,18 @@ typedef void(^OCConnectionRecipientsRetrievalCompletionHandler)(NSError * _Nulla - (nullable NSProgress *)retrieveCapabilitiesWithCompletionHandler:(void(^)(NSError * _Nullable error, OCCapabilities * _Nullable capabilities))completionHandler; #pragma mark - Version -- (nullable NSString *)serverVersion; //!< After connecting, the version of the server ("version"), f.ex. "10.0.8.5". -- (nullable NSString *)serverVersionString; //!< After connecting, the version string of the server ("versionstring"), fe.x. "10.0.8", "10.1.0 prealpha" +@property(readonly,strong,nullable,nonatomic) NSString *serverVersion; //!< After connecting, the version of the server ("version"), f.ex. "10.0.8.5". +@property(readonly,strong,nullable,nonatomic) NSString *serverVersionString; //!< After connecting, the version string of the server ("versionstring"), fe.x. "10.0.8", "10.1.0 prealpha" - (BOOL)runsServerVersionOrHigher:(NSString *)version; //!< Returns YES if the server runs at least [version]. -- (nullable NSString *)serverProductName; //!< After connecting, the product name of the server ("productname"), f.ex. "ownCloud". -- (nullable NSString *)serverEdition; //!< After connecting, the edition of the server ("edition"), f.ex. "Community". +@property(readonly,strong,nullable,nonatomic) NSString *serverProductName; //!< After connecting, the product name of the server ("productname"), f.ex. "ownCloud". +@property(readonly,strong,nullable,nonatomic) NSString *serverEdition; //!< After connecting, the edition of the server ("edition"), f.ex. "Community". -- (nullable NSString *)serverLongProductVersionString; //!< After connecting, a string summarizing the product, edition and version, f.ex. "ownCloud Community 10.0.8.5" +@property(readonly,strong,nullable,nonatomic) NSString *serverLongProductVersionString; //!< After connecting, a string summarizing the product, edition and version, f.ex. "ownCloud Community 10.0.8.5" + (nullable NSString *)serverLongProductVersionStringFromServerStatus:(NSDictionary *)serverStatus; #pragma mark - API Switches -- (BOOL)supportsPreviewAPI; //!< Returns YES if the server supports the Preview API. +@property(readonly,nonatomic) BOOL useDriveAPI; //!< Returns YES if the server supports the drive API and it should be used. #pragma mark - Checks - (nullable NSError *)supportsServerVersion:(NSString *)serverVersion product:(NSString *)product longVersion:(NSString *)longVersion allowHiddenVersion:(BOOL)allowHiddenVersion; @@ -389,13 +423,19 @@ extern OCConnectionEndpointID OCConnectionEndpointIDUser; extern OCConnectionEndpointID OCConnectionEndpointIDWebDAV; extern OCConnectionEndpointID OCConnectionEndpointIDWebDAVMeta; extern OCConnectionEndpointID OCConnectionEndpointIDWebDAVRoot; //!< Virtual, non-configurable endpoint, builds the root URL based on OCConnectionEndpointIDWebDAV and the username found in connection.loggedInUser -extern OCConnectionEndpointID OCConnectionEndpointIDThumbnail; +extern OCConnectionEndpointID OCConnectionEndpointIDPreview; //!< Virtual, non-configurable endpoint, builds the root URL for requesting previews based on OCConnectionEndpointIDWebDAV, the username found in connection.loggedInUser and the drive ID extern OCConnectionEndpointID OCConnectionEndpointIDStatus; extern OCConnectionEndpointID OCConnectionEndpointIDShares; extern OCConnectionEndpointID OCConnectionEndpointIDRemoteShares; extern OCConnectionEndpointID OCConnectionEndpointIDRecipients; +extern OCConnectionEndpointID OCConnectionEndpointIDAvatars; +extern OCConnectionEndpointID OCConnectionEndpointIDAppProviderList; +extern OCConnectionEndpointID OCConnectionEndpointIDAppProviderOpen; +extern OCConnectionEndpointID OCConnectionEndpointIDAppProviderOpenWeb; +extern OCConnectionEndpointID OCConnectionEndpointIDAppProviderNew; extern OCConnectionEndpointURLOption OCConnectionEndpointURLOptionWellKnownSubPath; +extern OCConnectionEndpointURLOption OCConnectionEndpointURLOptionDriveID; extern OCClassSettingsIdentifier OCClassSettingsIdentifierConnection; @@ -403,6 +443,7 @@ extern OCClassSettingsKey OCConnectionPreferredAuthenticationMethodIDs; //!< Arr extern OCClassSettingsKey OCConnectionAllowedAuthenticationMethodIDs; //!< Array of OCAuthenticationMethodIdentifiers of allowed authentication methods. Defaults to nil for no restrictions. [NSArray *] extern OCClassSettingsKey OCConnectionCertificateExtendedValidationRule; //!< Rule that defines the criteria a certificate needs to meet for OCConnection to accept it. extern OCClassSettingsKey OCConnectionRenewedCertificateAcceptanceRule; //!< Rule that defines the criteria that need to be met for OCConnection to accept a renewed certificate automatically. Used when OCConnectionCertificateExtendedValidationRule fails. Set this to "never" if the user should always be prompted when a server's certificate changed. +extern OCClassSettingsKey OCConnectionAssociatedCertificatesTrackingRule; //!< Rule that defines criteria for whether certificates for hosts other than the bookmark's host should be stored and observed for changes. extern OCClassSettingsKey OCConnectionMinimumVersionRequired; //!< Makes sure connections via -connectWithCompletionHandler:completionHandler: can only be made to servers with this version number or higher. extern OCClassSettingsKey OCConnectionAllowBackgroundURLSessions; //!< Allows (TRUE) or disallows (FALSE) the use of background URL sessions. Defaults to TRUE. extern OCClassSettingsKey OCConnectionForceBackgroundURLSessions; //!< Forces (TRUE) or allows (FALSE) the use of background URL sessions everywhere. Defaults to FALSE. @@ -424,6 +465,7 @@ extern OCConnectionOptionKey OCConnectionOptionTemporarySegmentFolderURLKey; //! extern OCConnectionOptionKey OCConnectionOptionForceReplaceKey; //!< If YES, force replace existing items. extern OCConnectionOptionKey OCConnectionOptionResponseDestinationURL; //!< NSURL of where to store a (raw) response extern OCConnectionOptionKey OCConnectionOptionResponseStreamHandler; //!< Response stream handler (OCHTTPRequestEphermalStreamHandler) to receive the response body stream +extern OCConnectionOptionKey OCConnectionOptionDriveID; //!< Drive ID (OCDriveID) to target. extern OCConnectionSetupOptionKey OCConnectionSetupOptionUserName; //!< User name to feed to OCConnectionServerLocator to determine server. diff --git a/ownCloudSDK/Connection/OCConnection.m b/ownCloudSDK/Connection/OCConnection.m index fdde3a3a..f52bd03b 100644 --- a/ownCloudSDK/Connection/OCConnection.m +++ b/ownCloudSDK/Connection/OCConnection.m @@ -51,7 +51,9 @@ #import "NSURL+OCURLNormalization.h" #import "OCDAVRawResponse.h" #import "OCBookmarkManager.h" +#import "OCConnection+GraphAPI.h" #import "NSError+OCNetworkFailure.h" +#import "OCLocaleFilterVariables.h" // Imported to use the identifiers in OCConnectionPreferredAuthenticationMethodIDs only #import "OCAuthenticationMethodOpenIDConnect.h" @@ -122,10 +124,14 @@ + (OCClassSettingsIdentifier)classSettingsIdentifier OCConnectionEndpointIDWebDAV : @"remote.php/dav/files", // Polled in intervals to detect changes to the root directory ETag OCConnectionEndpointIDWebDAVMeta : @"remote.php/dav/meta", // Metadata DAV endpoint, used for private link resolution OCConnectionEndpointIDStatus : @"status.php", // Requested during login and polled in intervals during maintenance mode - OCConnectionEndpointIDThumbnail : @"index.php/apps/files/api/v1/thumbnail", // Requested once per item thumbnail request OCConnectionEndpointIDShares : @"ocs/v2.php/apps/files_sharing/api/v1/shares", // Polled in intervals to detect changes if OCShareQuery is used with the interval option OCConnectionEndpointIDRemoteShares : @"ocs/v2.php/apps/files_sharing/api/v1/remote_shares",// Polled in intervals to detect changes if OCShareQuery is used with the interval option OCConnectionEndpointIDRecipients : @"ocs/v2.php/apps/files_sharing/api/v1/sharees", // Requested once per search string change when searching for recipients + OCConnectionEndpointIDAvatars : @"remote.php/dav/avatars", // Requested once per user per session (adding /[user]/[size-in-pixels]) + + OCConnectionEndpointIDGraphMeDrives : @"graph/v1.0/me/drives", // Drives of the user + OCConnectionEndpointIDGraphDrives : @"graph/v1.0/drives", // Drives + OCConnectionPreferredAuthenticationMethodIDs : @[ OCAuthenticationMethodIdentifierOpenIDConnect, OCAuthenticationMethodIdentifierOAuth2, OCAuthenticationMethodIdentifierBasicAuth ], OCConnectionCertificateExtendedValidationRule : @"bookmarkCertificate == serverCertificate", OCConnectionRenewedCertificateAcceptanceRule : @"(bookmarkCertificate.publicKeyData == serverCertificate.publicKeyData) OR ((check.parentCertificatesHaveIdenticalPublicKeys == true) AND (serverCertificate.passedValidationOrIsUserAccepted == true)) OR ((bookmarkCertificate.parentCertificate.sha256Fingerprint.asFingerPrintString == \"73 0C 1B DC D8 5F 57 CE 5D C0 BB A7 33 E5 F1 BA 5A 92 5B 2A 77 1D 64 0A 26 F7 A4 54 22 4D AD 3B\") AND (bookmarkCertificate.rootCertificate.sha256Fingerprint.asFingerPrintString == \"06 87 26 03 31 A7 24 03 D9 09 F1 05 E6 9B CF 0D 32 E1 BD 24 93 FF C6 D9 20 6D 11 BC D6 77 07 39\") AND (serverCertificate.parentCertificate.sha256Fingerprint.asFingerPrintString == \"67 AD D1 16 6B 02 0A E6 1B 8F 5F C9 68 13 C0 4C 2A A5 89 96 07 96 86 55 72 A3 C7 E7 37 61 3D FD\") AND (serverCertificate.rootCertificate.sha256Fingerprint.asFingerPrintString == \"96 BC EC 06 26 49 76 F3 74 60 77 9A CF 28 C5 A7 CF E8 A3 C0 AA E1 1A 8F FC EE 05 C0 BD DF 08 C6\") AND (serverCertificate.passedValidationOrIsUserAccepted == true))", @@ -142,7 +148,6 @@ + (OCClassSettingsIdentifier)classSettingsIdentifier - ISRG Root X1: 96 BC EC 06 26 49 76 F3 74 60 77 9A CF 28 C5 A7 CF E8 A3 C0 AA E1 1A 8F FC EE 05 C0 BD DF 08 C6 */ - OCConnectionMinimumVersionRequired : @"10.0", OCConnectionAllowBackgroundURLSessions : @(YES), OCConnectionForceBackgroundURLSessions : @(NO), @@ -268,14 +273,6 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) }, - OCConnectionEndpointIDThumbnail : @{ - OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, - OCClassSettingsMetadataKeyDescription : @"Path of the thumbnail endpoint.", - OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Endpoints", - OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) - }, - OCConnectionEndpointIDShares : @{ OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, OCClassSettingsMetadataKeyDescription : @"Path of the shares API endpoint.", @@ -337,6 +334,14 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) }, + OCConnectionAssociatedCertificatesTrackingRule : @{ + OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, + OCClassSettingsMetadataKeyDescription : @"Rule that defines the criteria that need to be met by a hostname other than a bookmark's hostname for the associated certificate to be added to the bookmark, tracked for changes and validated by the same rules as the bookmark's primary certificate. No value (default) or a value of `(0 == 1)` disables this feature. A value of `$hostname like \"*.mycompany.com\"` tracks the certificates for all hosts ending with mycompany.com.", + OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, + OCClassSettingsMetadataKeyCategory : @"Security", + OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) + }, + OCConnectionTransparentTemporaryRedirect : @{ OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, OCClassSettingsMetadataKeyDescription : @"Controls whether 307 redirects are handled transparently at the HTTP pipeline level (by resending the headers and body).", @@ -451,6 +456,8 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark _usersByUserID = [NSMutableDictionary new]; + _drivesByID = [NSMutableDictionary new]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_connectionCertificateUserApproved) name:self.bookmark.certificateUserApprovalUpdateNotificationName object:nil]; // Get pipelines @@ -1449,6 +1456,8 @@ - (NSProgress *)connectWithCompletionHandler:(void(^)(NSError *error, OCIssue *i connectProgress.localizedDescription = OCLocalizedString(@"Fetching user information…", @""); [self retrieveLoggedInUserWithCompletionHandler:^(NSError *error, OCUser *loggedInUser) { + BOOL saveBookmark = NO; + self.loggedInUser = loggedInUser; // Update bookmark.userDisplayName if it has changed @@ -1458,11 +1467,7 @@ - (NSProgress *)connectWithCompletionHandler:(void(^)(NSError *error, OCIssue *i self.bookmark.user = loggedInUser; self.bookmark.userDisplayName = loggedInUser.displayName; - if (self.bookmark.authenticationDataStorage == OCBookmarkAuthenticationDataStorageKeychain) - { - // Update bookmark - IF it is not a working copy - [OCBookmarkManager.sharedBookmarkManager updateBookmark:self.bookmark]; - } + saveBookmark = YES; } connectProgress.localizedDescription = OCLocalizedString(@"Connected", @""); @@ -1473,12 +1478,63 @@ - (NSProgress *)connectWithCompletionHandler:(void(^)(NSError *error, OCIssue *i } else { - // DONE! - connectProgress.localizedDescription = OCLocalizedString(@"Connected", @""); + // Favorites + if (capabilities.supportsFavorites.boolValue != [self.bookmark hasCapability:OCBookmarkCapabilityFavorites]) + { + if (capabilities.supportsFavorites.boolValue) + { + [self.bookmark addCapability:OCBookmarkCapabilityFavorites]; + } + else + { + [self.bookmark removeCapability:OCBookmarkCapabilityFavorites]; + } + + saveBookmark = YES; + } - self.state = OCConnectionStateConnected; + // Drives + if (self.useDriveAPI) + { + // Drives + // Make sure drives capability is present in bookmark + if (![self.bookmark hasCapability:OCBookmarkCapabilityDrives]) + { + [self.bookmark addCapability:OCBookmarkCapabilityDrives]; + saveBookmark = YES; + } + + // Get a list of drives + [self retrieveDriveListWithCompletionHandler:^(NSError * _Nullable error, NSArray * _Nullable drives) { + if (error!=nil) + { + completionHandler(error, [OCIssue issueForError:error level:OCIssueLevelError issueHandler:nil]); + } + else + { + connectProgress.localizedDescription = OCLocalizedString(@"Connected", @""); + + self.state = OCConnectionStateConnected; + + completionHandler(error, nil); + } + }]; + } + else + { + // No drives? DONE! + connectProgress.localizedDescription = OCLocalizedString(@"Connected", @""); + + self.state = OCConnectionStateConnected; + + completionHandler(nil, nil); + } - completionHandler(nil, nil); + if (saveBookmark && (self.bookmark.authenticationDataStorage == OCBookmarkAuthenticationDataStorageKeychain)) + { + // Update bookmark - IF it is not a working copy + [OCBookmarkManager.sharedBookmarkManager updateBookmark:self.bookmark]; + } } }]; } @@ -1753,14 +1809,9 @@ - (OCHTTPDAVRequest *)_propfindDAVRequestForPath:(OCPath)path endpointURL:(NSURL return (davRequest); } -- (NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth completionHandler:(void(^)(NSError *error, NSArray *items))completionHandler -{ - return ([self retrieveItemListAtPath:path depth:depth options:nil completionHandler:completionHandler]); -} - -- (NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth options:(NSDictionary *)options completionHandler:(void(^)(NSError *error, NSArray *items))completionHandler +- (NSProgress *)retrieveItemListAtLocation:(OCLocation *)location depth:(NSUInteger)depth options:(NSDictionary *)options completionHandler:(void(^)(NSError *error, NSArray *items))completionHandler { - return ([self retrieveItemListAtPath:path depth:depth options:options resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + return ([self retrieveItemListAtLocation:location depth:depth options:options resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { if (event.error != nil) { completionHandler(event.error, nil); @@ -1772,13 +1823,36 @@ - (NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth opti } userInfo:nil ephermalUserInfo:nil]]); } -- (NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth options:(NSDictionary *)options resultTarget:(OCEventTarget *)eventTarget +- (NSProgress *)retrieveItemListAtLocation:(OCLocation *)location depth:(NSUInteger)depth options:(NSDictionary *)options resultTarget:(OCEventTarget *)eventTarget { OCHTTPDAVRequest *davRequest; NSProgress *progress = nil; NSURL *endpointURL; + OCDriveID driveID = options[OCConnectionOptionDriveID]; + OCPath path = location.path; + + if (path == nil) + { + [eventTarget handleError:OCError(OCErrorInsufficientParameters) type:OCEventTypeRetrieveItemList uuid:nil sender:self]; + return (nil); + } + + if (driveID == nil) + { + driveID = location.driveID; + } - if ((endpointURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil]) != nil) + if (self.useDriveAPI && (driveID == nil)) + { + // Drive ID required for accounts with Drive API + OCLogWarning(@"retrieveItemListAtLocation: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:OCEventTypeRetrieveItemList uuid:nil sender:self]; + return (nil); + } + + if ((endpointURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ + OCConnectionEndpointURLOptionDriveID : OCNullProtect(driveID) + }]) != nil) { if ((davRequest = [self _propfindDAVRequestForPath:path endpointURL:endpointURL depth:depth]) != nil) { @@ -1797,7 +1871,8 @@ - (NSProgress *)retrieveItemListAtPath:(OCPath)path depth:(NSUInteger)depth opti @"path" : path, @"depth" : @(depth), @"endpointURL" : endpointURL, - @"options" : ((options != nil) ? options : [NSNull null]) + @"driveID" : OCNullProtect(driveID), + @"options" : OCNullProtect(options) }; davRequest.eventTarget = eventTarget; davRequest.downloadRequest = YES; @@ -1884,6 +1959,7 @@ - (void)_handleRetrieveItemListAtPathResult:(OCHTTPRequest *)request error:(NSEr NSDictionary *options = [request.userInfo[@"options"] isKindOfClass:[NSDictionary class]] ? request.userInfo[@"options"] : nil; OCEventType eventType = OCEventTypeRetrieveItemList; NSURL *responseDestinationURL = options[OCConnectionOptionResponseDestinationURL]; + OCDriveID driveID = OCNullResolved(request.userInfo[@"driveID"]); if ((options!=nil) && (options[@"alternativeEventType"]!=nil)) { @@ -1926,6 +2002,8 @@ - (void)_handleRetrieveItemListAtPathResult:(OCHTTPRequest *)request error:(NSEr } } + event.driveID = driveID; + if ((event.error == nil) && (responseDestinationURL == nil)) { NSArray *errors = nil; @@ -1933,7 +2011,7 @@ - (void)_handleRetrieveItemListAtPathResult:(OCHTTPRequest *)request error:(NSEr // OCLogDebug(@"Error: %@ - Response: %@", OCLogPrivate(error), ((request.downloadRequest && (request.downloadedFileURL != nil)) ? OCLogPrivate([NSString stringWithContentsOfURL:request.downloadedFileURL encoding:NSUTF8StringEncoding error:NULL]) : nil)); - items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:_usersByUserID withErrors:&errors]; + items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:_usersByUserID driveID:driveID withErrors:&errors]; if ((items.count == 0) && (errors.count > 0) && (event.error == nil)) { @@ -2078,7 +2156,15 @@ - (OCProgress *)downloadItem:(OCItem *)item to:(NSURL *)targetURL options:(NSDic return(nil); } - if ((downloadURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil] URLByAppendingPathComponent:item.path]) != nil) + if (self.useDriveAPI && (item.driveID == nil)) + { + // Drive ID required for accounts with Drive API + OCLogWarning(@"downloadItem: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:OCEventTypeDownload uuid:nil sender:self]; + return (nil); + } + + if ((downloadURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(item.driveID) }] URLByAppendingPathComponent:item.path]) != nil) { OCHTTPRequest *request = [OCHTTPRequest requestWithURL:downloadURL]; @@ -2186,6 +2272,20 @@ - (void)_handleDownloadItemResult:(OCHTTPRequest *)request error:(NSError *)erro } break; + case OCHTTPStatusCodeTOO_EARLY: { + NSString *itemName = OCTypedCast(request.userInfo[@"item"], OCItem).name; + + if (itemName == nil) + { + itemName = OCLocalized(@"File"); + } + + event.error = OCErrorWithDescriptionFromError(OCErrorItemProcessing, OCLocalizedFormat(@"{{itemName}} is currently processed on the server and can't be downloaded until it finishes processing.", @{ + @"itemName" : itemName + }), request.httpResponse.status.error); + } + break; + case OCHTTPStatusCodeFORBIDDEN: { NSError *davError; @@ -2224,7 +2324,15 @@ - (OCProgress *)updateItem:(OCItem *)item properties:(NSArray *items) { + [self retrieveItemListAtLocation:[[OCLocation alloc] initWithDriveID:parentItem.driveID path:fullFolderPath] depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { OCItem *newFolderItem = items.firstObject; newFolderItem.parentFileID = parentItem.fileID; @@ -2566,11 +2682,20 @@ - (OCProgress *)_copyMoveMethod:(OCHTTPMethod)requestMethod type:(OCEventType)ev { OCProgress *requestProgress = nil; NSURL *sourceItemURL, *destinationURL; - NSURL *webDAVRootURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil]; + NSURL *sourceWebDAVRootURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(item.driveID) }]; + NSURL *destinationWebDAVRootURL = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(parentItem.driveID) }]; + + if (self.useDriveAPI && ((item.driveID == nil) || (parentItem.driveID == nil))) + { + // Drive ID required for accounts with Drive API + OCLogWarning(@"copyMoveItem: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:eventType uuid:nil sender:self]; + return (nil); + } - if ((sourceItemURL = [webDAVRootURL URLByAppendingPathComponent:item.path]) != nil) + if ((sourceItemURL = [sourceWebDAVRootURL URLByAppendingPathComponent:item.path]) != nil) { - if ((destinationURL = [[webDAVRootURL URLByAppendingPathComponent:parentItem.path] URLByAppendingPathComponent:newName]) != nil) + if ((destinationURL = [[destinationWebDAVRootURL URLByAppendingPathComponent:parentItem.path] URLByAppendingPathComponent:newName]) != nil) { OCHTTPRequest *request = [OCHTTPRequest requestWithURL:sourceItemURL]; @@ -2659,7 +2784,7 @@ - (void)_handleCopyMoveItemResult:(OCHTTPRequest *)request error:(NSError *)erro postEvent = NO; // Wait until all info on the new item has been received // Retrieve all details on the new item - [self retrieveItemListAtPath:newFullPath depth:0 completionHandler:^(NSError *error, NSArray *items) { + [self retrieveItemListAtLocation:[[OCLocation alloc] initWithDriveID:parentItem.driveID path:newFullPath] depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { OCItem *newItem = items.firstObject; newItem.parentFileID = parentItem.fileID; @@ -2725,7 +2850,15 @@ - (OCProgress *)deleteItem:(OCItem *)item requireMatch:(BOOL)requireMatch result OCProgress *requestProgress = nil; NSURL *deleteItemURL; - if ((deleteItemURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil] URLByAppendingPathComponent:item.path]) != nil) + if (self.useDriveAPI && (item.driveID == nil)) + { + // Drive ID required for accounts with Drive API + OCLogWarning(@"deleteItem: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:OCEventTypeDelete uuid:nil sender:self]; + return (nil); + } + + if ((deleteItemURL = [[self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(item.driveID) }] URLByAppendingPathComponent:item.path]) != nil) { OCHTTPRequest *request = [OCHTTPRequest requestWithURL:deleteItemURL]; @@ -2884,62 +3017,47 @@ - (NSProgress *)retrieveThumbnailFor:(OCItem *)item to:(NSURL *)localThumbnailUR return (nil); } - if (item.type != OCItemTypeCollection) + if (self.useDriveAPI && (item.driveID == nil)) { - if (self.supportsPreviewAPI) - { - // Preview API (OC 10.0.9+) - url = [self URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil]; - - if (url == nil) - { - // WebDAV root could not be generated (likely due to lack of username) - [eventTarget handleError:OCError(OCErrorInternal) type:OCEventTypeRetrieveThumbnail uuid:nil sender:self]; - return (nil); - } - - // Add path - if (item.path != nil) - { - url = [url URLByAppendingPathComponent:item.path]; - } - - // Compose request - request = [OCHTTPRequest requestWithURL:url]; + // Drive ID required for accounts with Drive API + OCLogWarning(@"retrieveThumbnail: API call without drive ID in drive-based account"); + [eventTarget handleError:OCError(OCErrorMissingDriveID) type:OCEventTypeRetrieveThumbnail uuid:nil sender:self]; + return (nil); + } - request.groupID = item.path.stringByDeletingLastPathComponent; - request.priority = NSURLSessionTaskPriorityDefault; + if (item.type != OCItemTypeCollection) + { + // Preview API (OC 10.0.9+) + url = [self URLForEndpoint:OCConnectionEndpointIDPreview options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(item.driveID) }]; - request.parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: - @(size.width).stringValue, @"x", - @(size.height).stringValue, @"y", - item.eTag, @"c", - @"1", @"a", // Request resize respecting aspect ratio - @"1", @"preview", - nil]; + if (url == nil) + { + // WebDAV root could not be generated (likely due to lack of username) + [eventTarget handleError:OCError(OCErrorInternal) type:OCEventTypeRetrieveThumbnail uuid:nil sender:self]; + return (nil); } - else + + // Add path + if (item.path != nil) { - // Thumbnail API (OC < 10.0.9) - url = [self URLForEndpoint:OCConnectionEndpointIDThumbnail options:nil]; + url = [url URLByAppendingPathComponent:item.path]; + } - if (url == nil) - { - // WebDAV root could not be generated (likely due to lack of username) - [eventTarget handleError:OCError(OCErrorInternal) type:OCEventTypeRetrieveThumbnail uuid:nil sender:self]; - return (nil); - } + // Compose request + request = [OCHTTPRequest requestWithURL:url]; - url = [url URLByAppendingPathComponent:[NSString stringWithFormat:@"%d/%d/%@", (int)size.height, (int)size.width, item.path]]; + request.groupID = item.path.stringByDeletingLastPathComponent; + request.priority = NSURLSessionTaskPriorityDefault; - // Compose request - request = [OCHTTPRequest requestWithURL:url]; - /* + request.parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @(size.width).stringValue, @"x", + @(size.height).stringValue, @"y", + item.eTag, @"c", + @"1", @"a", // Request resize respecting aspect ratio + @"1", @"preview", - // Not supported for OC < 10.0.9 - error = [NSError errorWithOCError:OCErrorFeatureNotSupportedByServer]; - */ - } + @"0", @"scalingup", // do not scale up images (new in ocis) + nil]; } else { @@ -2999,25 +3117,40 @@ - (void)_handleRetrieveThumbnailResult:(OCHTTPRequest *)request error:(NSError * { if (request.httpResponse.status.isSuccess) { - OCItemThumbnail *thumbnail = [OCItemThumbnail new]; - OCItemVersionIdentifier *itemVersionIdentifier = request.userInfo[OCEventUserInfoKeyItemVersionIdentifier]; - CGSize maximumSize = ((NSValue *)request.userInfo[@"maximumSize"]).CGSizeValue; - - thumbnail.mimeType = request.httpResponse.headerFields[OCHTTPHeaderFieldNameContentType]; - - if ((request.httpResponse.bodyURL != nil) && !request.httpResponse.bodyURLIsTemporary) + if (![request.httpResponse.contentType hasPrefix:@"image/"]) { - thumbnail.url = request.downloadedFileURL; + // Do not accept anything but images as thumbnail (https://github.com/owncloud/ocis/issues/3558) + event.error = OCError(OCErrorFeatureNotSupportedForItem); } else { - thumbnail.data = request.httpResponse.bodyData; - } + OCItemThumbnail *thumbnail = [OCItemThumbnail new]; + OCItemVersionIdentifier *itemVersionIdentifier = request.userInfo[OCEventUserInfoKeyItemVersionIdentifier]; + CGSize maximumSize = ((NSValue *)request.userInfo[@"maximumSize"]).CGSizeValue; + + thumbnail.mimeType = request.httpResponse.contentType; + + if ((request.httpResponse.bodyURL != nil) && !request.httpResponse.bodyURLIsTemporary) + { + thumbnail.url = request.downloadedFileURL; + } + else + { + thumbnail.data = request.httpResponse.bodyData; + } + + thumbnail.itemVersionIdentifier = itemVersionIdentifier; + thumbnail.maxPixelSize = maximumSize; - thumbnail.itemVersionIdentifier = itemVersionIdentifier; - thumbnail.maximumSizeInPixels = maximumSize; + thumbnail.fillMode = OCImageFillModeScaleToFit; - event.result = thumbnail; + event.result = thumbnail; + } + } + else if (request.httpResponse.status.code == OCHTTPStatusCodeNOT_FOUND) + { + // No thumbnail available for item + event.error = OCError(OCErrorFeatureNotSupportedForItem); } else { @@ -3113,7 +3246,7 @@ - (void)_handleFilterFilesResult:(OCHTTPRequest *)request error:(NSError *)error if (endpointURL != nil) { - if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID withErrors:&errors]) != nil) + if ((items = [((OCHTTPDAVRequest *)request) responseItemsForBasePath:endpointURL.path reuseUsersByID:self->_usersByUserID driveID:nil withErrors:&errors]) != nil) { event.result = items; } @@ -3195,13 +3328,19 @@ - (NSError *)sendSynchronousRequest:(OCHTTPRequest *)request OCConnectionEndpointID OCConnectionEndpointIDWebDAV = @"endpoint-webdav"; OCConnectionEndpointID OCConnectionEndpointIDWebDAVMeta = @"endpoint-webdav-meta"; OCConnectionEndpointID OCConnectionEndpointIDWebDAVRoot = @"endpoint-webdav-root"; -OCConnectionEndpointID OCConnectionEndpointIDThumbnail = @"endpoint-thumbnail"; +OCConnectionEndpointID OCConnectionEndpointIDPreview = @"endpoint-preview"; OCConnectionEndpointID OCConnectionEndpointIDStatus = @"endpoint-status"; OCConnectionEndpointID OCConnectionEndpointIDShares = @"endpoint-shares"; OCConnectionEndpointID OCConnectionEndpointIDRemoteShares = @"endpoint-remote-shares"; OCConnectionEndpointID OCConnectionEndpointIDRecipients = @"endpoint-recipients"; +OCConnectionEndpointID OCConnectionEndpointIDAvatars = @"endpoint-avatars"; +OCConnectionEndpointID OCConnectionEndpointIDAppProviderList = @"app-provider-list"; +OCConnectionEndpointID OCConnectionEndpointIDAppProviderOpen = @"app-provider-open"; +OCConnectionEndpointID OCConnectionEndpointIDAppProviderOpenWeb = @"app-provider-open-web"; +OCConnectionEndpointID OCConnectionEndpointIDAppProviderNew = @"app-provider-new"; OCConnectionEndpointURLOption OCConnectionEndpointURLOptionWellKnownSubPath = @"well-known-subpath"; +OCConnectionEndpointURLOption OCConnectionEndpointURLOptionDriveID = @"drive-id"; OCClassSettingsIdentifier OCClassSettingsIdentifierConnection = @"connection"; @@ -3209,6 +3348,7 @@ - (NSError *)sendSynchronousRequest:(OCHTTPRequest *)request OCClassSettingsKey OCConnectionAllowedAuthenticationMethodIDs = @"allowed-authentication-methods"; OCClassSettingsKey OCConnectionCertificateExtendedValidationRule = @"certificate-extended-validation-rule"; OCClassSettingsKey OCConnectionRenewedCertificateAcceptanceRule = @"renewed-certificate-acceptance-rule"; +OCClassSettingsKey OCConnectionAssociatedCertificatesTrackingRule = @"associated-certificates-tracking-rule"; OCClassSettingsKey OCConnectionMinimumVersionRequired = @"minimum-server-version"; OCClassSettingsKey OCConnectionAllowBackgroundURLSessions = @"allow-background-url-sessions"; OCClassSettingsKey OCConnectionForceBackgroundURLSessions = @"force-background-url-sessions"; @@ -3230,6 +3370,7 @@ - (NSError *)sendSynchronousRequest:(OCHTTPRequest *)request OCConnectionOptionKey OCConnectionOptionForceReplaceKey = @"force-replace"; OCConnectionOptionKey OCConnectionOptionResponseDestinationURL = @"response-destination-url"; OCConnectionOptionKey OCConnectionOptionResponseStreamHandler = @"response-stream-handler"; +OCConnectionOptionKey OCConnectionOptionDriveID = @"drive-id"; OCConnectionSetupOptionKey OCConnectionSetupOptionUserName = @"user-name"; diff --git a/ownCloudSDK/Connection/OData/OCConnection+OData.h b/ownCloudSDK/Connection/OData/OCConnection+OData.h new file mode 100644 index 00000000..d2f73346 --- /dev/null +++ b/ownCloudSDK/Connection/OData/OCConnection+OData.h @@ -0,0 +1,36 @@ +// +// OCConnection+OData.h +// ownCloudSDK +// +// Created by Felix Schwarz on 10.02.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 "OCConnection.h" +#import "OCODataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^OCConnectionODataRequestCompletionHandler)(NSError * _Nullable error, id _Nullable response); + +@interface OCConnection (OData) + +- (NSProgress *)requestODataAtURL:(NSURL *)url requireSignals:(nullable NSSet *)requiredSignals selectEntityID:(nullable OCODataEntityID)selectEntityID selectProperties:(nullable NSArray *)selectProperties filterString:(nullable OCODataFilterString)filterString entityClass:(Class)entityClass completionHandler:(OCConnectionODataRequestCompletionHandler)completionHandler; + +//- (NSProgress *)createODataObject:(id)object atURL:(NSURL *)url +//- (NSProgress *)updateODataObject:(id)…; +//- (NSProgress *)removeOData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Connection/OData/OCConnection+OData.m b/ownCloudSDK/Connection/OData/OCConnection+OData.m new file mode 100644 index 00000000..4ceaee85 --- /dev/null +++ b/ownCloudSDK/Connection/OData/OCConnection+OData.m @@ -0,0 +1,108 @@ +// +// OCConnection+OData.m +// ownCloudSDK +// +// Created by Felix Schwarz on 10.02.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 "OCConnection+OData.h" +#import "GAODataError.h" + +@implementation OCConnection (OData) + +- (NSProgress *)requestODataAtURL:(NSURL *)url requireSignals:(nullable NSSet *)requiredSignals selectEntityID:(nullable OCODataEntityID)selectEntityID selectProperties:(nullable NSArray *)selectProperties filterString:(nullable OCODataFilterString)filterString entityClass:(Class)entityClass completionHandler:(OCConnectionODataRequestCompletionHandler)completionHandler +{ + OCHTTPRequest *request; + NSProgress *progress = nil; + NSString *urlSuffix = nil; + OCHTTPRequestParameters requestParameters = [NSMutableDictionary new]; + + // Select entity: "…/endpoint('entityID')" + if ((selectEntityID != nil) && (selectEntityID.length > 0)) + { + // urlSuffix = [NSString stringWithFormat:@"('%@')", selectEntityID]; // as per OData specification + urlSuffix = [NSString stringWithFormat:@"/%@", selectEntityID]; // practically supported + } + + if (urlSuffix != nil) + { + url = [NSURL URLWithString:[url.absoluteString stringByAppendingString:urlSuffix]]; + } + + // Select properties: "…/endpoint?$select=PropertyName1, PropertyName2" + if ((selectProperties != nil) && (selectProperties.count > 0)) + { + requestParameters[@"$select"] = [selectProperties componentsJoinedByString:@", "]; + } + + // Filter string: "…/endpoint?$filter=FirstName eq 'Scott'" + if (filterString.length > 0) + { + requestParameters[@"$filter"] = filterString; + } + + // Compose HTTP request + request = [OCHTTPRequest requestWithURL:url]; + request.requiredSignals = requiredSignals; // self.actionSignals; + if (requestParameters.count > 0) + { + request.parameters = requestParameters; + } + + progress = [self sendRequest:request ephermalCompletionHandler:^(OCHTTPRequest *request, OCHTTPResponse *response, NSError *error) { + NSDictionary *jsonDictionary = nil; + NSError *returnError = error; + id returnResult = nil; + + if (error == nil) + { + NSError *jsonError = nil; + + if ((jsonDictionary = [response bodyConvertedDictionaryFromJSONWithError:&jsonError]) != nil) + { + if (jsonDictionary[@"error"]) + { + NSError *decodeError = nil; + + GAODataError *dataError = [jsonDictionary objectForKey:@"error" ofClass:GAODataError.class inCollection:Nil required:NO context:nil error:&decodeError]; + returnError = dataError.nativeError; + } + else if (jsonDictionary[@"value"]) + { + if ([jsonDictionary[@"value"] isKindOfClass:NSArray.class]) + { + returnResult = [jsonDictionary objectForKey:@"value" ofClass:entityClass inCollection:NSArray.class required:NO context:nil error:&returnError]; + } + else + { + returnResult = [jsonDictionary objectForKey:@"value" ofClass:entityClass inCollection:Nil required:NO context:nil error:&returnError]; + } + } + } + else if (jsonError != nil) + { + returnError = jsonError; + } + + } + + OCLogDebug(@"OData response: returnResult=%@, error=%@, json: %@", returnResult, returnError, jsonDictionary); + + completionHandler(returnError, returnResult); + }]; + + return (progress); +} + +@end diff --git a/ownCloudSDK/Connection/OData/OCODataTypes.h b/ownCloudSDK/Connection/OData/OCODataTypes.h new file mode 100644 index 00000000..4f114a9c --- /dev/null +++ b/ownCloudSDK/Connection/OData/OCODataTypes.h @@ -0,0 +1,27 @@ +// +// OCODataTypes.h +// ownCloudSDK +// +// Created by Felix Schwarz on 10.02.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 + +typedef NSString* OCODataEntityID; +typedef NSString* OCODataProperty; +typedef NSString* OCODataFilterString; + +NS_ASSUME_NONNULL_BEGIN + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.h b/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.h new file mode 100644 index 00000000..77509291 --- /dev/null +++ b/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.h @@ -0,0 +1,30 @@ +// +// OCQueryCondition+ODataBuilder.h +// ownCloudSDK +// +// Created by Felix Schwarz on 10.02.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 "OCQueryCondition.h" +#import "OCODataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCQueryCondition (ODataBuilder) + +- (nullable OCODataFilterString)filterString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.m b/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.m new file mode 100644 index 00000000..08e28e67 --- /dev/null +++ b/ownCloudSDK/Connection/OData/OCQueryCondition+ODataBuilder.m @@ -0,0 +1,29 @@ +// +// OCQueryCondition+ODataBuilder.m +// ownCloudSDK +// +// Created by Felix Schwarz on 10.02.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 "OCQueryCondition+ODataBuilder.h" + +@implementation OCQueryCondition (ODataBuilder) + +- (OCODataFilterString)filterString +{ + // TBD + return (nil); +} + +@end diff --git a/ownCloudSDK/Core/AvailableOffline/OCCore+AvailableOffline.m b/ownCloudSDK/Core/AvailableOffline/OCCore+AvailableOffline.m index 4bb3d569..1d85bf39 100644 --- a/ownCloudSDK/Core/AvailableOffline/OCCore+AvailableOffline.m +++ b/ownCloudSDK/Core/AvailableOffline/OCCore+AvailableOffline.m @@ -35,7 +35,7 @@ - (OCItemPolicy *)_createAvailableOfflinePolicyForItem:(OCItem *)item if ((newItemPolicy = [[OCItemPolicy alloc] initWithKind:OCItemPolicyKindAvailableOffline item:item]) != nil) { - newItemPolicy.path = item.path; + newItemPolicy.location = item.location; newItemPolicy.localID = item.localID; newItemPolicy.policyAutoRemovalMethod = OCItemPolicyAutoRemovalMethodNoItems; @@ -91,7 +91,7 @@ - (void)makeAvailableOffline:(OCItem *)item options:(nullable NSDictionary 0) { - [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } } }]; @@ -155,7 +155,7 @@ - (void)makeAvailableOffline:(OCItem *)item options:(nullable NSDictionary * _Nullable allPolicies) { NSArray *redundantPolicies = [allPolicies filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(OCItemPolicy * _Nullable existingPolicy, NSDictionary * _Nullable bindings) { return ( (![newItemPolicy.databaseID isEqual:existingPolicy.databaseID]) && // Skip new entry :-D - ((existingPolicy.path != nil) && (newItemPolicy.path != nil) && ([existingPolicy.path hasPrefix:newItemPolicy.path])) // Both have paths and existing policy's path is identical or a sub-path of new path + ((existingPolicy.location != nil) && (newItemPolicy.location != nil) && ([existingPolicy.location isLocatedIn:newItemPolicy.location])) // Both have paths and existing policy's path is identical or a sub-path of new path ); }]]; @@ -165,7 +165,7 @@ - (void)makeAvailableOffline:(OCItem *)item options:(nullable NSDictionary 1) { @@ -201,7 +201,7 @@ - (void)makeAvailableOffline:(OCItem *)item options:(nullable NSDictionary *availableOfflinePolicies = [self retrieveAvailableOfflinePoliciesCoveringItem:nil completionHandler:nil]; - [_availableOfflineFolderPaths removeAllObjects]; + [_availableOfflineFolderLocations removeAllObjects]; [_availableOfflineIDs removeAllObjects]; for (OCItemPolicy *policy in availableOfflinePolicies) { - if (policy.path != nil) + if (policy.location.path != nil) { - [_availableOfflineFolderPaths addObject:policy.path]; + [_availableOfflineFolderLocations addObject:policy.location]; } if (policy.localID != nil) @@ -273,7 +273,7 @@ - (OCCoreAvailableOfflineCoverage)availableOfflinePolicyCoverageOfItem:(OCItem * { OCCoreAvailableOfflineCoverage coverage = OCCoreAvailableOfflineCoverageNone; - @synchronized(_availableOfflineFolderPaths) + @synchronized(_availableOfflineFolderLocations) { [self _updateAvailableOfflineCaches]; @@ -283,18 +283,18 @@ - (OCCoreAvailableOfflineCoverage)availableOfflinePolicyCoverageOfItem:(OCItem * } else { - OCPath itemPath; + OCLocation *itemLocation; - if ((itemPath = item.path) != nil) + if ((itemLocation = item.location) != nil) { - for (OCPath folderPath in _availableOfflineFolderPaths) + for (OCLocation *folderLocation in _availableOfflineFolderLocations) { - if ([folderPath isEqualToString:itemPath]) + if ([folderLocation isEqual:itemLocation]) { coverage = OCCoreAvailableOfflineCoverageDirect; break; } - else if ([itemPath hasPrefix:folderPath]) + else if ([itemLocation isLocatedIn:folderLocation]) { coverage = OCCoreAvailableOfflineCoverageIndirect; } diff --git a/ownCloudSDK/Core/Claims/OCCore+Claims.m b/ownCloudSDK/Core/Claims/OCCore+Claims.m index 6b997985..a91acb5c 100644 --- a/ownCloudSDK/Core/Claims/OCCore+Claims.m +++ b/ownCloudSDK/Core/Claims/OCCore+Claims.m @@ -54,7 +54,7 @@ - (void)mutateItem:(OCItem *)inItem withBlock:(OCItem *(^)(OCItem *inItem, OCSyn if (itemModified && (saveItem != nil)) { - [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[saveItem] refreshPaths:nil newSyncAnchor:newSyncAnchor beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[saveItem] refreshLocations:nil newSyncAnchor:newSyncAnchor beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } } else diff --git a/ownCloudSDK/Core/Data Sources/OCCore+DataSources.h b/ownCloudSDK/Core/Data Sources/OCCore+DataSources.h new file mode 100644 index 00000000..5e4407b8 --- /dev/null +++ b/ownCloudSDK/Core/Data Sources/OCCore+DataSources.h @@ -0,0 +1,56 @@ +// +// OCCore+DataSources.h +// ownCloudSDK +// +// Created by Felix Schwarz on 28.03.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCore (DataSources) + +#pragma mark - Drives data sources (event-driven) +@property(readonly,strong,nonatomic) OCDataSource *drivesDataSource; //!< ALL drives +@property(readonly,strong,nonatomic) OCDataSource *subscribedDrivesDataSource; //!< Drives the user is subscribed to +@property(readonly,strong,nonatomic) OCDataSource *personalDriveDataSource; //!< Personal drive +@property(readonly,strong,nonatomic) OCDataSource *shareJailDriveDataSource; //!< Shares Jail drive +@property(readonly,strong,nonatomic) OCDataSource *projectDrivesDataSource; //!< Spaces the user is subscribed to (applies filter on .subscribedDrivesDataSource) + +#pragma mark - Sharing data sources (on-demand) +// On-demand data sources require constant polling or other additional overhead - they are only updated when they have subscribers +// Therefore subscriptions should not be "hoarded" on on-demand data sources, but only be kept around for as long as needed - and then terminated. +@property(readonly,strong,nonatomic) OCDataSource *sharedWithMePendingDataSource; +@property(readonly,strong,nonatomic) OCDataSource *sharedWithMeAcceptedDataSource; //!< Contents same as the Shared Jail drive +@property(readonly,strong,nonatomic) OCDataSource *sharedWithMeDeclinedDataSource; + +@property(readonly,strong,nonatomic) OCDataSource *sharedByMeDataSource; +@property(readonly,strong,nonatomic) OCDataSource *sharedByMeGroupedDataSource; +@property(readonly,strong,nonatomic) OCDataSource *sharedByLinkDataSource; + +@property(copy,nullable,nonatomic) OCCoreShareJailQueryCustomizer shareJailQueryCustomizer; //!< If sharedWithMeAcceptedDataSource contents is provided by a query on the Shares Jail, this block is applied to customize its sorting/filtering. + +@property(class,readonly,nonatomic) NSComparator sharesSortComparator; //!< NSComparator sorting shares by name, then recipient + +#pragma mark - Special datasources (on-demand) +// On-demand data sources require constant polling or other additional overhead - they are only updated when they have subscribers +// Therefore subscriptions should not be "hoarded" on on-demand data sources, but only be kept around for as long as needed - and then terminated. +@property(readonly,strong,nonatomic) OCDataSource *favoritesDataSource; +@property(readonly,strong,nonatomic) OCDataSource *availableOfflineItemPoliciesDataSource; +@property(readonly,strong,nonatomic) OCDataSource *availableOfflineFilesDataSource; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Data Sources/OCCore+DataSources.m b/ownCloudSDK/Core/Data Sources/OCCore+DataSources.m new file mode 100644 index 00000000..2c1ee834 --- /dev/null +++ b/ownCloudSDK/Core/Data Sources/OCCore+DataSources.m @@ -0,0 +1,1084 @@ +// +// OCCore+DataSources.m +// ownCloudSDK +// +// Created by Felix Schwarz on 28.03.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 "OCCore+DataSources.h" +#import "OCCore+Internal.h" + +@implementation OCCore (DataSources) + +#pragma mark - Drive selections + +- (OCDataSource *)drivesDataSource +{ + return (_drivesDataSource); +} + +- (OCDataSource *)subscribedDrivesDataSource +{ + return (_subscribedDrivesDataSource); +} + +- (OCDataSource *)personalDriveDataSource +{ + return (_personalDriveDataSource); +} + +- (OCDataSource *)shareJailDriveDataSource +{ + return (_shareJailDriveDataSource); +} + +- (OCDataSource *)projectDrivesDataSource +{ + return (_projectDrivesDataSource); +} + +#pragma mark - Shared sort comparator ++ (NSComparator)sharesSortComparator +{ + static dispatch_once_t onceToken; + static NSComparator sharesSortComparator; + + dispatch_once(&onceToken, ^{ + sharesSortComparator = [^NSComparisonResult(OCShare * _Nonnull share1, OCShare * _Nonnull share2) { + NSString *name1, *name2; + + name1 = share1.itemLocation.lastPathComponent; + name2 = share2.itemLocation.lastPathComponent; + + if ([name1 isEqual:name2]) + { + name1 = share1.recipient.displayName; + name2 = share2.recipient.displayName; + } + + if ((name1 != nil) && (name2 != nil)) + { + return ([name1 localizedCaseInsensitiveCompare:name2]); + } + + return (NSOrderedDescending); + } copy]; + }); + + return (sharesSortComparator); +} + +#pragma mark - Shared with me +/* + // Shared with user + shareQueryWithUser = OCShareQuery(scope: .sharedWithUser, item: nil) + + if let shareQueryWithUser = shareQueryWithUser { + shareQueryWithUser.refreshInterval = 60 + + shareQueryWithUser.initialPopulationHandler = { [weak self] (_) in + self?.updateSharedWithYouResult() + self?.updatePendingSharesResult() + } + shareQueryWithUser.changesAvailableNotificationHandler = shareQueryWithUser.initialPopulationHandler + + start(query: shareQueryWithUser) + } + + if core?.connection.capabilities?.federatedSharingSupported == true { + // Accepted cloud shares + shareQueryAcceptedCloudShares = OCShareQuery(scope: .acceptedCloudShares, item: nil) + + if let shareQueryAcceptedCloudShares = shareQueryAcceptedCloudShares { + shareQueryAcceptedCloudShares.refreshInterval = 60 + + shareQueryAcceptedCloudShares.initialPopulationHandler = { [weak self] (_) in + self?.updateSharedWithYouResult() + self?.updatePendingSharesResult() + } + shareQueryAcceptedCloudShares.changesAvailableNotificationHandler = shareQueryAcceptedCloudShares.initialPopulationHandler + + start(query: shareQueryAcceptedCloudShares) + } + + // Pending cloud shares + shareQueryPendingCloudShares = OCShareQuery(scope: .pendingCloudShares, item: nil) + + if let shareQueryPendingCloudShares = shareQueryPendingCloudShares { + shareQueryPendingCloudShares.refreshInterval = 60 + + shareQueryPendingCloudShares.initialPopulationHandler = { [weak self] (query) in + if let library = self { + library.pendingCloudSharesCounter = query.queryResults.count + self?.updatePendingSharesResult() + } + } + shareQueryPendingCloudShares.changesAvailableNotificationHandler = shareQueryPendingCloudShares.initialPopulationHandler + + start(query: shareQueryPendingCloudShares) + } + } + + // Shared by user + shareQueryByUser = OCShareQuery(scope: .sharedByUser, item: nil) + + if let shareQueryByUser = shareQueryByUser { + shareQueryByUser.refreshInterval = 60 + + shareQueryByUser.initialPopulationHandler = { [weak self] (_) in + self?.updateSharedByUserResults() + } + shareQueryByUser.changesAvailableNotificationHandler = shareQueryByUser.initialPopulationHandler + + start(query: shareQueryByUser) + } +*/ + +- (void)_updateSharedWithMeQueryForceStop:(BOOL)forceStop +{ + BOOL hasSubscribers; + + @synchronized (self) + { + hasSubscribers = _sharedWithMeSubscribingDataSources > 0; + } + + if (hasSubscribers && !forceStop) + { + BOOL startQuery = NO; + + @synchronized(self) + { + if (_sharedWithMeQuery == nil) + { + NSMutableArray *sources = [NSMutableArray new]; + OCDataSource *source; + dispatch_group_t synchronizationGroup = [self _sharedWithMeDataSource].synchronizationGroup; + + _sharedWithMeQuery = [OCShareQuery queryWithScope:OCShareScopeSharedWithUser item:nil]; + _sharedWithMeQuery.refreshInterval = 10; + + if ((source = _sharedWithMeQuery.dataSource) != nil) + { + source.synchronizationGroup = synchronizationGroup; + [sources addObject:source]; + } + + if (_connection.capabilities.federatedSharingSupported) + { + _pendingCloudSharesQuery = [OCShareQuery queryWithScope:OCShareScopePendingCloudShares item:nil]; + _pendingCloudSharesQuery.refreshInterval = 10; + if ((source = _pendingCloudSharesQuery.dataSource) != nil) + { + source.synchronizationGroup = synchronizationGroup; + [sources addObject:source]; + } + + _acceptedCloudSharesQuery = [OCShareQuery queryWithScope:OCShareScopeAcceptedCloudShares item:nil]; + _acceptedCloudSharesQuery.refreshInterval = 10; + if ((source = _acceptedCloudSharesQuery.dataSource) != nil) + { + source.synchronizationGroup = synchronizationGroup; + [sources addObject:source]; + } + } + + [[self _sharedWithMeDataSource] setSources:sources]; + + startQuery = YES; + } + } + + if (startQuery) + { + [self startQuery:_sharedWithMeQuery]; + + if (_acceptedCloudSharesQuery != nil) + { + [self startQuery:_acceptedCloudSharesQuery]; + } + + if (_pendingCloudSharesQuery != nil) + { + [self startQuery:_pendingCloudSharesQuery]; + } + } + } + else + { + OCShareQuery *shareQuery = nil, *acceptedCloudShareQuery = nil, *pendingCloudShareQuery = nil; + OCQuery *sharesJailQuery = nil; + + @synchronized(self) + { + if (_sharedWithMeQuery != nil) + { + shareQuery = _sharedWithMeQuery; + _sharedWithMeQuery = nil; + } + + if (_acceptedCloudSharesQuery != nil) + { + acceptedCloudShareQuery = _acceptedCloudSharesQuery; + _acceptedCloudSharesQuery = nil; + } + + if (_pendingCloudSharesQuery != nil) + { + pendingCloudShareQuery = _pendingCloudSharesQuery; + _pendingCloudSharesQuery = nil; + } + + if ((_sharesJailQuery != nil) && forceStop) + { + sharesJailQuery = _sharesJailQuery; + _sharesJailQuery = nil; + } + + [_sharedWithMeDataSource setSources:@[]]; + } + + if (shareQuery != nil) + { + [self stopQuery:shareQuery]; + } + + if (acceptedCloudShareQuery != nil) + { + [self stopQuery:acceptedCloudShareQuery]; + } + + if (pendingCloudShareQuery != nil) + { + [self stopQuery:pendingCloudShareQuery]; + } + + if (sharesJailQuery != nil) + { + [self stopQuery:sharesJailQuery]; + } + } +} + +- (OCDataSourceComposition *)_sharedWithMeDataSource +{ + @synchronized(self) + { + if (_sharedWithMeDataSource == nil) + { + _sharedWithMeDataSource = [[OCDataSourceComposition alloc] initWithSources:@[] applyCustomizations:nil]; + _sharedWithMeDataSource.synchronizationGroup = dispatch_group_create(); // Ensure consistency of derived data sources + + [_sharedWithMeDataSource setSortComparator:^NSComparisonResult(OCDataSource * _Nonnull source1, OCDataItemReference _Nonnull itemRef1, OCDataSource * _Nonnull source2, OCDataItemReference _Nonnull itemRef2) { + id obj1 = [source1 recordForItemRef:itemRef1 error:NULL].item; + id obj2 = [source2 recordForItemRef:itemRef2 error:NULL].item; + + return (OCCore.sharesSortComparator(obj1, obj2)); + }]; + } + } + + return (_sharedWithMeDataSource); +} + +- (void)_sharedWithMeSubscriberChange:(NSInteger)subscriberChange +{ + @synchronized(self) + { + _sharedWithMeSubscribingDataSources += subscriberChange; + } + + [self beginActivity:@"Update shared with me query"]; + + [self queueBlock:^{ + [self _updateSharedWithMeQueryForceStop:NO]; + [self endActivity:@"Update shared with me query"]; + }]; +} + +- (OCDataSourceComposition *)_compositionDataSourceForShareState:(OCShareState)shareState +{ + OCDataSource *sharedWithMeDataSource = self._sharedWithMeDataSource; + dispatch_group_t synchronizationGroup = sharedWithMeDataSource.synchronizationGroup; + + return ([[OCDataSourceComposition alloc] initWithSources:@[ sharedWithMeDataSource ] applyCustomizations:^(OCDataSourceComposition *dataSource) { + dataSource.synchronizationGroup = synchronizationGroup; + + [dataSource setFilter:^BOOL(OCDataSource * _Nonnull source, OCDataItemReference _Nonnull itemRef) { + OCDataItemRecord *record; + + if ((record = [source recordForItemRef:itemRef error:NULL]) != nil) + { + OCShare *share; + + if ((share = OCTypedCast(record.item, OCShare)) != nil) + { + return ([share.effectiveState isEqual:shareState]); + } + } + + return (NO); + }]; + }]); +} + +- (OCDataSource *)sharedWithMePendingDataSource +{ + @synchronized(self) + { + if (_sharedWithMePendingDataSource == nil) + { + _sharedWithMePendingDataSource = [self _compositionDataSourceForShareState:OCShareStatePending]; + + [_sharedWithMePendingDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _sharedWithMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + + return (_sharedWithMePendingDataSource); +} + +- (OCDataSource *)sharedWithMeAcceptedDataSource +{ + @synchronized(self) + { + if (_sharedWithMeAcceptedDataSource == nil) + { +// if (self.useDrives) +// { +// // Provide contents of share jail drive +// _sharedWithMeAcceptedDataSource = [[OCDataSourceComposition alloc] initWithSources:@[] applyCustomizations:nil]; +// +// [_sharedWithMeAcceptedDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { +// OCCore *core = (OCCore *)owner; +// OCDataSourceComposition *dataSource = (OCDataSourceComposition *)source; +// +// if (hasSubscribers) +// { +// if (core->_sharesJailQuery == nil) +// { +// OCQuery *query = [OCQuery queryForLocation:[[OCLocation alloc] initWithDriveID:OCDriveIDSharesJail path:@"/"]]; +// OCDataSource *queryResultsDataSource = query.queryResultsDataSource; +// if (queryResultsDataSource != nil) +// { +// [dataSource addSources:@[ queryResultsDataSource ]]; +// } +// +// core->_sharesJailQuery = query; +// +// if (core->_shareJailQueryCustomizer != nil) +// { +// core->_shareJailQueryCustomizer(query); +// } +// +// [core startQuery:query]; +// } +// } +// else +// { +// OCQuery *query; +// +// if ((query = core->_sharesJailQuery) != nil) +// { +// OCDataSource *queryResultsDataSource; +// +// if ((queryResultsDataSource = query.queryResultsDataSource) != nil) +// { +// [dataSource removeSources:@[ queryResultsDataSource ]]; +// } +// +// [core stopQuery:query]; +// +// core->_sharesJailQuery = nil; +// } +// } +// } withOwner:self performInitial:NO]; +// } +// else + { + // Provide applicable results from sharedWithMe data source + _sharedWithMeAcceptedDataSource = [self _compositionDataSourceForShareState:OCShareStateAccepted]; + + [_sharedWithMeAcceptedDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _sharedWithMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + } + + return (_sharedWithMeAcceptedDataSource); +} + +- (OCDataSource *)sharedWithMeDeclinedDataSource +{ + @synchronized(self) + { + if (_sharedWithMeDeclinedDataSource == nil) + { + _sharedWithMeDeclinedDataSource = [self _compositionDataSourceForShareState:OCShareStateDeclined]; + + [_sharedWithMeDeclinedDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _sharedWithMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + + return (_sharedWithMeDeclinedDataSource); +} + + +- (void)setShareJailQueryCustomizer:(OCCoreShareJailQueryCustomizer)shareJailQueryCustomizer +{ + _shareJailQueryCustomizer = [shareJailQueryCustomizer copy]; + + if (_shareJailQueryCustomizer != nil) + { + if (_sharesJailQuery != nil) + { + _shareJailQueryCustomizer(_sharesJailQuery); + } + } +} + +- (OCCoreShareJailQueryCustomizer)shareJailQueryCustomizer +{ + return (_shareJailQueryCustomizer); +} + + +#pragma mark - Shared by me +- (void)_updateAllSharedByMeQueryForceStop:(BOOL)forceStop +{ + BOOL hasSubscribers; + + @synchronized (self) + { + hasSubscribers = _allSharedByMeSubscribingDataSources > 0; + } + + if (hasSubscribers && !forceStop) + { + BOOL startQuery = NO; + + @synchronized(self) + { + if (_allSharedByMeQuery == nil) + { + __weak OCCore *weakSelf = self; + + _allSharedByMeQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; + _allSharedByMeQuery.refreshInterval = 10; + + _allSharedByMeQuery.changesAvailableNotificationHandler = ^(OCShareQuery * _Nonnull query) { + OCWLogDebug(@"SharedByMe: %@", query.queryResults); + + // Group shares + NSArray *allSharedByMeShares = query.queryResults; + NSMutableDictionary *sharesByLocation = [NSMutableDictionary new]; + NSMutableArray *primaryShares = [NSMutableArray new]; + NSMutableArray *flatShares = [NSMutableArray new]; + NSMutableArray *linkShares = [NSMutableArray new]; + + for (OCShare *share in allSharedByMeShares) + { + OCLocation *shareLocation; + + if (share.type == OCShareTypeLink) + { + // Separate link shares so they can't become hidden by / hide non-link shares + [linkShares addObject:share]; + } + else + { + [flatShares addObject:share]; + + // Group shares by location, add additional shares to .otherItemShares of non-link share of same location + if ((shareLocation = share.itemLocation) != nil) + { + OCShare *existingShare; + + if ((existingShare = sharesByLocation[shareLocation]) != nil) + { + NSMutableArray *otherItemShares; + + if ((otherItemShares = (NSMutableArray *)existingShare.otherItemShares) == nil) + { + otherItemShares = [NSMutableArray new]; + existingShare.otherItemShares = otherItemShares; + } + + [otherItemShares addObject:share]; + } + else + { + sharesByLocation[shareLocation] = share; + [primaryShares addObject:share]; + } + } + } + } + + // Add link shares to primary shares + [primaryShares addObjectsFromArray:linkShares]; + + // Sort by name + NSComparator sharesComparator = OCCore.sharesSortComparator; + + [primaryShares sortUsingComparator:sharesComparator]; + [flatShares sortUsingComparator:sharesComparator]; + + // Update data sources + [[weakSelf _allSharedByMeDataSource] setVersionedItems:primaryShares]; + + OCCore *strongSelf; + if ((strongSelf = weakSelf) != nil) + { + [strongSelf->_sharedByMeDataSource setVersionedItems:flatShares]; + } + }; + + startQuery = YES; + } + } + + if (startQuery) + { + [self startQuery:_allSharedByMeQuery]; + } + } + else + { + OCShareQuery *shareQuery = nil; + + @synchronized(self) + { + if (_allSharedByMeQuery != nil) + { + shareQuery = _allSharedByMeQuery; + _allSharedByMeQuery = nil; + } + } + + if (shareQuery != nil) + { + [self stopQuery:shareQuery]; + } + } +} + +- (OCDataSourceArray *)_allSharedByMeDataSource +{ + @synchronized(self) + { + if (_allSharedByMeDataSource == nil) + { + _allSharedByMeDataSource = [[OCDataSourceArray alloc] initWithItems:nil]; + _allSharedByMeDataSource.synchronizationGroup = dispatch_group_create(); // Ensure consistency of derived data sources + _allSharedByMeDataSource.trackItemVersions = YES; // Track item versions, so changes in status can be detected as actual changes + } + } + + return (_allSharedByMeDataSource); +} + +- (void)_allSharedByMeSubscriberChange:(NSInteger)subscriberChange +{ + @synchronized(self) + { + _allSharedByMeSubscribingDataSources += subscriberChange; + } + + [self beginActivity:@"Update shared by me query"]; + + [self queueBlock:^{ + [self _updateAllSharedByMeQueryForceStop:NO]; + [self endActivity:@"Update shared by me query"]; + }]; +} + +- (OCDataSourceComposition *)_compositionDataSourceForShareTypeLink:(BOOL)shareTypeLink +{ + OCDataSource *allSharedByMeDataSource = [self _allSharedByMeDataSource]; + dispatch_group_t synchronizationGroup = allSharedByMeDataSource.synchronizationGroup; + + return ([[OCDataSourceComposition alloc] initWithSources:@[ allSharedByMeDataSource ] applyCustomizations:^(OCDataSourceComposition *dataSource) { + dataSource.synchronizationGroup = synchronizationGroup; + + [dataSource setFilter:^BOOL(OCDataSource * _Nonnull source, OCDataItemReference _Nonnull itemRef) { + OCDataItemRecord *record; + + if ((record = [source recordForItemRef:itemRef error:NULL]) != nil) + { + OCShare *share; + + if ((share = OCTypedCast(record.item, OCShare)) != nil) + { + return ((share.type == OCShareTypeLink) == shareTypeLink); + } + } + + return (NO); + }]; + }]); +} + +#pragma mark - Shared by me (to other users) +- (OCDataSource *)sharedByMeDataSource +{ + @synchronized(self) + { + if (_sharedByMeDataSource == nil) + { + NSArray *flatSharedByMe = nil; + + if (_allSharedByMeQuery != nil) + { + flatSharedByMe = [_allSharedByMeQuery.queryResults filteredArrayUsingBlock:^BOOL(OCShare * _Nonnull share, BOOL * _Nonnull stop) { + return (share.type != OCShareTypeLink); + }]; + } + + _sharedByMeDataSource = [[OCDataSourceArray alloc] initWithItems:flatSharedByMe]; + _sharedByMeDataSource.synchronizationGroup = [self _allSharedByMeDataSource].synchronizationGroup; + + [_sharedByMeDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _allSharedByMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + + return (_sharedByMeDataSource); +} + +- (OCDataSource *)sharedByMeGroupedDataSource +{ + @synchronized(self) + { + if (_sharedByMeGroupedDataSource == nil) + { + _sharedByMeGroupedDataSource = [self _compositionDataSourceForShareTypeLink:NO]; + + [_sharedByMeGroupedDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _allSharedByMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + + return (_sharedByMeGroupedDataSource); +} + +#pragma mark - Shared by link +- (OCDataSource *)sharedByLinkDataSource +{ + @synchronized(self) + { + if (_sharedByLinkDataSource == nil) + { + _sharedByLinkDataSource = [self _compositionDataSourceForShareTypeLink:YES]; + + [_sharedByLinkDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + [(OCCore *)owner _allSharedByMeSubscriberChange:(hasSubscribers ? 1 : -1)]; + } withOwner:self performInitial:NO]; + } + } + + return (_sharedByLinkDataSource); +} + +#pragma mark - Favorites +- (OCDataSource *)favoritesDataSource +{ + @synchronized(self) + { + if (_favoritesDataSource == nil) + { + _favoritesDataSource = [[OCDataSourceComposition alloc] initWithSources:@[] applyCustomizations:nil]; + + [_favoritesDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + OCCore *core = (OCCore *)owner; + + @synchronized(core) + { + core->_favoritesDataSourceHasSubscribers = hasSubscribers; + } + + [core beginActivity:@"Update favorites data source"]; + + [core queueBlock:^{ + [core _setFavoritesDataSourceSubscriptionHasUpdate]; + [core endActivity:@"Update favorites data source"]; + }]; + } withOwner:self performInitial:NO]; + } + } + + return (_favoritesDataSource); +} + +- (void)_setFavoritesDataSourceSubscriptionHasUpdate // Performed on core queue, which acts as lock +{ + BOOL favoritesDataSourceHasSubscribers; + + @synchronized(self) + { + favoritesDataSourceHasSubscribers = _favoritesDataSourceHasSubscribers; + } + + if (favoritesDataSourceHasSubscribers == (_favoritesQuery != nil)) + { + // Nothing to do + return; + } + + if (favoritesDataSourceHasSubscribers) + { + // Create favorites query, add results as data source and start query + if (_favoritesQuery == nil) + { + OCQuery *query = [OCQuery queryWithCondition:[OCQueryCondition where:OCItemPropertyNameIsFavorite isEqualTo:@(YES)] inputFilter:nil]; + OCDataSource *queryDatasource = query.queryResultsDataSource; + + if ((query != nil) && (queryDatasource != nil)) + { + _favoritesQuery = query; + [_favoritesDataSource addSources:@[ queryDatasource ]]; + + [self startQuery:_favoritesQuery]; + + if (!self.useDrives) + { + // Only OC10 requires polling for favorites - ocis propagates the change via regular PROPFIND update scanning + [self subscribeToPollingDatasourcesTimer:OCCoreDataSourcePollTypeFavorites]; + } + } + } + } + else + { + // Remove favorites query results data source and stop query + OCQuery *query = _favoritesQuery; + OCDataSource *queryDatasource = query.queryResultsDataSource; + + if (queryDatasource != nil) + { + [_favoritesDataSource removeSources:@[ queryDatasource ]]; + + if (!self.useDrives) + { + // Only OC10 requires polling for favorites - ocis propagates the change via regular PROPFIND update scanning + [self unsubscribeFromPollingDatasourcesTimer:OCCoreDataSourcePollTypeFavorites withForcedStop:NO]; + } + } + + if (query != nil) + { + [self stopQuery:query]; + _favoritesQuery = nil; + } + } +} + +#pragma mark - Available Offline: Item Policies +- (OCDataSource *)availableOfflineItemPoliciesDataSource +{ + @synchronized(self) + { + if (_availableOfflineItemPoliciesDataSource == nil) + { + _availableOfflineItemPoliciesDataSource = [[OCDataSourceArray alloc] initWithItems:@[]]; + + [_availableOfflineItemPoliciesDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + OCCore *core = (OCCore *)owner; + + @synchronized(core) + { + core->_availableOfflineItemPoliciesDataSourceHasSubscribers = hasSubscribers; + } + + [core beginActivity:@"Update offline item policies data source"]; + + [core queueBlock:^{ + [core _setOfflineItemPoliciesDataSourceSubscriptionHasUpdate]; + [core endActivity:@"Update offline item policies data source"]; + }]; + } withOwner:self performInitial:NO]; + } + } + + return (_availableOfflineItemPoliciesDataSource); +} + +- (void)_setOfflineItemPoliciesDataSourceSubscriptionHasUpdate +{ + OCItemPolicyProcessor *availableOfflinePolicyProcessor; + BOOL availableOfflineItemPoliciesDataSourceHasSubscribers; + + @synchronized(self) + { + availableOfflineItemPoliciesDataSourceHasSubscribers = _availableOfflineItemPoliciesDataSourceHasSubscribers; + } + + if (availableOfflineItemPoliciesDataSourceHasSubscribers == _observesOfflineItemPolicies) + { + // Nothing to do + return; + } + + if ((availableOfflinePolicyProcessor = [self itemPolicyProcessorForKind:OCItemPolicyKindAvailableOffline]) != nil) + { + _observesOfflineItemPolicies = availableOfflineItemPoliciesDataSourceHasSubscribers; + + if (availableOfflineItemPoliciesDataSourceHasSubscribers) + { + [self _reloadOfflineItemDataPoliciesIntoDataSource]; + + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_reloadOfflineItemDataPoliciesIntoDataSource) name:OCCoreItemPolicyProcessorUpdated object:availableOfflinePolicyProcessor]; + } + else + { + [NSNotificationCenter.defaultCenter removeObserver:self name:OCCoreItemPolicyProcessorUpdated object:availableOfflinePolicyProcessor]; + } + } +} + +- (void)_reloadOfflineItemDataPoliciesIntoDataSource +{ + [self retrievePoliciesOfKind:OCItemPolicyKindAvailableOffline affectingItem:nil includeInternal:NO completionHandler:^(NSError * _Nullable error, NSArray * _Nullable policies) { + NSArray *sortedPolicies = [policies sortedArrayUsingComparator:^NSComparisonResult(OCItemPolicy *policy1, OCItemPolicy *policy2) { + OCPath policy1Path = policy1.location.path, policy2Path = policy2.location.path; + + if ((policy1Path != nil) && (policy2Path != nil)) + { + return ([policy1.location.path localizedStandardCompare:policy2.location.path]); + } + + return (NSOrderedDescending); + }]; + + [self->_availableOfflineItemPoliciesDataSource setVersionedItems:sortedPolicies]; + }]; +} + +#pragma mark - Available Offline: Files +- (OCDataSource *)availableOfflineFilesDataSource +{ + @synchronized(self) + { + if (_availableOfflineFilesDataSource == nil) + { + _availableOfflineFilesDataSource = [[OCDataSourceComposition alloc] initWithSources:@[] applyCustomizations:nil]; + + [_availableOfflineFilesDataSource addSubscriptionObserver:^(OCDataSource * _Nonnull source, id _Nonnull owner, BOOL hasSubscribers) { + OCCore *core = (OCCore *)owner; + + @synchronized(core) + { + core->_availableOfflineFilesDataSourceHasSubscribers = hasSubscribers; + } + + [core beginActivity:@"Update favorites data source"]; + + [core queueBlock:^{ + [core _setAvailableOfflineFilesDataSourceSubscriptionHasUpdate]; + [core endActivity:@"Update favorites data source"]; + }]; + } withOwner:self performInitial:NO]; + } + } + + return (_availableOfflineFilesDataSource); +} + +- (void)_setAvailableOfflineFilesDataSourceSubscriptionHasUpdate // Performed on core queue, which acts as lock +{ + BOOL availableOfflineFilesDataSourceHasSubscribers; + + @synchronized(self) + { + availableOfflineFilesDataSourceHasSubscribers = _availableOfflineFilesDataSourceHasSubscribers; + } + + if (availableOfflineFilesDataSourceHasSubscribers == (_availableOfflineFilesQuery != nil)) + { + // Nothing to do + return; + } + + if (availableOfflineFilesDataSourceHasSubscribers) + { + // Create favorites query, add results as data source and start query + if (_availableOfflineFilesQuery == nil) + { + OCQuery *query = [OCQuery queryWithCondition:[OCQueryCondition where:OCItemPropertyNameDownloadTrigger isEqualTo:OCItemDownloadTriggerIDAvailableOffline] inputFilter:nil]; + OCDataSource *queryDatasource = query.queryResultsDataSource; + + if ((query != nil) && (queryDatasource != nil)) + { + _availableOfflineFilesQuery = query; + [_availableOfflineFilesDataSource addSources:@[ queryDatasource ]]; + + [self startQuery:_availableOfflineFilesQuery]; + } + } + } + else + { + // Remove favorites query results data source and stop query + OCQuery *query = _availableOfflineFilesQuery; + OCDataSource *queryDatasource = query.queryResultsDataSource; + + if (queryDatasource != nil) + { + [_availableOfflineFilesDataSource removeSources:@[ queryDatasource ]]; + } + + if (query != nil) + { + [self stopQuery:query]; + _availableOfflineFilesQuery = nil; + } + } +} + + +#pragma mark - Polling timer +- (void)subscribeToPollingDatasourcesTimer:(OCCoreDataSourcePollType)pollType +{ + BOOL start = NO; + + @synchronized (self) + { + _pollingDataSourcesSubscribers += 1; + + if (_pollingDataSourcesSubscribers == 1) + { + start = YES; + } + } + + if (start) + { + _pollingDataSourcesTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue); + + __weak __auto_type weakSelf = self; + dispatch_source_set_event_handler(_pollingDataSourcesTimer, ^{ + [weakSelf _pollSubscribedDataSource:OCCoreDataSourcePollTypeAll]; + }); + + int64_t pollIntervalSec = 30; + dispatch_source_set_timer(_pollingDataSourcesTimer, dispatch_time(DISPATCH_TIME_NOW, pollIntervalSec * NSEC_PER_SEC), pollIntervalSec * NSEC_PER_SEC, NSEC_PER_SEC); + dispatch_resume(_pollingDataSourcesTimer); + + [self _pollSubscribedDataSource:pollType]; + } +} + +- (void)unsubscribeFromPollingDatasourcesTimer:(OCCoreDataSourcePollType)pollType withForcedStop:(BOOL)force +{ + BOOL stop = force; + + @synchronized (self) + { + if (_pollingDataSourcesSubscribers > 0) + { + _pollingDataSourcesSubscribers -= 1; + } + + if (_pollingDataSourcesSubscribers == 0) + { + stop = YES; + } + } + + if (stop) + { + if (_pollingDataSourcesTimer != NULL) + { + dispatch_source_cancel(_pollingDataSourcesTimer); + _pollingDataSourcesTimer = NULL; + } + } +} + +- (void)_performPollForDataSource:(void(^)(dispatch_block_t completionHandler))pollRoutine +{ + __block BOOL didFinish = NO; + __weak OCCore *weakSelf = self; + + @synchronized(self) + { + _pollingDataSourcesOutstandingRequests += 1; + } + + pollRoutine([^{ + if (!didFinish) + { + didFinish = YES; + + OCCore *core; + + if ((core = weakSelf) != nil) + { + @synchronized(core) + { + core->_pollingDataSourcesOutstandingRequests -= 1; + } + } + } + } copy]); +} + +#pragma mark - Perform polling +- (void)_pollSubscribedDataSource:(OCCoreDataSourcePollType)pollType +{ + __weak OCCore *weakSelf = self; + + if ((self.state == OCCoreStateStopping) || (self.state == OCCoreStateStopped)) + { + // Skip when the core is stopping or stopped + return; + } + + if (self.state != OCCoreStateRunning) + { + // Only perform when the core is running + return; + } + + BOOL pollFavorites = NO; + + @synchronized(self) + { + if ((_pollingDataSourcesOutstandingRequests > 0) && (pollType == OCCoreDataSourcePollTypeAll)) + { + // Do not perform next polling before the previous polling isn't done + return; + } + + // Determine what to poll + pollFavorites = _favoritesDataSourceHasSubscribers && ((pollType == OCCoreDataSourcePollTypeAll) || (pollType == OCCoreDataSourcePollTypeFavorites)); + } + + if (pollFavorites) + { + [self _performPollForDataSource:^(dispatch_block_t completionHandler) { + [weakSelf refreshFavoritesWithCompletionHandler:^(NSError * _Nullable error, NSArray * _Nullable favoritedItems) { + completionHandler(); + }]; + }]; + } +} + +@end diff --git a/ownCloudSDK/Core/DirectURL/OCCore+DirectURL.m b/ownCloudSDK/Core/DirectURL/OCCore+DirectURL.m index 867c1268..919738b7 100644 --- a/ownCloudSDK/Core/DirectURL/OCCore+DirectURL.m +++ b/ownCloudSDK/Core/DirectURL/OCCore+DirectURL.m @@ -18,6 +18,7 @@ #import "OCCore+DirectURL.h" #import "NSError+OCError.h" +#import "OCMacros.h" @implementation OCCore (DirectURL) @@ -43,7 +44,7 @@ - (void)provideDirectURLForItem:(OCItem *)item allowFileURL:(BOOL)allowFileURL c // Provide URL to file on server if (url == nil) { - if ((item.path != nil) && ((url = [[self.connection URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:nil] URLByAppendingPathComponent:item.path]) != nil)) + if ((item.path != nil) && ((url = [[self.connection URLForEndpoint:OCConnectionEndpointIDWebDAVRoot options:@{ OCConnectionEndpointURLOptionDriveID : OCNullProtect(item.driveID) }] URLByAppendingPathComponent:item.path]) != nil)) { authHeaders = [self.connection.authenticationMethod authorizationHeadersForConnection:self.connection error:&error]; } diff --git a/ownCloudSDK/Core/Favorites/OCCore+Favorites.m b/ownCloudSDK/Core/Favorites/OCCore+Favorites.m index 91f031ce..b6a2398e 100644 --- a/ownCloudSDK/Core/Favorites/OCCore+Favorites.m +++ b/ownCloudSDK/Core/Favorites/OCCore+Favorites.m @@ -136,7 +136,7 @@ - (void)_handleFavoritesSearchEvent:(OCEvent *)event sender:(id)sender // Update items if (updateItems.count > 0) { - [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } return (nil); diff --git a/ownCloudSDK/Core/ItemList/OCCore+ItemList.h b/ownCloudSDK/Core/ItemList/OCCore+ItemList.h index fabd9c5e..73ed6d65 100644 --- a/ownCloudSDK/Core/ItemList/OCCore+ItemList.h +++ b/ownCloudSDK/Core/ItemList/OCCore+ItemList.h @@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OCCore (ItemList) #pragma mark - Item List Tasks -- (void)scheduleItemListTaskForPath:(OCPath)path forDirectoryUpdateJob:(nullable OCCoreDirectoryUpdateJob *)directoryUpdateJob withMeasurement:(nullable OCMeasurement *)measurement; +- (void)scheduleItemListTaskForLocation:(OCLocation *)location forDirectoryUpdateJob:(nullable OCCoreDirectoryUpdateJob *)directoryUpdateJob withMeasurement:(nullable OCMeasurement *)measurement; - (void)handleUpdatedTask:(OCCoreItemListTask *)task; - (void)queueRequestJob:(OCAsyncSequentialQueueJob)requestJob; @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)_handleRetrieveItemListEvent:(OCEvent *)event sender:(id)sender; #pragma mark - Update Scans -- (void)scheduleUpdateScanForPath:(OCPath)path waitForNextQueueCycle:(BOOL)waitForNextQueueCycle; +- (void)scheduleUpdateScanForLocation:(OCLocation *)location waitForNextQueueCycle:(BOOL)waitForNextQueueCycle; - (void)recoverPendingUpdateJobs; - (void)coordinatedScanForChanges; diff --git a/ownCloudSDK/Core/ItemList/OCCore+ItemList.m b/ownCloudSDK/Core/ItemList/OCCore+ItemList.m index 0f44efdb..c6ef1a24 100644 --- a/ownCloudSDK/Core/ItemList/OCCore+ItemList.m +++ b/ownCloudSDK/Core/ItemList/OCCore+ItemList.m @@ -37,6 +37,9 @@ #import "OCCoreUpdateScheduleRecord.h" #import "OCLockManager.h" #import "OCLockRequest.h" +#import "OCConnection+GraphAPI.h" +#import "GADrive.h" +#import "GADriveItem.h" #import static OCHTTPRequestGroupID OCCoreItemListTaskGroupQueryTasks = @"queryItemListTasks"; @@ -60,15 +63,15 @@ - (NSUInteger)parallelItemListTaskCount } #pragma mark - Item List Tasks -- (void)scheduleItemListTaskForPath:(OCPath)path forDirectoryUpdateJob:(nullable OCCoreDirectoryUpdateJob *)directoryUpdateJob withMeasurement:(nullable OCMeasurement *)measurement +- (void)scheduleItemListTaskForLocation:(OCLocation *)location forDirectoryUpdateJob:(nullable OCCoreDirectoryUpdateJob *)directoryUpdateJob withMeasurement:(nullable OCMeasurement *)measurement { BOOL putInQueue = YES; - if (path != nil) + if (location != nil) { if (directoryUpdateJob == nil) { - directoryUpdateJob = [OCCoreDirectoryUpdateJob withPath:path]; + directoryUpdateJob = [OCCoreDirectoryUpdateJob withLocation:location]; [directoryUpdateJob attachMeasurement:measurement]; } @@ -81,9 +84,9 @@ - (void)scheduleItemListTaskForPath:(OCPath)path forDirectoryUpdateJob:(nullable { putInQueue = NO; - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - if ((existingQueryTask = _itemListTasksByPath[path]) != nil) + if ((existingQueryTask = _itemListTasksByLocationString[location.string]) != nil) { putInQueue = YES; } @@ -183,7 +186,7 @@ - (void)scheduleNextItemListTask } else { - if ([updateJob.path isEqual:nextUpdateJob.path]) + if ([updateJob.location isEqual:nextUpdateJob.location]) { // Add to represented array, so the database can be cleaned up properly [nextUpdateJob addRepresentedJobID:updateJob.identifier]; @@ -213,7 +216,7 @@ - (OCCoreItemListTask *)_scheduleItemListTaskForDirectoryUpdateJob:(OCCoreDirect { OCCoreItemListTask *task = nil; OCHTTPRequestGroupID groupID = nil; - OCPath path = updateJob.path; + OCLocationString locationString = updateJob.location.string; if (updateJob.identifier != nil) { @@ -224,11 +227,11 @@ - (OCCoreItemListTask *)_scheduleItemListTaskForDirectoryUpdateJob:(OCCoreDirect groupID = OCCoreItemListTaskGroupQueryTasks; } - if (path!=nil) + if (updateJob.location.path != nil) { - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - task = _itemListTasksByPath[path]; + task = _itemListTasksByLocationString[locationString]; } if (task != nil) @@ -247,15 +250,15 @@ - (OCCoreItemListTask *)_scheduleItemListTaskForDirectoryUpdateJob:(OCCoreDirect return (nil); } - if ((task = [[OCCoreItemListTask alloc] initWithCore:self path:path updateJob:updateJob]) != nil) + if ((task = [[OCCoreItemListTask alloc] initWithCore:self location:updateJob.location updateJob:updateJob]) != nil) { task.groupID = groupID; [task attachMeasurement:updateJob.extractedMeasurement]; - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - _itemListTasksByPath[task.path] = task; + _itemListTasksByLocationString[task.location.string] = task; } if (updateJob.isForQuery) @@ -264,7 +267,7 @@ - (OCCoreItemListTask *)_scheduleItemListTaskForDirectoryUpdateJob:(OCCoreDirect } else { - [self _updateBackgroundScanActivityWithIncrement:NO currentPathChange:path]; + [self _updateBackgroundScanActivityWithIncrement:NO currentLocationChange:updateJob.location]; } // Start item list task @@ -310,7 +313,7 @@ - (void)_finishedItemListTask:(OCCoreItemListTask *)finishedTask if ((!finishedTask.updateJob.isForQuery) && (finishedTask.retrievedSet.error != nil)) { // Task is not for query (=> background scan) and terminated due to an error - OCLog(@"Removing update job for %@ with cacheError=%@, retrieveError=%@", finishedTask.path, finishedTask.cachedSet.error, finishedTask.retrievedSet.error); + OCLog(@"Removing update job for %@ with cacheError=%@, retrieveError=%@", finishedTask.location, finishedTask.cachedSet.error, finishedTask.retrievedSet.error); if (finishedTask.retrievedSet.error.isNetworkFailureError) { @@ -349,7 +352,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task BOOL targetRemoved = NO; NSMutableArray *queryResults = nil; __block NSMutableArray *queryResultsChangedItems = nil; - NSString *taskPath = task.path; + OCLocationString taskLocationString = task.location.string; __block OCSyncAnchor querySyncAnchor = nil; OCCoreItemListTask *nextTask = nil; @@ -402,7 +405,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task case OCCoreItemListStateFailed: // Error retrieving items from cache. This should never happen. - OCLogError(@"Error retrieving items from cache for %@: %@", OCLogPrivate(task.path), OCLogPrivate(task.cachedSet.error)); + OCLogError(@"Error retrieving items from cache for %@: %@", OCLogPrivate(taskLocationString), OCLogPrivate(task.cachedSet.error)); performMerge = YES; removeTask = YES; break; @@ -411,13 +414,13 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task break; } - if (performMerge && (task.path!=nil)) + if (performMerge && (task.location.path!=nil)) { OCCoreItemListTask *existingTask; - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - existingTask = _itemListTasksByPath[task.path]; + existingTask = _itemListTasksByLocationString[taskLocationString]; } if (existingTask != nil) @@ -449,9 +452,9 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task } else { - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - _itemListTasksByPath[task.path] = task; + _itemListTasksByLocationString[taskLocationString] = task; } } } @@ -482,7 +485,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task if (![previousSyncAnchor isEqualToNumber:task.syncAnchorAtStart]) { // Out of sync - trigger catching the latest from the cache again, rinse and repeat - OCLogDebug(@"IL[%@, path=%@]: Sync anchor changed before task finished: previousSyncAnchor=%@ != task.syncAnchorAtStart=%@", task, task.path, previousSyncAnchor, task.syncAnchorAtStart); + OCLogDebug(@"IL[%@, location=%@]: Sync anchor changed before task finished: previousSyncAnchor=%@ != task.syncAnchorAtStart=%@", task, task.location, previousSyncAnchor, task.syncAnchorAtStart); task.syncAnchorAtStart = newSyncAnchor; // Update sync anchor before triggering the reload from cache @@ -547,6 +550,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task [queryResults addObject:cacheItem]; // Update cache + [cacheItem updateSeedFrom:retrievedItem.versionSeed]; [changedCacheItems addObject:cacheItem]; } else @@ -558,6 +562,13 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task // Attach databaseID of cached items to the retrieved items [retrievedItem prepareToReplace:cacheItem]; + // Preserve path change + if (![cacheItem.path isEqual:retrievedItem.path]) + { + // Save cacheItem.path as previousPath if it differs + retrievedItem.previousPath = cacheItem.path; + } + retrievedItem.localRelativePath = cacheItem.localRelativePath; retrievedItem.localCopyVersionIdentifier = cacheItem.localCopyVersionIdentifier; retrievedItem.downloadTriggerIdentifier = cacheItem.downloadTriggerIdentifier; @@ -567,9 +578,11 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task (retrievedItem.shareTypesMask != cacheItem.shareTypesMask) || // Share types mismatch (retrievedItem.permissions != cacheItem.permissions) || // Permissions mismatch - (retrievedItem.isFavorite != cacheItem.isFavorite)) // Favorite mismatch + (retrievedItem.isFavorite != cacheItem.isFavorite) || // Favorite mismatch + (retrievedItem.state != cacheItem.state)) // State mismatch { // Update item in the cache if the server has a different version + [retrievedItem updateSeedFrom:cacheItem.versionSeed]; [changedCacheItems addObject:retrievedItem]; } } @@ -589,6 +602,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) @@ -631,6 +645,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task else { // Remove item + [cacheItem updateSeed]; [deletedCacheItems addObject:cacheItem]; } } @@ -644,7 +659,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task { if (deletedItem.type == OCItemTypeCollection) { - [self.database retrieveCacheItemsRecursivelyBelowPath:deletedItem.path includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.database retrieveCacheItemsRecursivelyBelowLocation:deletedItem.location includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { if (items.count > 0) { if (recursivelyDeletedItems == nil) @@ -742,6 +757,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task } // Add to updatedItems + [newItem updateSeedFrom:knownItem.versionSeed]; [changedCacheItems addObject:newItem]; } @@ -767,7 +783,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task if (queryState == OCQueryStateIdle) { // Fully merged => use for updating existing queries that have already gone through their complete, initial update - NSMutableArray *refreshPaths = [NSMutableArray new]; + NSMutableArray *refreshLocations = [NSMutableArray new]; NSMutableArray *movedItems = [NSMutableArray new]; BOOL fetchUpdatesRunning = NO; @@ -777,25 +793,27 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task } BOOL allowRefreshPathAddition = (self.automaticItemListUpdatesEnabled || fetchUpdatesRunning); + OCDriveID taskLocationDriveID = task.location.driveID; + OCPath taskLocationPath = task.location.path; // Determine refreshPaths if automatic item list updates are enabled for (OCItem *item in newItems) { - if ((item.type == OCItemTypeCollection) && (item.path != nil) && (item.fileID!=nil) && (item.eTag!=nil) && ![item.path isEqual:task.path]) + if ((item.type == OCItemTypeCollection) && (item.path != nil) && (item.fileID!=nil) && (item.eTag!=nil) && OCDriveIDIsIdentical(item.driveID, taskLocationDriveID) && ![item.path isEqual:taskLocationPath]) { // Moved items are removed from newItems, updated and moved to changedCacheItems above, so that // such items should not end up ending up their item.path to refreshPaths here. Only truly new- // discovered collections will. if (allowRefreshPathAddition) { - [refreshPaths addObject:item.path]; + [refreshLocations addObject:item.location]; } } } for (OCItem *item in changedCacheItems) { - if ((item.type == OCItemTypeCollection) && (item.path != nil) && (item.fileID!=nil) && (item.eTag!=nil) && ![item.path isEqual:task.path]) + if ((item.type == OCItemTypeCollection) && (item.path != nil) && (item.fileID!=nil) && (item.eTag!=nil) && OCDriveIDIsIdentical(item.driveID, taskLocationDriveID) && ![item.path isEqual:taskLocationPath]) { __block OCItem *cacheItem = cacheItemsByFileID[item.fileID]; @@ -811,7 +829,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task { if (allowRefreshPathAddition) { - [refreshPaths addObject:item.path]; + [refreshLocations addObject:item.location]; } } @@ -830,14 +848,26 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task if (item.activeSyncRecordIDs.count == 0) { // Item has no sync activity + OCPath recomposedPath; + + if (item.type == OCItemTypeCollection) + { + recomposedPath = [[item.path stringByAppendingPathComponent:[containedItem.path substringFromIndex:cacheItem.path.length]] normalizedDirectoryPath]; + } + else + { + recomposedPath = [item.path stringByAppendingPathComponent:[containedItem.path substringFromIndex:cacheItem.path.length]]; + } + containedItem.previousPath = containedItem.path; - containedItem.path = [item.path stringByAppendingPathComponent:[containedItem.path substringFromIndex:cacheItem.path.length]]; + containedItem.path = recomposedPath; if ([containedItem countOfSyncRecordsWithSyncActivity:OCItemSyncActivityDeleting] == 0) { containedItem.removed = NO; } + [containedItem updateSeedFrom:item.versionSeed]; [movedItems addObject:containedItem]; } else @@ -851,9 +881,9 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task } } - if (refreshPaths.count == 0) + if (refreshLocations.count == 0) { - refreshPaths = nil; + refreshLocations = nil; } if (movedItems.count > 0) @@ -868,7 +898,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task [self performUpdatesForAddedItems:newItems removedItems:deletedCacheItems updatedItems:changedCacheItems - refreshPaths:refreshPaths + refreshLocations:refreshLocations newSyncAnchor:newSyncAnchor beforeQueryUpdates:^(dispatch_block_t _Nonnull completionHandler) { // Called AFTER the database has been updated, but before UPDATING queries @@ -883,7 +913,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task OCMeasureEventEnd(task, @"itemlist.query-update", coreQueueRef, @"Done with query updates"); OCMeasureEventBegin(task, @"itemlist.query-updates.finalize", finalizeQueryUpdateRef, @"Finalize query updates"); - [self _finalizeQueryUpdatesWithQueryResults:queryResults queryResultsChangedItems:queryResultsChangedItems queryState:queryState querySyncAnchor:querySyncAnchor task:task taskPath:taskPath targetRemoved:targetRemoved]; + [self _finalizeQueryUpdatesWithQueryResults:queryResults queryResultsChangedItems:queryResultsChangedItems queryState:queryState querySyncAnchor:querySyncAnchor task:task targetRemoved:targetRemoved]; OCMeasureEventEnd(task, @"itemlist.query-updates.finalize", finalizeQueryUpdateRef, @"Finalized query updates"); OCMeasureLog(task); completionHandler(); @@ -947,21 +977,21 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task // Remove task if (removeTask) { - if (task.path != nil) + if (task.location.path != nil) { - @synchronized(_itemListTasksByPath) + @synchronized(_itemListTasksByLocationString) { - if (_itemListTasksByPath[task.path] == task) + if (_itemListTasksByLocationString[taskLocationString] == task) { if (task.nextItemListTask != nil) { - _itemListTasksByPath[task.path] = task.nextItemListTask; + _itemListTasksByLocationString[taskLocationString] = task.nextItemListTask; nextTask = task.nextItemListTask; task.nextItemListTask = nil; } else { - [_itemListTasksByPath removeObjectForKey:task.path]; + [_itemListTasksByLocationString removeObjectForKey:taskLocationString]; } } } @@ -974,7 +1004,7 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task [self queueBlock:^{ // Update non-idle queries - [self _finalizeQueryUpdatesWithQueryResults:queryResults queryResultsChangedItems:queryResultsChangedItems queryState:queryState querySyncAnchor:querySyncAnchor task:task taskPath:taskPath targetRemoved:targetRemoved]; + [self _finalizeQueryUpdatesWithQueryResults:queryResults queryResultsChangedItems:queryResultsChangedItems queryState:queryState querySyncAnchor:querySyncAnchor task:task targetRemoved:targetRemoved]; if (removeTask) { @@ -1002,19 +1032,22 @@ - (void)handleUpdatedTask:(OCCoreItemListTask *)task OCMeasureEventEnd(task, @"core.task-update", taskUpdateEventRef, nil); } -- (void)_finalizeQueryUpdatesWithQueryResults:(NSMutableArray *)queryResults queryResultsChangedItems:(NSMutableArray *)queryResultsChangedItems queryState:(OCQueryState)queryState querySyncAnchor:(OCSyncAnchor)querySyncAnchor task:(OCCoreItemListTask * _Nonnull)task taskPath:(NSString *)taskPath targetRemoved:(BOOL)targetRemoved +- (void)_finalizeQueryUpdatesWithQueryResults:(NSMutableArray *)queryResults queryResultsChangedItems:(NSMutableArray *)queryResultsChangedItems queryState:(OCQueryState)queryState querySyncAnchor:(OCSyncAnchor)querySyncAnchor task:(OCCoreItemListTask * _Nonnull)task targetRemoved:(BOOL)targetRemoved { NSMutableDictionary *queryResultItemsByPath = nil; NSMutableArray *queryResultWithoutRootItem = nil; OCItem *taskRootItem = nil; + OCLocation *taskLocation = task.location; + OCPath taskPath = taskLocation.path; // Determine root item if ((taskPath != nil) && !targetRemoved) { OCItem *cacheRootItem = nil, *retrievedRootItem = nil; + OCDriveID taskLocationDriveID = OCDriveIDWrap(taskLocation.driveID); - retrievedRootItem = task.retrievedSet.itemsByPath[taskPath]; - cacheRootItem = task.cachedSet.itemsByPath[taskPath]; + retrievedRootItem = task.retrievedSet.itemListsByDriveID[taskLocationDriveID].itemsByPath[taskPath]; + cacheRootItem = task.cachedSet.itemListsByDriveID[taskLocationDriveID].itemsByPath[taskPath]; if ((taskRootItem==nil) && (cacheRootItem!=nil) && ([queryResults indexOfObjectIdenticalTo:cacheRootItem]!=NSNotFound)) { @@ -1039,15 +1072,17 @@ - (void)_finalizeQueryUpdatesWithQueryResults:(NSMutableArray *)queryR { NSMutableArray *useQueryResults = nil; OCItem *queryRootItem = nil; - OCPath queryPath = query.queryPath; + OCLocation *queryLocation = query.queryLocation; + OCLocation *queryItemLocation = query.queryItem.location; + OCPath queryPath = query.queryLocation.path; OCPath queryItemPath = query.queryItem.path; - BOOL taskPathIsAncestorOfQueryPath = (taskPath!=nil) && [queryPath hasPrefix:taskPath] && taskPath.isNormalizedDirectoryPath && ![queryPath isEqual:taskPath]; - OCQueryState setQueryState = (([queryPath isEqual:taskPath] || [queryItemPath isEqual:taskPath] || taskPathIsAncestorOfQueryPath) && !query.isCustom) ? + BOOL taskPathIsAncestorOfQueryPath = (taskPath!=nil) && [queryLocation isLocatedIn:taskLocation] && taskPath.isNormalizedDirectoryPath && ![queryLocation isEqual:taskLocation]; + OCQueryState setQueryState = (([queryLocation isEqual:taskLocation] || [queryItemLocation isEqual:taskLocation] || taskPathIsAncestorOfQueryPath) && !query.isCustom) ? queryState : query.state; // Queries targeting the path - if ([queryPath isEqual:taskPath]) + if ([queryLocation isEqual:taskLocation]) { if (query.state != OCQueryStateIdle) // Keep updating queries that have not gone through its complete, initial content update { @@ -1119,7 +1154,7 @@ - (void)_finalizeQueryUpdatesWithQueryResults:(NSMutableArray *)queryR } // Queries targeting a particular item - if (queryItemPath != nil) + if ((queryItemPath != nil) && OCDriveIDIsIdentical(queryItemLocation.driveID, taskLocation.driveID)) { if (query.state != OCQueryStateIdle) // Keep updating queries that have not gone through its complete, initial content update { @@ -1153,7 +1188,7 @@ - (void)_finalizeQueryUpdatesWithQueryResults:(NSMutableArray *)queryR } else { - if ([[queryItemPath parentPath] isEqual:task.path]) + if ([[queryItemPath parentPath] isEqual:taskLocation.path]) { // Item was contained in queried directory, but is no longer there useQueryResults = [NSMutableArray new]; @@ -1283,10 +1318,6 @@ - (void)_checkForUpdatesNonCritical:(BOOL)nonCritical inBackground:(BOOL)inBackg { if (strongSelf.state == OCCoreStateRunning) { - OCEventTarget *eventTarget; - - eventTarget = [OCEventTarget eventTargetWithEventHandlerIdentifier:strongSelf.eventHandlerIdentifier userInfo:nil ephermalUserInfo:nil]; - NSDictionary *options = nil; if (nonCritical) @@ -1296,7 +1327,76 @@ - (void)_checkForUpdatesNonCritical:(BOOL)nonCritical inBackground:(BOOL)inBackg }; } - [strongSelf.connection retrieveItemListAtPath:@"/" depth:0 options:options resultTarget:eventTarget]; + if (self.useDrives) + { + [strongSelf.connection retrieveDriveListWithCompletionHandler:^(NSError * _Nullable error, NSArray * _Nullable drives) { + [weakSelf queueBlock:^{ + OCCore *strongSelf; + + + if ((strongSelf = weakSelf) != nil) + { + BOOL foundChanges = NO; + + if (error == nil) + { + OCWTLogDebug((@[@"ScanChanges", @"Drives"]), @"New drive list: %@", drives); + + [strongSelf.vault updateWithRemoteDrives:drives]; + + // Discover whose drives' root eTag has changed + @synchronized(strongSelf->_lastRootETagsByDriveID) + { + for (OCDrive *subscribedDrive in strongSelf.vault.subscribedDrives) + { + OCDriveID subscribedDriveID = subscribedDrive.identifier; + OCFileETag subscribedDriveETag = subscribedDrive.rootETag; + + OCFileETag lastETag = strongSelf->_lastRootETagsByDriveID[subscribedDriveID]; + + if (((lastETag == nil) || ![lastETag isEqual:subscribedDriveETag]) && // Request an update if no last ETag is known (indicating no prior scan) or if the ETag differs from last scan + (subscribedDriveETag != nil)) // Do NOT request an update if no ETag is available (consider this a temporary malfunction on the server-side which needs to be resolved there) + { + OCWTLogDebug((@[@"ScanChanges", @"Drives"]), @"Root eTag changed %@ -> %@ for %@", lastETag, subscribedDriveETag, subscribedDrive); + + foundChanges = YES; + [strongSelf scheduleUpdateScanForLocation:[[OCLocation alloc] initWithDriveID:subscribedDriveID path:@"/"] waitForNextQueueCycle:NO]; + + strongSelf->_lastRootETagsByDriveID[subscribedDriveID] = subscribedDriveETag; + } + } + } + } + else + { + OCWTLogWarning((@[@"ScanChanges", @"Drives"]), @"Error retrieving drive list: %@", error); + } + + if (!foundChanges) + { + // No changes. We're done. + @synchronized(strongSelf->_scheduledDirectoryUpdateJobIDs) + { + if (strongSelf->_scheduledDirectoryUpdateJobActivity == nil) + { + [strongSelf _finishedUpdateScanWithError:nil foundChanges:NO]; + } + } + } + + // Schedule next + [strongSelf coordinatedScanForChangesDidFinish]; + } + }]; + }]; + } + else + { + OCEventTarget *eventTarget; + + eventTarget = [OCEventTarget eventTargetWithEventHandlerIdentifier:strongSelf.eventHandlerIdentifier userInfo:nil ephermalUserInfo:nil]; + [strongSelf.connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:options resultTarget:eventTarget]; + } } } } inBackground:inBackground]; @@ -1351,7 +1451,9 @@ - (void)_handleRetrieveItemListEvent:(OCEvent *)event sender:(id)sender } } - if ((cacheItems = [self.database retrieveCacheItemsSyncAtPath:event.path itemOnly:YES error:&error syncAnchor:NULL]) != nil) + OCLocation *eventLocation = [[OCLocation alloc] initWithDriveID:event.driveID path:event.path]; + + if ((cacheItems = [self.database retrieveCacheItemsSyncAtLocation:eventLocation itemOnly:YES error:&error syncAnchor:NULL]) != nil) { BOOL doSchedule = NO; @@ -1374,7 +1476,7 @@ - (void)_handleRetrieveItemListEvent:(OCEvent *)event sender:(id)sender if (doSchedule) { - [self scheduleUpdateScanForPath:event.path waitForNextQueueCycle:NO]; + [self scheduleUpdateScanForLocation:eventLocation waitForNextQueueCycle:NO]; } else { @@ -1536,13 +1638,13 @@ - (void)_finishedUpdateScanWithError:(nullable NSError *)error foundChanges:(BOO } #pragma mark - Update Scans -- (void)scheduleUpdateScanForPath:(OCPath)path waitForNextQueueCycle:(BOOL)waitForNextQueueCycle +- (void)scheduleUpdateScanForLocation:(OCLocation *)location waitForNextQueueCycle:(BOOL)waitForNextQueueCycle { - OCCoreDirectoryUpdateJob *updateScanPath; + OCCoreDirectoryUpdateJob *updateJob; - OCLogDebug(@"Scheduling scan for path=%@, waitForNextCycle: %d", path, waitForNextQueueCycle); + OCLogDebug(@"Scheduling scan for location=%@, waitForNextCycle: %d", location, waitForNextQueueCycle); - if ((updateScanPath = [OCCoreDirectoryUpdateJob withPath:path]) != nil) + if ((updateJob = [OCCoreDirectoryUpdateJob withLocation:location]) != nil) { [self beginActivity:@"Scheduling update scan"]; @@ -1560,30 +1662,30 @@ - (void)scheduleUpdateScanForPath:(OCPath)path waitForNextQueueCycle:(BOOL)waitF _pendingScheduledDirectoryUpdateJobs++; } - [self.database retrieveDirectoryUpdateJobsAfter:nil forPath:path maximumJobs:1 completionHandler:^(OCDatabase *db, NSError *error, NSArray *updateJobs) { + [self.database retrieveDirectoryUpdateJobsAfter:nil forLocation:location maximumJobs:1 completionHandler:^(OCDatabase *db, NSError *error, NSArray *updateJobs) { if (updateJobs.count > 0) { // Don't schedule - OCLogDebug(@"Skipping duplicate update job for path=%@", path); + OCLogDebug(@"Skipping duplicate update job for location=%@", location); doneSchedulingPendingDirectoryUpdateJob(); [self _checkForUpdateJobsCompletion]; return; } - [self.database addDirectoryUpdateJob:updateScanPath completionHandler:^(OCDatabase *db, NSError *error, OCCoreDirectoryUpdateJob *scanPath) { + [self.database addDirectoryUpdateJob:updateJob completionHandler:^(OCDatabase *db, NSError *error, OCCoreDirectoryUpdateJob *scanPath) { if (error == nil) { if (waitForNextQueueCycle) { [self queueBlock:^{ - [self _scheduleUpdateJob:updateScanPath]; + [self _scheduleUpdateJob:updateJob]; doneSchedulingPendingDirectoryUpdateJob(); }]; } else { - [self _scheduleUpdateJob:updateScanPath]; + [self _scheduleUpdateJob:updateJob]; doneSchedulingPendingDirectoryUpdateJob(); } } @@ -1594,7 +1696,7 @@ - (void)scheduleUpdateScanForPath:(OCPath)path waitForNextQueueCycle:(BOOL)waitF - (void)recoverPendingUpdateJobs { - [self.database retrieveDirectoryUpdateJobsAfter:nil forPath:nil maximumJobs:0 completionHandler:^(OCDatabase *db, NSError *error, NSArray *updateJobs) { + [self.database retrieveDirectoryUpdateJobsAfter:nil forLocation:nil maximumJobs:0 completionHandler:^(OCDatabase *db, NSError *error, NSArray *updateJobs) { OCLogDebug(@"Recovering pending update jobs"); for (OCCoreDirectoryUpdateJob *job in updateJobs) @@ -1620,18 +1722,18 @@ - (void)_scheduleUpdateJob:(OCCoreDirectoryUpdateJob *)job { [_scheduledDirectoryUpdateJobIDs addObject:job.identifier]; - [self _updateBackgroundScanActivityWithIncrement:YES currentPathChange:nil]; + [self _updateBackgroundScanActivityWithIncrement:YES currentLocationChange:nil]; } } } if (schedule) { - [self scheduleItemListTaskForPath:job.path forDirectoryUpdateJob:job withMeasurement:nil]; + [self scheduleItemListTaskForLocation:job.location forDirectoryUpdateJob:job withMeasurement:nil]; } } -- (void)_updateBackgroundScanActivityWithIncrement:(BOOL)increment currentPathChange:(OCPath)currentPathChange +- (void)_updateBackgroundScanActivityWithIncrement:(BOOL)increment currentLocationChange:(OCLocation *)currentLocationChange { @synchronized(_scheduledDirectoryUpdateJobIDs) { @@ -1662,7 +1764,7 @@ - (void)_updateBackgroundScanActivityWithIncrement:(BOOL)increment currentPathCh _totalScheduledDirectoryUpdateJobs++; } - if (currentPathChange != nil) + if (currentLocationChange != nil) { _scheduledDirectoryUpdateJobActivity.completedUpdateJobs = _totalScheduledDirectoryUpdateJobs - activeScheduledJobsCount; _scheduledDirectoryUpdateJobActivity.totalUpdateJobs = _totalScheduledDirectoryUpdateJobs; @@ -1688,7 +1790,7 @@ - (void)_handleCompletionOfUpdateJobWithID:(OCCoreDirectoryUpdateJobID)doneJobID { [_scheduledDirectoryUpdateJobIDs removeObject:doneJobID]; - [self _updateBackgroundScanActivityWithIncrement:NO currentPathChange:nil]; + [self _updateBackgroundScanActivityWithIncrement:NO currentLocationChange:nil]; } [self _checkForUpdateJobsCompletion]; diff --git a/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.h b/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.h index 060fe853..8e437aa6 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.h +++ b/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.h @@ -18,6 +18,7 @@ #import #import "OCTypes.h" +#import "OCLocation.h" NS_ASSUME_NONNULL_BEGIN @@ -26,13 +27,13 @@ typedef NSNumber* OCCoreDirectoryUpdateJobID; @interface OCCoreDirectoryUpdateJob : NSObject @property(nullable,strong) OCCoreDirectoryUpdateJobID identifier; -@property(strong) OCPath path; +@property(strong) OCLocation *location; @property(nonatomic,strong) NSSet *representedJobIDs; //!< The jobs represented by this job. Typically its own identifier and the identifiers of other jobs it was scheduled for. @property(nonatomic,readonly) BOOL isForQuery; -+ (instancetype)withPath:(OCPath)path; ++ (instancetype)withLocation:(OCLocation *)location; - (void)addRepresentedJobID:(nullable OCCoreDirectoryUpdateJobID)jobID; diff --git a/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.m b/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.m index 546dfd9e..7a82c0b5 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.m +++ b/ownCloudSDK/Core/ItemList/OCCoreDirectoryUpdateJob.m @@ -20,11 +20,11 @@ @implementation OCCoreDirectoryUpdateJob -+ (instancetype)withPath:(OCPath)path ++ (instancetype)withLocation:(OCLocation *)location { OCCoreDirectoryUpdateJob *updateScanPath = [OCCoreDirectoryUpdateJob new]; - updateScanPath.path = path; + updateScanPath.location = location; return (updateScanPath); } @@ -69,7 +69,7 @@ - (BOOL)isForQuery #pragma mark - Description - (NSString *)description { - return ([NSString stringWithFormat:@"<%@: %p, jobID: %@, path: %@, isForQuery: %d, representedJobIDs: %@>", NSStringFromClass(self.class), self, _identifier, _path, self.isForQuery, self.representedJobIDs]); + return ([NSString stringWithFormat:@"<%@: %p, jobID: %@, location: %@, isForQuery: %d, representedJobIDs: %@>", NSStringFromClass(self.class), self, _identifier, _location, self.isForQuery, self.representedJobIDs]); } @end diff --git a/ownCloudSDK/Core/ItemList/OCCoreItemList.h b/ownCloudSDK/Core/ItemList/OCCoreItemList.h index 92f66528..54466d32 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreItemList.h +++ b/ownCloudSDK/Core/ItemList/OCCoreItemList.h @@ -44,6 +44,8 @@ typedef NS_ENUM(NSUInteger, OCCoreItemListState) NSMutableDictionary *> *_itemsByParentPaths; NSSet *_itemParentPaths; + NSMutableDictionary *_itemListsByDriveID; + NSError *_error; } @@ -63,6 +65,8 @@ typedef NS_ENUM(NSUInteger, OCCoreItemListState) @property(readonly,strong,nonatomic) NSMutableDictionary *> *itemsByParentPaths; @property(readonly,strong,nonatomic) NSSet *itemParentPaths; +@property(readonly,strong,nonatomic) NSMutableDictionary *itemListsByDriveID; + @property(strong) NSError *error; + (instancetype)itemListWithItems:(NSArray *)items; diff --git a/ownCloudSDK/Core/ItemList/OCCoreItemList.m b/ownCloudSDK/Core/ItemList/OCCoreItemList.m index 49206d06..6d5f00fe 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreItemList.m +++ b/ownCloudSDK/Core/ItemList/OCCoreItemList.m @@ -18,6 +18,7 @@ #import "OCCoreItemList.h" #import "NSString+OCPath.h" +#import "OCDrive.h" @implementation OCCoreItemList @@ -64,6 +65,14 @@ - (void)setItems:(NSArray *)items { _itemsByPath = nil; _itemPathsSet = nil; + _itemsByFileID = nil; + _itemFileIDsSet = nil; + _itemsByLocalID = nil; + _itemLocalIDsSet = nil; + _itemsByParentPaths = nil; + _itemParentPaths = nil; + _itemListsByDriveID = nil; + _items = items; } @@ -224,4 +233,43 @@ - (void)setItems:(NSArray *)items return (_itemParentPaths); } +- (NSMutableDictionary *> *)_itemsByDriveID +{ + NSMutableDictionary *> *itemsByDriveID = [NSMutableDictionary new]; + OCDriveID nilID = OCDriveIDNil; + + for (OCItem *item in self.items) + { + OCDriveID driveID = (item.driveID == nil) ? nilID : item.driveID; + NSMutableArray *items; + + if ((items = itemsByDriveID[driveID]) == nil) + { + items = [NSMutableArray new]; + itemsByDriveID[driveID] = items; + } + + [items addObject:item]; + } + + return(itemsByDriveID); +} + +- (NSMutableDictionary *)itemListsByDriveID +{ + if (_itemListsByDriveID == nil) + { + NSMutableDictionary *> *itemsByDriveID = [self _itemsByDriveID]; + NSMutableDictionary *itemListsByDriveID = [NSMutableDictionary new]; + + [itemsByDriveID enumerateKeysAndObjectsUsingBlock:^(OCDriveID _Nonnull driveID, NSMutableArray * _Nonnull driveItems, BOOL * _Nonnull stop) { + itemListsByDriveID[driveID] = [OCCoreItemList itemListWithItems:driveItems]; + }]; + + _itemListsByDriveID = itemListsByDriveID; + } + + return(_itemListsByDriveID); +} + @end diff --git a/ownCloudSDK/Core/ItemList/OCCoreItemListTask.h b/ownCloudSDK/Core/ItemList/OCCoreItemListTask.h index c4c9af01..f9cc0c53 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreItemListTask.h +++ b/ownCloudSDK/Core/ItemList/OCCoreItemListTask.h @@ -38,7 +38,7 @@ typedef void(^OCCoreItemListTaskChangeHandler)(OCCore *core, OCCoreItemListTask @interface OCCoreItemListTask : NSObject @property(weak) OCCore *core; -@property(strong) OCPath path; +@property(strong) OCLocation *location; @property(strong) OCCoreDirectoryUpdateJob *updateJob; @@ -55,7 +55,7 @@ typedef void(^OCCoreItemListTaskChangeHandler)(OCCore *core, OCCoreItemListTask @property(strong) OCCoreItemListTask *nextItemListTask; -- (instancetype)initWithCore:(OCCore *)core path:(OCPath)path updateJob:(OCCoreDirectoryUpdateJob *)updateJob; +- (instancetype)initWithCore:(OCCore *)core location:(OCLocation *)location updateJob:(OCCoreDirectoryUpdateJob *)updateJob; - (void)update; diff --git a/ownCloudSDK/Core/ItemList/OCCoreItemListTask.m b/ownCloudSDK/Core/ItemList/OCCoreItemListTask.m index 7705b154..76e0050c 100644 --- a/ownCloudSDK/Core/ItemList/OCCoreItemListTask.m +++ b/ownCloudSDK/Core/ItemList/OCCoreItemListTask.m @@ -50,12 +50,12 @@ - (instancetype)init return(self); } -- (instancetype)initWithCore:(OCCore *)core path:(OCPath)path updateJob:(OCCoreDirectoryUpdateJob *)updateJob +- (instancetype)initWithCore:(OCCore *)core location:(OCLocation *)location updateJob:(OCCoreDirectoryUpdateJob *)updateJob { if ((self = [self init]) != nil) { self.core = core; - self.path = path; + self.location = location; self.updateJob = updateJob; } @@ -95,7 +95,7 @@ - (void)_cacheUpdateInline:(BOOL)doInline notifyChange:(BOOL)notifyChange comple { OCMeasureEventBegin(self, @"db.cache", cacheRetrieveRef, @"Retrieve from cache"); - [_core.vault.database retrieveCacheItemsAtPath:self.path itemOnly:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [_core.vault.database retrieveCacheItemsAtLocation:self.location itemOnly:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { OCSyncAnchor latestAnchorAtRetrieval = [self->_core retrieveLatestSyncAnchorWithError:NULL]; OCMeasurementEventReference queueRef = 0; @@ -167,16 +167,16 @@ - (void)_updateRetrievedSet OCMeasureEventEnd(self, @"core.queue", propFindEvenRef, @"Beginning PROPFIND"); - OCMeasureEventBegin(self, @"network.propfind", propFindEvenRef, ([NSString stringWithFormat:@"Starting PROPFIND for %@", self.path])); + OCMeasureEventBegin(self, @"network.propfind", propFindEvenRef, ([NSString stringWithFormat:@"Starting PROPFIND for %@", self.location])); - retrievalProgress = [self->_core.connection retrieveItemListAtPath:self.path depth:1 options:[NSDictionary dictionaryWithObjectsAndKeys: + retrievalProgress = [self->_core.connection retrieveItemListAtLocation:self.location depth:1 options:[NSDictionary dictionaryWithObjectsAndKeys: // For background scan jobs, wait with scheduling until there is connectivity ((self.updateJob.isForQuery) ? self.core.connection.propFindSignals : self.core.connection.actionSignals), OCConnectionOptionRequiredSignalsKey, // Schedule in a particular group ((self.groupID != nil) ? self.groupID : nil), OCConnectionOptionGroupIDKey, nil] completionHandler:^(NSError *error, NSArray *items) { - OCMeasureEventEnd(self, @"network.propfind", propFindEvenRef, ([NSString stringWithFormat:@"Completed PROPFIND for %@", self.path])); + OCMeasureEventEnd(self, @"network.propfind", propFindEvenRef, ([NSString stringWithFormat:@"Completed PROPFIND for %@", self.location])); if (self.core.state != OCCoreStateRunning) { @@ -188,25 +188,35 @@ - (void)_updateRetrievedSet [self->_core beginActivity:@"update retrieved set"]; - OCMeasureEventBegin(self, @"core.queue", queueRef, ([NSString stringWithFormat:@"Queue update of retrieved set for %@", self.path])); + OCMeasureEventBegin(self, @"core.queue", queueRef, ([NSString stringWithFormat:@"Queue update of retrieved set for %@", self.location])); [self->_core queueBlock:^{ + if (self.core.state != OCCoreStateRunning) + { + // Skip processing the response if the core is not starting or running + self.retrievedSet.state = OCCoreItemListStateNew; + completionHandler(); // we're done for now, make sure the queue doesn't get stuck + + [self->_core endActivity:@"update retrieved set"]; + return; + } + // Update inside the core's serial queue to make sure we never change the data while the core is also working on it - OCMeasureEventEnd(self, @"core.queue", queueRef, ([NSString stringWithFormat:@"Processing update of retrieved set for %@", self.path])); + OCMeasureEventEnd(self, @"core.queue", queueRef, ([NSString stringWithFormat:@"Processing update of retrieved set for %@", self.location])); - OCMeasureEventBegin(self, @"itemlist.update-from-propfind", propFindRef, ([NSString stringWithFormat:@"Update retrieved set for %@", self.path])); + OCMeasureEventBegin(self, @"itemlist.update-from-propfind", propFindRef, ([NSString stringWithFormat:@"Update retrieved set for %@", self.location])); OCSyncAnchor latestSyncAnchor = [self.core retrieveLatestSyncAnchorWithError:NULL]; if ((latestSyncAnchor != nil) && (![latestSyncAnchor isEqualToNumber:self.syncAnchorAtStart])) { - OCTLogDebug(@[@"ItemListTask"], @"Sync anchor changed before task finished: latestSyncAnchor=%@ != task.syncAnchorAtStart=%@, path=%@ -> updating inline", latestSyncAnchor, self.syncAnchorAtStart, self.path); + OCTLogDebug(@[@"ItemListTask"], @"Sync anchor changed before task finished: latestSyncAnchor=%@ != task.syncAnchorAtStart=%@, path=%@ -> updating inline", latestSyncAnchor, self.syncAnchorAtStart, self.location); // Cache set is outdated - update now to avoid unnecessary requests OCSyncExec(inlineUpdate, { - OCMeasureEventBegin(self, @"itemlist.cache-reload", cacheUpdateRef, ([NSString stringWithFormat:@"Start inline cache update for %@", self.path])); + OCMeasureEventBegin(self, @"itemlist.cache-reload", cacheUpdateRef, ([NSString stringWithFormat:@"Start inline cache update for %@", self.location])); [self _cacheUpdateInline:YES notifyChange:NO completionHandler:^{ - OCMeasureEventEnd(self, @"itemlist.cache-reload", cacheUpdateRef, ([NSString stringWithFormat:@"Done inline cache update for %@", self.path])); + OCMeasureEventEnd(self, @"itemlist.cache-reload", cacheUpdateRef, ([NSString stringWithFormat:@"Done inline cache update for %@", self.location])); OCSyncExecDone(inlineUpdate); }]; }); @@ -227,16 +237,16 @@ - (void)_updateRetrievedSet if (self->_retrievedSet.state == OCCoreItemListStateSuccess) { // Update all items with root item - if (self.path != nil) + if (self.location != nil) { OCItem *rootItem; OCItem *cachedRootItem; - if ((rootItem = self->_retrievedSet.itemsByPath[self.path]) != nil) + if ((rootItem = self->_retrievedSet.itemsByPath[self.location.path]) != nil) { if ((cachedRootItem = self->_cachedSet.itemsByFileID[rootItem.fileID]) == nil) { - cachedRootItem = self->_cachedSet.itemsByPath[self.path]; + cachedRootItem = self->_cachedSet.itemsByPath[self.location.path]; } if (cachedRootItem != nil) @@ -268,7 +278,7 @@ - (void)_updateRetrievedSet } else { - OCLogWarning(@"Missing root item for %@", self.path); + OCLogWarning(@"Missing root item for %@", self.location); } } else @@ -286,7 +296,7 @@ - (void)_updateRetrievedSet [self->_core endActivity:@"update retrieved set"]; - OCMeasureEventEnd(self, @"itemlist.update-from-propfind", propFindRef, ([NSString stringWithFormat:@"Done updating retrieved set for %@", self.path])); + OCMeasureEventEnd(self, @"itemlist.update-from-propfind", propFindRef, ([NSString stringWithFormat:@"Done updating retrieved set for %@", self.location])); completionHandler(); }]; @@ -300,7 +310,7 @@ - (void)_updateRetrievedSet }]; }; - if ([self.path isEqual:@"/"]) + if ([self.location.path isEqual:@"/"]) { RetrieveItems(nil); } @@ -312,7 +322,7 @@ - (void)_updateRetrievedSet NSArray *items = nil; // Retrieve parent item from cache. - items = [self->_core.vault.database retrieveCacheItemsSyncAtPath:[self.path parentPath] itemOnly:YES error:&dbError syncAnchor:NULL]; + items = [self->_core.vault.database retrieveCacheItemsSyncAtLocation:self.location.parentLocation itemOnly:YES error:&dbError syncAnchor:NULL]; if (dbError != nil) { @@ -328,7 +338,7 @@ - (void)_updateRetrievedSet // contents after discovery, this should never happen. However, for direct requests to directories, this may happen. // In that case, the parent directory(s) need to be requested first, so that their parent item(s) are known and in // the database. - OCQuery *parentDirectoryQuery = [OCQuery queryForPath:[self.path parentPath]]; + OCQuery *parentDirectoryQuery = [OCQuery queryForLocation:self.location.parentLocation]; [parentDirectoryQuery attachMeasurement:self.extractedMeasurement]; @@ -406,7 +416,7 @@ - (OCActivityIdentifier)activityIdentifier - (OCActivity *)provideActivity { - OCActivity *activity = [OCActivity withIdentifier:self.activityIdentifier description:[NSString stringWithFormat:OCLocalized(@"Retrieving items for %@"), self.path] statusMessage:nil ranking:0]; + OCActivity *activity = [OCActivity withIdentifier:self.activityIdentifier description:[NSString stringWithFormat:OCLocalized(@"Retrieving items for %@"), self.location.path] statusMessage:nil ranking:0]; activity.progress = NSProgress.indeterminateProgress; diff --git a/ownCloudSDK/Core/ItemPolicies/OCCore+ItemPolicies.m b/ownCloudSDK/Core/ItemPolicies/OCCore+ItemPolicies.m index 62de415c..0e72bc53 100644 --- a/ownCloudSDK/Core/ItemPolicies/OCCore+ItemPolicies.m +++ b/ownCloudSDK/Core/ItemPolicies/OCCore+ItemPolicies.m @@ -397,7 +397,7 @@ - (void)postInternalItemPoliciesChangedNotificationForPolicy:(OCItemPolicy *)pol { if ([policy.kind isEqual:OCItemPolicyKindAvailableOffline]) { - @synchronized(_availableOfflineFolderPaths) + @synchronized(_availableOfflineFolderLocations) { _availableOfflineCacheValid = NO; } diff --git a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.h b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.h new file mode 100644 index 00000000..f14e2881 --- /dev/null +++ b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.h @@ -0,0 +1,28 @@ +// +// OCItemPolicy+OCDataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.12.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 "OCItemPolicy.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCItemPolicy (OCDataItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.m b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.m new file mode 100644 index 00000000..64ae4723 --- /dev/null +++ b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy+OCDataItem.m @@ -0,0 +1,67 @@ +// +// OCItemPolicy+OCDataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.12.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 "OCItemPolicy+OCDataItem.h" +#import "OCDataConverter.h" +#import "OCDataRenderer.h" +#import "OCDataItemPresentable.h" +#import "OCSymbol.h" + +@implementation OCItemPolicy (OCDataItem) + +#pragma mark - OCDataItem & OCDataItemVersioning +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeItemPolicy); +} + +- (OCDataItemReference)dataItemReference +{ + return ((self.uuid != nil) ? self.uuid : [NSString stringWithFormat:@"%@:%@:%p", self.kind, self.databaseID, self]); +} + +- (OCDataItemVersion)dataItemVersion +{ + return ([NSString stringWithFormat:@"%@", self.location.string]); +} + +#pragma mark - OCDataConverter for OCDrives ++ (void)load +{ + OCDataConverter *itemPolicyToPresentableConverter; + + itemPolicyToPresentableConverter = [[OCDataConverter alloc] initWithInputType:OCDataItemTypeItemPolicy outputType:OCDataItemTypePresentable conversion:^id _Nullable(OCDataConverter * _Nonnull converter, OCItemPolicy * _Nullable inPolicy, OCDataRenderer * _Nullable renderer, NSError * _Nullable __autoreleasing * _Nullable outError, OCDataViewOptions _Nullable options) { + OCDataItemPresentable *presentable = nil; + + if (inPolicy != nil) + { + presentable = [[OCDataItemPresentable alloc] initWithItem:inPolicy]; + presentable.title = inPolicy.location.lastPathComponent; + presentable.subtitle = inPolicy.location.path; + presentable.image = (inPolicy.location.type == OCLocationTypeFile) ? [OCSymbol iconForSymbolName:@"doc"] : [OCSymbol iconForSymbolName:@"folder"] ; + } + + return (presentable); + }]; + + [OCDataRenderer.defaultRenderer addConverters:@[ + itemPolicyToPresentableConverter + ]]; +} + +@end diff --git a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.h b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.h index f6f7b6f1..b6332c66 100644 --- a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.h +++ b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.h @@ -19,10 +19,13 @@ #import #import "OCQueryCondition.h" #import "OCTypes.h" +#import "OCDataTypes.h" typedef NSString* OCItemPolicyKind NS_TYPED_ENUM; typedef NSString* OCItemPolicyIdentifier; +typedef NSString* OCItemPolicyUUID; + typedef NS_ENUM(NSUInteger, OCItemPolicyAutoRemovalMethod) { OCItemPolicyAutoRemovalMethodNone, //!< Do not automatically remove this item policy @@ -33,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN @interface OCItemPolicy : NSObject +@property(strong) OCItemPolicyUUID uuid; //!< UUID of the item policy + #pragma mark - Database glue @property(nullable,strong) OCDatabaseID databaseID; //!< OCDatabase-specific ID referencing the policy in the database @@ -40,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) OCItemPolicyIdentifier identifier; //!< Optional identifier uniquely identifying a policy (f.ex. to re-recognize an internal policy) @property(nullable,strong) NSString *policyDescription; //!< Optional description of the policy (f.ex. to store a user-facing/editable description) -@property(nullable,strong) OCPath path; //!< Optional path for use by clients of the ItemPolicy system such as AvailableOffline. +@property(nullable,strong) OCLocation *location; //!< Optional location for use by clients of the ItemPolicy system such as AvailableOffline. @property(nullable,strong) OCLocalID localID; //!< Optional localID for use by clients of the ItemPolicy system such as AvailableOffline. #pragma mark - Policy definition diff --git a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.m b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.m index 0ce590d0..d6212aee 100644 --- a/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.m +++ b/ownCloudSDK/Core/ItemPolicies/OCItemPolicy.m @@ -20,6 +20,16 @@ @implementation OCItemPolicy +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _uuid = NSUUID.UUID.UUIDString; + } + + return (self); +} + - (instancetype)initWithKind:(OCItemPolicyKind)kind condition:(OCQueryCondition *)condition { if ((self = [self init]) != nil) @@ -38,7 +48,14 @@ - (instancetype)initWithKind:(OCItemPolicyKind)kind item:(OCItem *)item switch (item.type) { case OCItemTypeCollection: - condition = [OCQueryCondition where:OCItemPropertyNamePath startsWith:item.path]; + if (item.driveID != nil) + { + condition = [OCQueryCondition where:OCItemPropertyNameLocationString startsWith:item.locationString]; + } + else + { + condition = [OCQueryCondition where:OCItemPropertyNamePath startsWith:item.path]; + } break; case OCItemTypeFile: @@ -59,17 +76,28 @@ - (instancetype)initWithCoder:(NSCoder *)decoder { if ((self = [self init]) != nil) { - _identifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; - _policyDescription = [decoder decodeObjectOfClass:[NSString class] forKey:@"policyDescription"]; + _uuid = [decoder decodeObjectOfClass:NSString.class forKey:@"uuid"]; - _path = [decoder decodeObjectOfClass:[NSString class] forKey:@"path"]; - _localID = [decoder decodeObjectOfClass:[NSString class] forKey:@"localID"]; + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _policyDescription = [decoder decodeObjectOfClass:NSString.class forKey:@"policyDescription"]; - _kind = [decoder decodeObjectOfClass:[NSString class] forKey:@"kind"]; - _condition = [decoder decodeObjectOfClass:[OCQueryCondition class] forKey:@"condition"]; + _location = [decoder decodeObjectOfClass:OCLocation.class forKey:@"location"]; + if (_location == nil) + { + OCPath path; + + if ((path = [decoder decodeObjectOfClass:NSString.class forKey:@"path"]) != nil) + { + _location = [OCLocation legacyRootPath:path]; + } + } + _localID = [decoder decodeObjectOfClass:NSString.class forKey:@"localID"]; + + _kind = [decoder decodeObjectOfClass:NSString.class forKey:@"kind"]; + _condition = [decoder decodeObjectOfClass:OCQueryCondition.class forKey:@"condition"]; _policyAutoRemovalMethod = [decoder decodeIntegerForKey:@"policyAutoRemovalMethod"]; - _policyAutoRemovalCondition = [decoder decodeObjectOfClass:[OCQueryCondition class] forKey:@"policyAutoRemovalCondition"]; + _policyAutoRemovalCondition = [decoder decodeObjectOfClass:OCQueryCondition.class forKey:@"policyAutoRemovalCondition"]; } return (self); @@ -77,10 +105,20 @@ - (instancetype)initWithCoder:(NSCoder *)decoder - (void)encodeWithCoder:(NSCoder *)coder { + OCItemPolicyUUID uuid = _uuid; + + if (uuid == nil) + { + // Add missing UUID when saving without modifying the live object + uuid = NSUUID.UUID.UUIDString; + } + + [coder encodeObject:uuid forKey:@"uuid"]; + [coder encodeObject:_identifier forKey:@"identifier"]; [coder encodeObject:_policyDescription forKey:@"policyDescription"]; - [coder encodeObject:_path forKey:@"path"]; + [coder encodeObject:_location forKey:@"location"]; [coder encodeObject:_localID forKey:@"localID"]; [coder encodeObject:_kind forKey:@"kind"]; diff --git a/ownCloudSDK/Core/ItemPolicies/Processors/Available Offline/OCItemPolicyProcessorAvailableOffline.m b/ownCloudSDK/Core/ItemPolicies/Processors/Available Offline/OCItemPolicyProcessorAvailableOffline.m index a52a7695..be95bff1 100644 --- a/ownCloudSDK/Core/ItemPolicies/Processors/Available Offline/OCItemPolicyProcessorAvailableOffline.m +++ b/ownCloudSDK/Core/ItemPolicies/Processors/Available Offline/OCItemPolicyProcessorAvailableOffline.m @@ -77,23 +77,45 @@ - (void)performPreflightOnPoliciesWithTrigger:(OCItemPolicyProcessorTrigger)trig (changedItem.type == OCItemTypeCollection) // .. and a directory ) { - if (![changedItem.path isEqual:policy.path]) + if (![changedItem.location isEqual:policy.location]) { - if ((policy.condition.operator == OCQueryConditionOperatorPropertyHasPrefix) && - ([policy.condition.property isEqual:OCItemPropertyNamePath]) && - ([policy.condition.value isEqual:policy.path])) - { - OCLogDebug(@"Updating existing policy from path %@ to %@", policy.path, changedItem.path); - - policy.condition.value = changedItem.path; - policy.path = changedItem.path; + BOOL updatePolicy = NO; - OCSyncExec(waitForPolicyUpdate, { - [self.core updateItemPolicy:policy options:OCCoreItemPolicyOptionSkipTrigger completionHandler:^(NSError * _Nullable error) { - OCLogDebug(@"Updated %@ with error=%@", policy, error); - OCSyncExecDone(waitForPolicyUpdate); - }]; - }); + if (policy.condition.operator == OCQueryConditionOperatorPropertyHasPrefix) + { + // Legacy / OC10 policy + if (([policy.condition.property isEqual:OCItemPropertyNamePath]) && + ([policy.condition.value isEqual:policy.location.path])) + { + OCLogDebug(@"Updating existing policy from %@ to %@", policy.location, changedItem.location); + + policy.condition.value = changedItem.path; + policy.location = changedItem.location; + + updatePolicy = YES; + } + + // Drive-based policy + if (([policy.condition.property isEqual:OCItemPropertyNameLocationString]) && + ([policy.condition.value isEqual:policy.location.string])) + { + OCLogDebug(@"Updating existing policy from %@ to %@", policy.location, changedItem.location); + + policy.condition.value = changedItem.locationString; + policy.location = changedItem.location; + + updatePolicy = YES; + } + + if (updatePolicy) + { + OCSyncExec(waitForPolicyUpdate, { + [self.core updateItemPolicy:policy options:OCCoreItemPolicyOptionSkipTrigger completionHandler:^(NSError * _Nullable error) { + OCLogDebug(@"Updated %@ with error=%@", policy, error); + OCSyncExecDone(waitForPolicyUpdate); + }]; + }); + } } } } @@ -168,7 +190,7 @@ - (void)performActionOn:(OCItem *)matchingItem withTrigger:(OCItemPolicyProcesso { matchingItem.downloadTriggerIdentifier = OCItemDownloadTriggerIDAvailableOffline; - [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ matchingItem ] refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ matchingItem ] refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } } } diff --git a/ownCloudSDK/Core/ItemPolicies/Processors/Download Expiration/OCItemPolicyProcessorDownloadExpiration.m b/ownCloudSDK/Core/ItemPolicies/Processors/Download Expiration/OCItemPolicyProcessorDownloadExpiration.m index e4ade83b..9d4fc9e4 100644 --- a/ownCloudSDK/Core/ItemPolicies/Processors/Download Expiration/OCItemPolicyProcessorDownloadExpiration.m +++ b/ownCloudSDK/Core/ItemPolicies/Processors/Download Expiration/OCItemPolicyProcessorDownloadExpiration.m @@ -224,7 +224,7 @@ - (void)endCleanupWithTrigger:(OCItemPolicyProcessorTrigger)trigger // Update items in database in a single transaction OCLogDebug(@"Updating %lu trimmed items", _trimmedItems.count); - [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:_trimmedItems refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:_trimmedItems refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; _trimmedItems = nil; } diff --git a/ownCloudSDK/Core/ItemPolicies/Processors/Version Updates/OCItemPolicyProcessorVersionUpdates.m b/ownCloudSDK/Core/ItemPolicies/Processors/Version Updates/OCItemPolicyProcessorVersionUpdates.m index c43603f8..5297d35a 100644 --- a/ownCloudSDK/Core/ItemPolicies/Processors/Version Updates/OCItemPolicyProcessorVersionUpdates.m +++ b/ownCloudSDK/Core/ItemPolicies/Processors/Version Updates/OCItemPolicyProcessorVersionUpdates.m @@ -88,7 +88,7 @@ - (void)performActionOn:(OCItem *)matchingItem withTrigger:(OCItemPolicyProcesso OCFileOpLog(@"rm", deleteError, @"Deleted outdated, unclaimed local copy at %@", deleteFileURL.path); - [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ matchingItem ] refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self.core performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ matchingItem ] refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } } } diff --git a/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.h b/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.h index 5b344d3d..8aa81385 100644 --- a/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.h +++ b/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.h @@ -35,7 +35,7 @@ typedef void(^OCCoreUnusedNameSuggestionResultHandler)(NSString * _Nullable sugg @interface OCCore (NameConflicts) -- (void)suggestUnusedNameBasedOn:(NSString *)name atPath:(OCPath)path isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)nameStyle filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler; //!< Request a suggestion for an unused item name based on a given name and path, filtered by an optional block, returning a suggested name and an array of evaluated, but taken names. +- (void)suggestUnusedNameBasedOn:(NSString *)name atLocation:(OCLocation *)location isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)nameStyle filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler; //!< Request a suggestion for an unused item name based on a given name and path, filtered by an optional block, returning a suggested name and an array of evaluated, but taken names. @end diff --git a/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.m b/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.m index f27aeeb1..282bcfbf 100644 --- a/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.m +++ b/ownCloudSDK/Core/NameConflicts/OCCore+NameConflicts.m @@ -24,14 +24,14 @@ @implementation OCCore (NameConflicts) #pragma mark - Name conflict resolution -- (void)suggestUnusedNameBasedOn:(NSString *)itemName atPath:(OCPath)path isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)style filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler +- (void)suggestUnusedNameBasedOn:(NSString *)itemName atLocation:(OCLocation *)location isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)style filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler { [self queueBlock:^{ - [self _suggestUnusedNameBasedOn:itemName atPath:path isDirectory:isDirectory usingNameStyle:style filteredBy:filter resultHandler:resultHandler]; + [self _suggestUnusedNameBasedOn:itemName atLocation:location isDirectory:isDirectory usingNameStyle:style filteredBy:filter resultHandler:resultHandler]; } allowInlining:YES]; } -- (void)_suggestUnusedNameBasedOn:(NSString *)itemName atPath:(OCPath)path isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)style filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler +- (void)_suggestUnusedNameBasedOn:(NSString *)itemName atLocation:(OCLocation *)location isDirectory:(BOOL)isDirectory usingNameStyle:(OCCoreDuplicateNameStyle)style filteredBy:(nullable OCCoreUnusedNameSuggestionFilter)filter resultHandler:(OCCoreUnusedNameSuggestionResultHandler)resultHandler { OCCoreDuplicateNameStyle nameStyle = style; NSNumber *duplicateCountNumber = nil; @@ -90,7 +90,7 @@ - (void)_suggestUnusedNameBasedOn:(NSString *)itemName atPath:(OCPath)path isDir // Check for existing item NSError *error = nil; - if ([self cachedItemInParentPath:path withName:suggestedName isDirectory:isDirectory error:&error] != nil) + if ([self cachedItemInParentLocation:location withName:suggestedName isDirectory:isDirectory error:&error] != nil) { [duplicateNames addObject:suggestedName]; duplicateCount++; diff --git a/ownCloudSDK/Core/OCCore+Internal.h b/ownCloudSDK/Core/OCCore+Internal.h index 72981f10..5528cdea 100644 --- a/ownCloudSDK/Core/OCCore+Internal.h +++ b/ownCloudSDK/Core/OCCore+Internal.h @@ -20,6 +20,12 @@ #import "OCCoreItemListTask.h" #import "OCShareQuery.h" +typedef NS_ENUM(NSUInteger, OCCoreDataSourcePollType) +{ + OCCoreDataSourcePollTypeAll, + OCCoreDataSourcePollTypeFavorites +}; + @interface OCCore (Internal) #pragma mark - Managed @@ -58,4 +64,9 @@ - (void)_pollNextShareQuery; +#pragma mark - Data sources +- (void)unsubscribeFromPollingDatasourcesTimer:(OCCoreDataSourcePollType)pollType withForcedStop:(BOOL)force; +- (void)_updateSharedWithMeQueryForceStop:(BOOL)forceStop; +- (void)_updateAllSharedByMeQueryForceStop:(BOOL)forceStop; + @end diff --git a/ownCloudSDK/Core/OCCore+ItemUpdates.h b/ownCloudSDK/Core/OCCore+ItemUpdates.h index 525d7518..b7be805e 100644 --- a/ownCloudSDK/Core/OCCore+ItemUpdates.h +++ b/ownCloudSDK/Core/OCCore+ItemUpdates.h @@ -31,7 +31,7 @@ typedef void(^OCCoreItemUpdateQueryPostProcessor)(OCCore *core, OCQuery *query, - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems removedItems:(nullable NSArray *)removedItems updatedItems:(nullable NSArray *)updatedItems - refreshPaths:(nullable NSArray *)refreshPaths + refreshLocations:(nullable NSArray *)refreshLocations newSyncAnchor:(nullable OCSyncAnchor)newSyncAnchor beforeQueryUpdates:(nullable OCCoreItemUpdateAction)beforeQueryUpdatesAction afterQueryUpdates:(nullable OCCoreItemUpdateAction)afterQueryUpdatesAction diff --git a/ownCloudSDK/Core/OCCore+ItemUpdates.m b/ownCloudSDK/Core/OCCore+ItemUpdates.m index 86785767..ca999e2b 100644 --- a/ownCloudSDK/Core/OCCore+ItemUpdates.m +++ b/ownCloudSDK/Core/OCCore+ItemUpdates.m @@ -33,7 +33,7 @@ @implementation OCCore (ItemUpdates) - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems removedItems:(nullable NSArray *)removedItems updatedItems:(nullable NSArray *)updatedItems - refreshPaths:(nullable NSArray *)refreshPaths + refreshLocations:(nullable NSArray *)refreshLocations newSyncAnchor:(nullable OCSyncAnchor)newSyncAnchor beforeQueryUpdates:(nullable OCCoreItemUpdateAction)beforeQueryUpdatesAction afterQueryUpdates:(nullable OCCoreItemUpdateAction)afterQueryUpdatesAction @@ -41,12 +41,17 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems skipDatabase:(BOOL)skipDatabase { // Discard empty updates - if ((addedItems.count==0) && (removedItems.count == 0) && (updatedItems.count == 0) && (refreshPaths.count == 0) && + if ((addedItems.count==0) && (removedItems.count == 0) && (updatedItems.count == 0) && (refreshLocations.count == 0) && (beforeQueryUpdatesAction == nil) && (afterQueryUpdatesAction == nil) && (queryPostProcessor == nil)) { 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"]; @@ -55,7 +60,7 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems { // Make sure updates are wrapped into -incrementSyncAnchorWithProtectedBlock [self incrementSyncAnchorWithProtectedBlock:^NSError *(OCSyncAnchor previousSyncAnchor, OCSyncAnchor newSyncAnchor) { - [self performUpdatesForAddedItems:addedItems removedItems:removedItems updatedItems:updatedItems refreshPaths:refreshPaths newSyncAnchor:newSyncAnchor beforeQueryUpdates:beforeQueryUpdatesAction afterQueryUpdates:afterQueryUpdatesAction queryPostProcessor:queryPostProcessor skipDatabase:skipDatabase]; + [self performUpdatesForAddedItems:addedItems removedItems:removedItems updatedItems:updatedItems refreshLocations:refreshLocations newSyncAnchor:newSyncAnchor beforeQueryUpdates:beforeQueryUpdatesAction afterQueryUpdates:afterQueryUpdatesAction queryPostProcessor:queryPostProcessor skipDatabase:skipDatabase]; return ((NSError *)nil); } completionHandler:^(NSError *error, OCSyncAnchor previousSyncAnchor, OCSyncAnchor newSyncAnchor) { @@ -65,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)) { @@ -123,7 +132,7 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems // In parallel: remove thumbnails from in-memory cache for removed items OCWaitWillStartTask(cacheUpdatesGroup); - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ for (OCItem *removeItem in removedItems) { [self->_thumbnailCache removeObjectForKey:removeItem.fileID]; @@ -160,6 +169,7 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems NSArray *removedItems = theRemovedItems; __block NSMutableArray *addedUpdatedRemovedItems = nil; NSMutableArray *relocatedItems = nil; + NSMutableArray *movedFolderItems = nil; // Support for relocated items for (OCItem *updatedItem in updatedItems) @@ -182,6 +192,13 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems [relocatedItems addObject:reMovedItem]; } } + + // Is this a moved folder? + if (updatedItem.type == OCItemTypeCollection) + { + if (movedFolderItems == nil) { movedFolderItems = [NSMutableArray new]; } + [movedFolderItems addObject:updatedItem]; + } } } @@ -235,10 +252,34 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems @synchronized(query) { // Queries targeting directories + OCLocation *queryLocation = query.queryLocation; OCPath queryPath; - if ((queryPath = query.queryPath) != nil) + if ((queryPath = queryLocation.path) != nil) { + // Find moved observed folders + for (OCItem *movedFolderItem in movedFolderItems) + { + OCPath previousPath = movedFolderItem.previousPath; + + if ((previousPath != nil) && + [queryPath isEqual:movedFolderItem.previousPath] && + OCDriveIDIsIdentical(queryLocation.driveID, movedFolderItem.driveID)) + { + query.queryLocation = movedFolderItem.location; + queryLocation = query.queryLocation; + } + } + } + + if ((queryPath = queryLocation.path) != nil) + { + // Create drive-specific item lists + OCDriveID queryDriveID = OCDriveIDWrap(queryLocation.driveID); + OCCoreItemList *driveAddedItemList = addedItemList.itemListsByDriveID[queryDriveID]; + OCCoreItemList *driveRemovedItemList = removedItemList.itemListsByDriveID[queryDriveID]; + OCCoreItemList *driveUpdatedItemList = updatedItemList.itemListsByDriveID[queryDriveID]; + // Only update queries that .. if ((query.state == OCQueryStateIdle) || // .. have already gone through their complete, initial content update. ((query.state == OCQueryStateWaitingForServerReply) && (self.connectionStatus != OCCoreConnectionStatusOnline)) || // .. have not yet been able to factor in server replies because the connection isn't online. @@ -268,12 +309,12 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } }; - if ((addedItemList != nil) && (addedItemList.itemsByParentPaths[queryPath].count > 0)) + if ((driveAddedItemList != nil) && (driveAddedItemList.itemsByParentPaths[queryPath].count > 0)) { // Items were added in the target path of this query GetUpdatedFullResultsReady(); - for (OCItem *item in addedItemList.itemsByParentPaths[queryPath]) + for (OCItem *item in driveAddedItemList.itemsByParentPaths[queryPath]) { if (!query.includeRootItem && [item.path isEqual:queryPath]) { @@ -285,14 +326,14 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } } - if (removedItemList != nil) + if (driveRemovedItemList != nil) { - if (removedItemList.itemsByParentPaths[queryPath].count > 0) + if (driveRemovedItemList.itemsByParentPaths[queryPath].count > 0) { // Items were removed in the target path of this query GetUpdatedFullResultsReady(); - for (OCItem *item in removedItemList.itemsByParentPaths[queryPath]) + for (OCItem *item in driveRemovedItemList.itemsByParentPaths[queryPath]) { if (item.path != nil) { @@ -310,12 +351,12 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } } - if (removedItemList.itemsByPath[queryPath] != nil) + if (driveRemovedItemList.itemsByPath[queryPath] != nil) { - if (addedItemList.itemsByPath[queryPath] != nil) + if (driveAddedItemList.itemsByPath[queryPath] != nil) { // Handle replacement scenario - query.rootItem = addedItemList.itemsByPath[queryPath]; + query.rootItem = driveAddedItemList.itemsByPath[queryPath]; } else { @@ -328,11 +369,11 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems // Check if a parent folder of the queryPath has been removed if (query.state != OCQueryStateTargetRemoved) { - for (OCItem *removedItem in removedItemList.items) + for (OCItem *removedItem in driveRemovedItemList.items) { OCPath removedItemPath = removedItem.path; - if (removedItemPath.isNormalizedDirectoryPath && [query.queryPath hasPrefix:removedItemPath]) + if (removedItemPath.isNormalizedDirectoryPath && [queryPath hasPrefix:removedItemPath]) { // A parent folder of this query has been removed updatedFullQueryResults = [NSMutableArray new]; @@ -344,19 +385,19 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } } - if ((updatedItemList != nil) && (query.state != OCQueryStateTargetRemoved)) + if ((driveUpdatedItemList != nil) && (query.state != OCQueryStateTargetRemoved)) { OCItem *updatedRootItem = nil; GetUpdatedFullResultsReady(); - if ((updatedItemList.itemsByParentPaths[query.queryPath].count > 0) || // path match - ([updatedItemList.itemLocalIDsSet intersectsSet:updatedFullQueryResultsItemList.itemLocalIDsSet])) // Contained localID match + if ((driveUpdatedItemList.itemsByParentPaths[queryPath].count > 0) || // path match + ([driveUpdatedItemList.itemLocalIDsSet intersectsSet:updatedFullQueryResultsItemList.itemLocalIDsSet])) // Contained localID match { // Items were updated - for (OCItem *item in updatedItemList.itemsByParentPaths[query.queryPath]) + for (OCItem *item in driveUpdatedItemList.itemsByParentPaths[queryPath]) { - if (!query.includeRootItem && [item.path isEqual:query.queryPath]) + if (!query.includeRootItem && [item.path isEqual:queryPath]) { // Respect query.includeRootItem for special case "/" and don't include root items if not wanted continue; @@ -403,7 +444,7 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } } - if ((updatedRootItem = updatedItemList.itemsByPath[query.queryPath]) != nil) + if ((updatedRootItem = driveUpdatedItemList.itemsByPath[queryPath]) != nil) { // Root item of query was updated query.rootItem = updatedRootItem; @@ -412,7 +453,7 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems { OCItem *removeItem; - if ((removeItem = updatedFullQueryResultsItemList.itemsByPath[query.queryPath]) != nil) + if ((removeItem = updatedFullQueryResultsItemList.itemsByPath[queryPath]) != nil) { [updatedFullQueryResults removeObject:removeItem]; } @@ -441,28 +482,33 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems { OCPath queryItemPath = query.queryItem.path; OCLocalID queryItemLocalID = query.queryItem.localID; + OCDriveID queryItemDriveID = OCDriveIDWrap(query.queryItem.driveID); OCItem *resultItem = nil; OCItem *setNewItem = nil; - if (addedItemList!=nil) + OCCoreItemList *itemQueryAddedItemList = addedItemList.itemListsByDriveID[queryItemDriveID]; + OCCoreItemList *itemQueryRemovedItemList = removedItemList.itemListsByDriveID[queryItemDriveID]; + OCCoreItemList *itemQueryUpdatedItemList = updatedItemList.itemListsByDriveID[queryItemDriveID]; + + if (itemQueryAddedItemList!=nil) { - if ((resultItem = addedItemList.itemsByPath[queryItemPath]) != nil) + if ((resultItem = itemQueryAddedItemList.itemsByPath[queryItemPath]) != nil) { setNewItem = resultItem; } - else if ((resultItem = addedItemList.itemsByLocalID[queryItemLocalID]) != nil) + else if ((resultItem = itemQueryAddedItemList.itemsByLocalID[queryItemLocalID]) != nil) { setNewItem = resultItem; } } - if (updatedItemList!=nil) + if (itemQueryUpdatedItemList!=nil) { - if ((resultItem = updatedItemList.itemsByPath[queryItemPath]) != nil) + if ((resultItem = itemQueryUpdatedItemList.itemsByPath[queryItemPath]) != nil) { setNewItem = resultItem; } - else if ((resultItem = updatedItemList.itemsByLocalID[queryItemLocalID]) != nil) + else if ((resultItem = itemQueryUpdatedItemList.itemsByLocalID[queryItemLocalID]) != nil) { setNewItem = resultItem; } @@ -476,9 +522,9 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } else { - if (removedItemList!=nil) + if (itemQueryRemovedItemList!=nil) { - if ((removedItemList.itemsByPath[queryItemPath] != nil) || (removedItemList.itemsByLocalID[queryItemLocalID] != nil)) + if ((itemQueryRemovedItemList.itemsByPath[queryItemPath] != nil) || (itemQueryRemovedItemList.itemsByLocalID[queryItemLocalID] != nil)) { query.state = OCQueryStateTargetRemoved; query.fullQueryResults = [NSMutableArray new]; @@ -545,12 +591,12 @@ - (void)performUpdatesForAddedItems:(nullable NSArray *)addedItems } // - Fetch updated directory contents as needed - if (refreshPaths.count > 0) + if (refreshLocations.count > 0) { - for (OCPath path in refreshPaths) + for (OCLocation *location in refreshLocations) { // Ensure the sync anchor was updated following these updates before triggering a refresh - [self scheduleUpdateScanForPath:[path normalizedDirectoryPath] waitForNextQueueCycle:YES]; + [self scheduleUpdateScanForLocation:location.normalizedDirectoryPathLocation waitForNextQueueCycle:YES]; } } diff --git a/ownCloudSDK/Core/OCCore.h b/ownCloudSDK/Core/OCCore.h index c1ea7156..f67c11bd 100644 --- a/ownCloudSDK/Core/OCCore.h +++ b/ownCloudSDK/Core/OCCore.h @@ -39,6 +39,10 @@ #import "OCMeasurement.h" #import "OCLock.h" #import "OCLockRequest.h" +#import "OCDataSourceArray.h" +#import "OCDataSourceComposition.h" +#import "OCDataItemPresentable.h" +#import "OCShareRole.h" @class OCCore; @class OCItem; @@ -62,7 +66,7 @@ typedef NS_ENUM(NSUInteger, OCCoreState) OCCoreStateReady, //!< The core is started and ready, awaiting connecting to complete OCCoreStateRunning //!< The core is fully operational - and now running -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCCoreConnectionStatus) { @@ -70,7 +74,7 @@ typedef NS_ENUM(NSUInteger, OCCoreConnectionStatus) OCCoreConnectionStatusUnavailable, //!< The server is in maintenance mode and returns with 503 Service Unavailable or /status.php returns "maintenance"=true OCCoreConnectionStatusConnecting, //!< The connection is available and the client is actively trying to connect to the server OCCoreConnectionStatusOnline //!< The server and client device are online -}; +} __attribute__((enum_extensibility(closed))); typedef NS_OPTIONS(NSUInteger, OCCoreConnectionStatusSignal) { @@ -80,7 +84,7 @@ typedef NS_OPTIONS(NSUInteger, OCCoreConnectionStatusSignal) OCCoreConnectionStatusSignalConnected = (1 << 3), //!< The OCConnection has connected successfully OCCoreConnectionStatusSignalBitCount = 4 //!< Number of bits used for status signal -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCCoreConnectionStatusSignalState) { @@ -88,20 +92,20 @@ typedef NS_ENUM(NSUInteger, OCCoreConnectionStatusSignalState) OCCoreConnectionStatusSignalStateTrue, //!< Signal state is true OCCoreConnectionStatusSignalStateForceFalse, //!< Signal state is force false (overriding any true + force true states) OCCoreConnectionStatusSignalStateForceTrue //!< Signal state is force true (overriding any false states) -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCCoreMemoryConfiguration) { OCCoreMemoryConfigurationDefault, //!< Default memory configuration OCCoreMemoryConfigurationMinimum //!< Try using only the minimum amount of memory needed -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger,OCCoreAvailableOfflineCoverage) { OCCoreAvailableOfflineCoverageNone, //!< Item is not targeted by available offline item policy OCCoreAvailableOfflineCoverageIndirect, //!< Item is indirectly targeted by available offline item policy (f.ex. returned for /Photos/Paris.jpg if /Photos/ is available offline OCCoreAvailableOfflineCoverageDirect //!< Item is directly targeted by available offline item policy -}; +} __attribute__((enum_extensibility(closed))); NS_ASSUME_NONNULL_BEGIN @@ -124,6 +128,8 @@ typedef void(^OCCoreSyncIssueResolutionResultHandler)(OCSyncIssueChoice *choice) typedef void(^OCCoreItemListFetchUpdatesCompletionHandler)(NSError * _Nullable error, BOOL didFindChanges); +typedef void(^OCCoreShareJailQueryCustomizer)(OCQuery *query); // Used by OCCore+DataSources.shareJailQueryCustomizer + typedef NSError * _Nullable (^OCCoreImportTransformation)(NSURL *sourceURL); typedef NSString* OCCoreOption NS_TYPED_ENUM; @@ -194,7 +200,7 @@ typedef id OCCoreItemTracking; OCRateLimiter *_syncResetRateLimiter; - NSMutableDictionary *_itemListTasksByPath; + NSMutableDictionary *_itemListTasksByLocationString; NSMutableArray *_queuedItemListTaskUpdateJobs; NSMutableArray *_scheduledItemListTasks; NSMutableSet *_scheduledDirectoryUpdateJobIDs; @@ -214,7 +220,7 @@ typedef id OCCoreItemTracking; BOOL _itemPoliciesAppliedInitially; BOOL _itemPoliciesValid; - NSMutableSet *_availableOfflineFolderPaths; + NSMutableSet *_availableOfflineFolderLocations; NSMutableSet *_availableOfflineIDs; BOOL _availableOfflineCacheValid; NSMapTable *_claimTokensByClaimIdentifier; @@ -236,8 +242,54 @@ typedef id OCCoreItemTracking; NSMutableArray *_warnedCertificates; + NSMutableArray *_drives; + NSMutableDictionary *_lastRootETagsByDriveID; + + OCDataSourceArray *_drivesDataSource; + OCDataSourceArray *_subscribedDrivesDataSource; + OCDataSourceArray *_personalDriveDataSource; + OCDataSourceArray *_shareJailDriveDataSource; + OCDataSourceArray *_projectDrivesDataSource; + + dispatch_source_t _pollingDataSourcesTimer; + NSUInteger _pollingDataSourcesSubscribers; + NSUInteger _pollingDataSourcesOutstandingRequests; + + OCShareQuery *_acceptedCloudSharesQuery; + OCShareQuery *_pendingCloudSharesQuery; + + OCShareQuery *_sharedWithMeQuery; + OCDataSourceComposition *_sharedWithMeDataSource; + NSInteger _sharedWithMeSubscribingDataSources; + OCQuery *_sharesJailQuery; + OCCoreShareJailQueryCustomizer _shareJailQueryCustomizer; + OCDataSourceComposition *_sharedWithMePendingDataSource; + OCDataSourceComposition *_sharedWithMeAcceptedDataSource; + OCDataSourceComposition *_sharedWithMeDeclinedDataSource; + + OCShareQuery *_allSharedByMeQuery; + OCDataSourceArray *_allSharedByMeDataSource; + NSInteger _allSharedByMeSubscribingDataSources; + OCDataSourceArray *_sharedByMeDataSource; + OCDataSourceArray *_sharedByMeGroupedDataSource; + OCDataSourceArray *_sharedByLinkDataSource; + + OCQuery *_favoritesQuery; // provides content for .favoritesDataSource + BOOL _favoritesDataSourceHasSubscribers; + OCDataSourceComposition *_favoritesDataSource; + + BOOL _availableOfflineItemPoliciesDataSourceHasSubscribers; + BOOL _observesOfflineItemPolicies; + OCDataSourceArray *_availableOfflineItemPoliciesDataSource; + + OCQuery *_availableOfflineFilesQuery; + BOOL _availableOfflineFilesDataSourceHasSubscribers; + OCDataSourceComposition *_availableOfflineFilesDataSource; + __weak id _delegate; + NSMutableArray *_shareRoles; + NSNumber *_rootQuotaBytesRemaining; NSNumber *_rootQuotaBytesUsed; NSNumber *_rootQuotaBytesTotal; @@ -309,10 +361,12 @@ typedef id OCCoreItemTracking; - (BOOL)sendError:(nullable NSError *)error issue:(nullable OCIssue *)issue; //!< If YES is returned, the error was sent to the OCCoreDelegate. If NO is returned, the error was not sent to the OCCoreDelegate. #pragma mark - Item lookup and information -- (nullable OCCoreItemTracking)trackItemAtPath:(OCPath)path trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler; //!< Retrieve an item at the specified path from cache and receive updates via the trackingHandler. The returned OCCoreItemTracking object needs to be retained by the caller. Releasing it will end the tracking. This method is a convenience method wrapping cache retrieval, regular and custom queries under the hood. +- (nullable OCCoreItemTracking)trackItemAtLocation:(OCLocation *)location trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler; //!< Retrieve an item at the specified path from cache and receive updates via the trackingHandler. The returned OCCoreItemTracking object needs to be retained by the caller. Releasing it will end the tracking. This method is a convenience method wrapping cache retrieval, regular and custom queries under the hood. +- (nullable OCCoreItemTracking)trackItemWithCondition:(OCQueryCondition *)queryCondition trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler; //!< Like -trackItemAtLocation:trackingHandler:, but backed by a query condition. The returned OCCoreItemTracking object needs to be retained by the caller. Releasing it will end the tracking. -- (nullable OCItem *)cachedItemAtPath:(OCPath)path error:(__autoreleasing NSError * _Nullable * _Nullable)outError; //!< If one exists, returns the item at the specified path from the cache. -- (nullable OCItem *)cachedItemInParentPath:(NSString *)parentPath withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError; //!< If one exists, returns the item with the provided name in the specified parent directory. +- (nullable OCItem *)cachedItemAtLocation:(OCLocation *)location error:(__autoreleasing NSError * _Nullable * _Nullable)outError; //!< If one exists, returns the item at the specified location from the cache. +- (void)cachedItemAtLocation:(OCLocation *)location resultHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item))resultHandler; //!< If one exists, returns the item at the specified location from the cache. +- (nullable OCItem *)cachedItemInParentLocation:(OCLocation *)parentLocation withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError; //!< If one exists, returns the item with the provided name in the specified parent directory. - (nullable OCItem *)cachedItemInParent:(OCItem *)parentItem withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError; //!< If one exists, returns the item with the provided name in the parent directory represented by parentItem. - (nullable NSURL *)localCopyOfItem:(OCItem *)item; //!< Returns the local URL of the item if a local copy exists. @@ -327,6 +381,21 @@ typedef id OCCoreItemTracking; - (nullable NSError *)deleteDirectoryForItem:(OCItem *)item; //!< Deletes the directory for the item - (nullable NSError *)renameDirectoryFromItem:(OCItem *)fromItem forItem:(OCItem *)toItem adjustLocalMetadata:(BOOL)adjustLocalMetadata; //!< Renames the directory of a (placeholder) item to be usable by another item +#pragma mark - Drives +@property(readonly,nonatomic) BOOL useDrives; //!< Returns YES if this account is drive-based (oCIS) rather than driven by a single WebDAV endpoint (OC10) + +- (void)subscribeToDrive:(OCDrive *)drive; //!< Subscribes to a drive. The metadata for subscribed drives are actively kept up-to-date. [TBD] +- (void)unsubscribeFromDrive:(OCDrive *)drive; //!< Unsubscribe from a drive. Metadata + files may be kept around, but are not kept up-to-date. [TBD] + +@property(strong,readonly,nonatomic) NSArray *drives; //!< Returns all known drives. +@property(strong,readonly,nonatomic) NSArray *subscribedDrives; //!< Returns all subscribed drives. +- (nullable OCDrive *)driveWithIdentifier:(OCDriveID)driveID; //!< Returns the OCDrive* instance for an OCDriveID - or nil, if it wasn't found. + +@property(strong,readonly,nullable,nonatomic) OCDrive *personalDrive; + +#pragma mark - App Providers +@property(readonly,nullable,nonatomic) OCAppProvider *appProvider; + #pragma mark - Item usage - (void)registerUsageOfItem:(OCItem *)item completionHandler:(nullable OCCompletionHandler)completionHandler; //!< Registers that the item has been used by the user, updating the locally tracked OCItem.lastUsed date with the current date and time. @@ -346,8 +415,6 @@ typedef id OCCoreItemTracking; @interface OCCore (Thumbnails) + (BOOL)thumbnailSupportedForMIMEType:(NSString *)mimeType; -- (nullable NSProgress *)retrieveThumbnailFor:(OCItem *)item maximumSize:(CGSize)size scale:(CGFloat)scale retrieveHandler:(OCCoreThumbnailRetrieveHandler)retrieveHandler; -- (nullable NSProgress *)retrieveThumbnailFor:(OCItem *)item maximumSize:(CGSize)size scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity retrieveHandler:(OCCoreThumbnailRetrieveHandler)retrieveHandler; @end @interface OCCore (Sharing) @@ -395,6 +462,9 @@ typedef id OCCoreItemTracking; - (nullable NSProgress *)retrievePrivateLinkForItem:(OCItem *)item completionHandler:(void(^)(NSError * _Nullable error, NSURL * _Nullable privateLink))completionHandler; //!< Returns the private link for the item - (nullable NSProgress *)retrieveItemForPrivateLink:(NSURL *)privateLink completionHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item))completionHandler; //!< Returns the item for the private link +- (nullable NSArray *)availableShareRolesForType:(OCShareType)type location:(OCLocation *)location; //!< Returns the share roles available for this location and type. Returns nil if none are available. +- (nullable OCShareRole *)matchingShareRoleForShare:(OCShare *)share; //!< Returns the share role matching the provided item and share's permissions and location. Returns nil if none matches. + @end @interface OCCore (AvailableOffline) diff --git a/ownCloudSDK/Core/OCCore.m b/ownCloudSDK/Core/OCCore.m index fe2caffd..10eba469 100644 --- a/ownCloudSDK/Core/OCCore.m +++ b/ownCloudSDK/Core/OCCore.m @@ -56,6 +56,20 @@ #import "OCProcessManager.h" #import "OCBookmark+DBMigration.h" #import "OCMeasurement.h" +#import "OCResourceManager.h" +#import "OCResourceSourceAvatars.h" +#import "OCResourceSourceAvatarPlaceholders.h" +#import "OCResourceSourceItemThumbnails.h" +#import "OCResourceSourceItemLocalThumbnails.h" +#import "OCResourceSourceDriveItems.h" +#import "OCResourceSourceURLItems.h" +#import "OCConnection+GraphAPI.h" +#import "NSArray+OCFiltering.h" +#import "OCCore+DataSources.h" +#import "OCDataSourceKVO.h" +#import "OCVault+Internal.h" +#import "OCLocale+SystemLanguage.h" +#import "OCCore+DataSources.h" @interface OCCore () { @@ -198,12 +212,11 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCClassSettingsMetadataKeyCategory : @"Connection", }, - // Privacy OCCoreAddAcceptLanguageHeader : @{ OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, OCClassSettingsMetadataKeyDescription : @"Add an `Accept-Language` HTTP header using the preferred languages set on the device.", OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, - OCClassSettingsMetadataKeyCategory : @"Privacy" + OCClassSettingsMetadataKeyCategory : @"Connection" }, }); } @@ -228,6 +241,8 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark OCLogError(@"Error creating pthread key: %d", pthreadKeyError); } + OCLog(@"Priorities: default %f low %f high %f", NSURLSessionTaskPriorityDefault, NSURLSessionTaskPriorityLow, NSURLSessionTaskPriorityHigh); + _runIdentifier = [NSUUID new]; _bookmark = bookmark; @@ -247,12 +262,14 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark _unsolvedIssueSignatures = [NSMutableSet new]; _rejectedIssueSignatures = [NSMutableSet new]; + _shareRoles = [NSMutableArray new]; + _vault = [[OCVault alloc] initWithBookmark:bookmark]; _queries = [NSMutableArray new]; _shareQueries = [NSMutableArray new]; - _itemListTasksByPath = [NSMutableDictionary new]; + _itemListTasksByLocationString = [NSMutableDictionary new]; _queuedItemListTaskUpdateJobs = [NSMutableArray new]; _scheduledItemListTasks = [NSMutableArray new]; _scheduledDirectoryUpdateJobIDs = [NSMutableSet new]; @@ -272,13 +289,35 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark _progressByLocalID = [NSMutableDictionary new]; + _drives = [NSMutableArray new]; + _lastRootETagsByDriveID = [NSMutableDictionary new]; + + _drivesDataSource = [[OCDataSourceKVO alloc] initWithObject:_vault keyPath:@"activeDrives" versionedItemUpdateHandler:nil]; + _subscribedDrivesDataSource = [[OCDataSourceKVO alloc] initWithObject:_vault keyPath:@"subscribedDrives" versionedItemUpdateHandler:nil]; + + _projectDrivesDataSource = [[OCDataSourceKVO alloc] initWithObject:_vault keyPath:@"subscribedDrives" versionedItemUpdateHandler:^NSArray> * _Nullable(NSObject * _Nonnull object, NSString * _Nonnull keyPath, NSArray * _Nullable activeDrives) { + return ([activeDrives filteredArrayUsingBlock:^BOOL(OCDrive * _Nonnull drive, BOOL * _Nonnull stop) { + return ([drive.type isEqual:OCDriveTypeProject]); + }]); + }]; + _personalDriveDataSource = [[OCDataSourceKVO alloc] initWithObject:_vault keyPath:@"activeDrives" versionedItemUpdateHandler:^NSArray> * _Nullable(NSObject * _Nonnull object, NSString * _Nonnull keyPath, NSArray * _Nullable activeDrives) { + return ([activeDrives filteredArrayUsingBlock:^BOOL(OCDrive * _Nonnull drive, BOOL * _Nonnull stop) { + return ([drive.type isEqual:OCDriveTypePersonal]); + }]); + }]; + _shareJailDriveDataSource = [[OCDataSourceKVO alloc] initWithObject:_vault keyPath:@"activeDrives" versionedItemUpdateHandler:^NSArray> * _Nullable(NSObject * _Nonnull object, NSString * _Nonnull keyPath, NSArray * _Nullable activeDrives) { + return ([activeDrives filteredArrayUsingBlock:^BOOL(OCDrive * _Nonnull drive, BOOL * _Nonnull stop) { + return ([drive.type isEqual:OCDriveTypeVirtual] && [drive.identifier isEqual:OCDriveIDSharesJail]); + }]); + }]; + _activityManager = [[OCActivityManager alloc] initWithUpdateNotificationName:[@"OCCore.ActivityUpdate." stringByAppendingString:_bookmark.uuid.UUIDString]]; _publishedActivitySyncRecordIDs = [NSMutableSet new]; _itemPolicies = [NSMutableArray new]; _itemPolicyProcessors = [NSMutableArray new]; - _availableOfflineFolderPaths = [NSMutableSet new]; + _availableOfflineFolderLocations = [NSMutableSet new]; _availableOfflineIDs = [NSMutableSet new]; _claimTokensByClaimIdentifier = [NSMapTable strongToWeakObjectsMapTable]; @@ -318,16 +357,11 @@ - (instancetype)initWithBookmark:(OCBookmark *)bookmark if ([((NSNumber *)[self classSettingForOCClassSettingsKey:OCCoreAddAcceptLanguageHeader]) boolValue]) { - NSArray *preferredLocalizations = [NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations] forPreferences:nil]; + NSString *acceptLanguage; - if (preferredLocalizations.count > 0) + if ((acceptLanguage = OCLocale.sharedLocale.acceptLanguageString) != nil) { - NSString *acceptLanguage; - - if ((acceptLanguage = [[preferredLocalizations componentsJoinedByString:@", "] lowercaseString]) != nil) - { - _connection.staticHeaderFields = @{ @"Accept-Language" : acceptLanguage }; - } + _connection.staticHeaderFields = @{ @"Accept-Language" : acceptLanguage }; } } @@ -382,6 +416,10 @@ - (void)dealloc OCLogTagName runIDTag = OCLogTagTypedID(@"RunID", _runIdentifier); NSArray *deallocTags = (runIDTag != nil) ? @[@"DEALLOC", runIDTag] : @[@"DEALLOC"]; + [self unsubscribeFromPollingDatasourcesTimer:OCCoreDataSourcePollTypeAll withForcedStop:YES]; + [self _updateSharedWithMeQueryForceStop:YES]; + [self _updateAllSharedByMeQueryForceStop:YES]; + [self stopIPCObserveration]; [self removeSignalProviders]; @@ -467,6 +505,14 @@ - (void)startWithCompletionHandler:(nullable OCCompletionHandler)completionHandl }); } + // Get latest drive list + if (startError == nil) + { + [self initializeWithDrives]; + + self->_connection.drives = self.vault.activeDrives; + } + // Proceed with connecting - or stop if (startError == nil) { @@ -488,6 +534,14 @@ - (void)startWithCompletionHandler:(nullable OCCompletionHandler)completionHandl // Register as message response handler [self.messageQueue addResponseHandler:self]; + + // Register resource sources + [self.vault.resourceManager addSource:[[OCResourceSourceAvatarPlaceholders alloc] initWithCore:self]]; + [self.vault.resourceManager addSource:[[OCResourceSourceAvatars alloc] initWithCore:self]]; + [self.vault.resourceManager addSource:[[OCResourceSourceItemThumbnails alloc] initWithCore:self]]; + [self.vault.resourceManager addSource:[[OCResourceSourceItemLocalThumbnails alloc] initWithCore:self]]; + [self.vault.resourceManager addSource:[[OCResourceSourceDriveItems alloc] initWithCore:self]]; + [self.vault.resourceManager addSource:[[OCResourceSourceURLItems alloc] initWithCore:self]]; } else { @@ -556,6 +610,9 @@ - (void)stopWithCompletionHandler:(nullable OCCompletionHandler)completionHandle } } + // Shutdown drives + [weakSelf shutdownWithDrives]; + // Tear down item policies [weakSelf teardownItemPolicies]; @@ -686,6 +743,27 @@ - (void)__attemptConnect self->_preferredChecksumAlgorithm = preferredUploadChecksumType; } } + + // If app provider is available and enabled + OCAppProvider *latestSupportedAppProvider = self.connection.capabilities.latestSupportedAppProvider; + + if ((latestSupportedAppProvider != nil) && latestSupportedAppProvider.enabled) + { + [self.connection retrieveAppProviderListWithCompletionHandler:^(NSError * _Nullable error, OCAppProvider * _Nullable appProvider) { + OCLogDebug(@"AppProviderList: error=%@, appProvider=%@", error, appProvider); + + if (error == nil) + { + [self willChangeValueForKey:@"appProvider"]; + self->_appProvider = appProvider; + [self didChangeValueForKey:@"appProvider"]; + } + else + { + OCLogWarning(@"Error retrieving app provider list: %@", error); + } + }]; + } } [self queueBlock:^{ @@ -731,17 +809,17 @@ - (void)_startItemListTaskForQuery:(OCQuery *)query query.state = OCQueryStateStarted; // Start task - if (query.queryPath != nil) + if (query.queryLocation != nil) { // Start item list task for queried directory - [self scheduleItemListTaskForPath:query.queryPath forDirectoryUpdateJob:nil withMeasurement:[query extractedMeasurement]]; + [self scheduleItemListTaskForLocation:query.queryLocation forDirectoryUpdateJob:nil withMeasurement:[query extractedMeasurement]]; } else { if (query.queryItem.path != nil) { // Start item list task for parent directory of queried item - [self scheduleItemListTaskForPath:[query.queryItem.path parentPath] forDirectoryUpdateJob:nil withMeasurement:[query extractedMeasurement]]; + [self scheduleItemListTaskForLocation:query.queryItem.location.parentLocation forDirectoryUpdateJob:nil withMeasurement:[query extractedMeasurement]]; } } }]; @@ -819,6 +897,28 @@ - (void)startQuery:(OCCoreQuery *)coreQuery OCMeasureEvent(coreQuery, @"query", @"Starting"); + if ((query != nil) && self.useDrives) + { + // Adapt query location from; legacy root to personal folder + OCLocation *queryLocation; + + if ((queryLocation = query.queryLocation) != nil) + { + // No drive ID? => legacy/OC10 location + if (queryLocation.driveID == nil) + { + // Find personal drive + OCDrive *personalDrive; + + if ((personalDrive = self.personalDrive) != nil) + { + // Set personal drive ID from personal drive + queryLocation.driveID = personalDrive.identifier; + } + } + } + } + if (query != nil) { // Add query to list of queries @@ -916,7 +1016,7 @@ - (OCDatabase *)database - (void)retrieveLatestDatabaseVersionOfItem:(OCItem *)item completionHandler:(void(^)(NSError *error, OCItem *requestedItem, OCItem *databaseItem))completionHandler { - [self.vault.database retrieveCacheItemsAtPath:item.path itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.vault.database retrieveCacheItemsAtLocation:item.location itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { completionHandler(error, item, items.firstObject); }]; } @@ -932,6 +1032,8 @@ - (void)setMemoryConfiguration:(OCCoreMemoryConfiguration)memoryConfiguration { _memoryConfiguration = memoryConfiguration; + self.vault.resourceManager.memoryConfiguration = memoryConfiguration; + switch (_memoryConfiguration) { case OCCoreMemoryConfigurationDefault: @@ -1106,7 +1208,7 @@ - (void)_replayChangesSinceSyncAnchor:(OCSyncAnchor)fromSyncAnchor [self performUpdatesForAddedItems:nil removedItems:removedItems updatedItems:addedOrUpdatedItems - refreshPaths:nil + refreshLocations:nil newSyncAnchor:syncAnchor beforeQueryUpdates:^(dispatch_block_t _Nonnull completionHandler) { // Find items that moved to a different path @@ -1467,21 +1569,29 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N #pragma mark - Item lookup and information -- (OCCoreItemTracking)trackItemAtPath:(OCPath)inPath trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler +- (OCCoreItemTracking)trackItemAtLocation:(OCLocation *)location trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler { NSObject *trackingObject = [NSObject new]; __weak NSObject *weakTrackingObject = trackingObject; __weak OCCore *weakSelf = self; // Detect unnormalized path - if ([inPath isUnnormalizedPath]) + if ([location.path isUnnormalizedPath]) { trackingHandler(OCError(OCErrorUnnormalizedPath), nil, YES); return (nil); } + // Detect unresolvable paths + if ((location.driveID == nil) && self.useDrives) // Legacy WebDAV is not available in drive-based accounts + { + trackingHandler(OCError(OCErrorItemNotFound), nil, YES); + return (nil); + } + [self queueBlock:^{ - OCPath path = inPath; + OCPath path = location.path; + OCDriveID driveID = location.driveID; NSError *error = nil; OCItem *item = nil; OCQuery *query = nil; @@ -1500,13 +1610,13 @@ - (OCCoreItemTracking)trackItemAtPath:(OCPath)inPath trackingHandler:(void(^)(NS return; } - if ((item = [core cachedItemAtPath:path error:&error]) == nil) + if ((item = [core cachedItemAtLocation:location error:&error]) == nil) { // No item for this path found in cache if (path.itemTypeByPath == OCItemTypeFile) { // This path indicates a file - but maybe that's what's wanted: retry by looking for a folder at that location instead. - if ((item = [core cachedItemAtPath:path.normalizedDirectoryPath error:&error]) != nil) + if ((item = [core cachedItemAtLocation:[[OCLocation alloc] initWithBookmarkUUID:core.bookmark.uuid driveID:driveID path:path.normalizedDirectoryPath] error:&error]) != nil) { path = path.normalizedDirectoryPath; } @@ -1525,7 +1635,20 @@ - (OCCoreItemTracking)trackItemAtPath:(OCPath)inPath trackingHandler:(void(^)(NS } // Start custom query to track changes (won't touch network, but will provide updates) - query = [OCQuery queryWithCondition:[OCQueryCondition where:OCItemPropertyNamePath isEqualTo:path] inputFilter:nil]; + OCQueryCondition *queryCondition = nil; + if (driveID != nil) + { + queryCondition = [OCQueryCondition require:@[ + [OCQueryCondition where:OCItemPropertyNameDriveID isEqualTo:driveID], + [OCQueryCondition where:OCItemPropertyNamePath isEqualTo:path] + ]]; + } + else + { + queryCondition = [OCQueryCondition where:OCItemPropertyNamePath isEqualTo:path]; + } + + query = [OCQuery queryWithCondition:queryCondition inputFilter:nil]; query.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { if (weakTrackingObject != nil) { @@ -1542,7 +1665,7 @@ - (OCCoreItemTracking)trackItemAtPath:(OCPath)inPath trackingHandler:(void(^)(NS // Item not in cache - create full-fledged query __block BOOL lastSentItemWasNil = YES; - query = [OCQuery queryForPath:path]; + query = [OCQuery queryForLocation:[[OCLocation alloc] initWithDriveID:driveID path:path]]; query.includeRootItem = YES; NSString *pathAsDirectory = path.normalizedDirectoryPath; @@ -1611,14 +1734,80 @@ - (OCCoreItemTracking)trackItemAtPath:(OCPath)inPath trackingHandler:(void(^)(NS return (trackingObject); } -- (nullable OCItem *)cachedItemAtPath:(OCPath)path error:(__autoreleasing NSError * _Nullable * _Nullable)outError +- (OCCoreItemTracking)trackItemWithCondition:(OCQueryCondition *)queryCondition trackingHandler:(void(^)(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial))trackingHandler +{ + NSObject *trackingObject = [NSObject new]; + __weak NSObject *weakTrackingObject = trackingObject; + __weak OCCore *weakSelf = self; + + // Detect unnormalized path + if (queryCondition == nil) + { + trackingHandler(OCError(OCErrorInsufficientParameters), nil, YES); + return (nil); + } + + [self queueBlock:^{ + NSError *error = nil; + OCQuery *query = nil; + NSObject *trackingObject = weakTrackingObject; + __block BOOL isFirstInvocation = YES; + OCCore *core = weakSelf; + + if (trackingObject == nil) + { + return; + } + + if (core == nil) + { + trackingHandler(OCError(OCErrorInternal), nil, YES); + return; + } + + query = [OCQuery queryWithCondition:queryCondition inputFilter:nil]; + query.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { + if (weakTrackingObject != nil) + { + if ((query.state == OCQueryStateContentsFromCache) || (query.state == OCQueryStateIdle)) + { + trackingHandler(nil, query.queryResults.firstObject, isFirstInvocation); + isFirstInvocation = NO; + } + } + }; + + if (query != nil) + { + __weak OCCore *weakCore = core; + __weak OCQuery *weakQuery = query; + + [core startQuery:query]; + + // Stop query as soon as trackingObject is deallocated + [OCDeallocAction addAction:^{ + OCCore *core = weakCore; + OCQuery *query = weakQuery; + + if ((core != nil) && (query != nil)) + { + [core stopQuery:query]; + } + } forDeallocationOfObject:trackingObject]; + } + }]; + + return (trackingObject); +} + +- (nullable OCItem *)cachedItemAtLocation:(OCLocation *)location error:(__autoreleasing NSError * _Nullable * _Nullable)outError { __block OCItem *cachedItem = nil; - if (path != nil) + if (location.path != nil) { OCSyncExec(retrieveCachedItem, { - [self.vault.database retrieveCacheItemsAtPath:path itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.vault.database retrieveCacheItemsAtLocation:location itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { cachedItem = items.firstObject; if (outError != NULL) @@ -1641,21 +1830,35 @@ - (nullable OCItem *)cachedItemAtPath:(OCPath)path error:(__autoreleasing NSErro return (cachedItem); } -- (nullable OCItem *)cachedItemInParentPath:(NSString *)parentPath withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError +- (void)cachedItemAtLocation:(OCLocation *)location resultHandler:(void (^)(NSError * _Nullable, OCItem * _Nullable))resultHandler { - NSString *path = [parentPath stringByAppendingPathComponent:name]; + if (location.path != nil) + { + [self.vault.database retrieveCacheItemsAtLocation:location itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + resultHandler(error, items.firstObject); + }]; + } + else + { + resultHandler(OCError(OCErrorInsufficientParameters), nil); + } +} + +- (nullable OCItem *)cachedItemInParentLocation:(OCLocation *)parentLocation withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError +{ + NSString *path = [parentLocation.path stringByAppendingPathComponent:name]; if (isDirectory) { path = [path normalizedDirectoryPath]; } - return ([self cachedItemAtPath:path error:outError]); + return ([self cachedItemAtLocation:[[OCLocation alloc] initWithBookmarkUUID:_bookmark.uuid driveID:parentLocation.driveID path:path] error:outError]); } - (nullable OCItem *)cachedItemInParent:(OCItem *)parentItem withName:(NSString *)name isDirectory:(BOOL)isDirectory error:(__autoreleasing NSError * _Nullable * _Nullable)outError { - return ([self cachedItemInParentPath:parentItem.path withName:name isDirectory:isDirectory error:outError]); + return ([self cachedItemInParentLocation:parentItem.location withName:name isDirectory:isDirectory error:outError]); } - (NSURL *)localCopyOfItem:(OCItem *)item @@ -1673,7 +1876,7 @@ - (NSURL *)localURLForItem:(OCItem *)item { if (item.localRelativePath != nil) { - return ([self.vault.filesRootURL URLByAppendingPathComponent:item.localRelativePath isDirectory:NO]); + return ([[self.vault localDriveRootURLForDriveID:item.driveID] URLByAppendingPathComponent:item.localRelativePath isDirectory:NO]); } return ([self.vault localURLForItem:item]); @@ -1891,11 +2094,10 @@ - (void)handleEvent:(OCEvent *)event sender:(id)sender // Handle by event type switch (event.eventType) { - case OCEventTypeRetrieveThumbnail: { - queueBlock = ^{ - [self _handleRetrieveThumbnailEvent:event sender:sender]; - }; - } + case OCEventTypeRetrieveThumbnail: + // Legacy thumbnail event (pre-OCResourceManager era) + OCLogWarning(@"Dropping legacy thumbnail event: %@", event); + completionHandler(); break; case OCEventTypeRetrieveItemList: { @@ -1954,7 +2156,7 @@ - (void)registerUsageOfItem:(OCItem *)item completionHandler:(nullable OCComplet { updatedItem.lastUsed = [NSDate new]; - [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ updatedItem ] refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:@[ updatedItem ] refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } [self endActivity:@"Registering item usage"]; @@ -1966,6 +2168,143 @@ - (void)registerUsageOfItem:(OCItem *)item completionHandler:(nullable OCComplet }]; } +#pragma mark - Drives +- (BOOL)useDrives +{ + return (_connection.useDriveAPI); +} + +- (void)initializeWithDrives +{ + @synchronized(_lastRootETagsByDriveID) + { + for (OCDrive *drive in self.vault.activeDrives) + { + _lastRootETagsByDriveID[drive.identifier] = drive.rootETag; + } + + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleSubscribedDrivesUpdate:) name:OCVaultSubscribedDrivesListChanged object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleDetachedDrivesUpdate:) name:OCVaultDetachedDrivesListChanged object:nil]; + } +} + +- (void)shutdownWithDrives +{ + [NSNotificationCenter.defaultCenter removeObserver:self name:OCVaultSubscribedDrivesListChanged object:nil]; + [NSNotificationCenter.defaultCenter removeObserver:self name:OCVaultDetachedDrivesListChanged object:nil]; +} + +- (void)subscribeToDrive:(OCDrive *)drive +{ + [self.vault subscribeToDrives:@[ drive ]]; +} + +- (void)unsubscribeFromDrive:(OCDrive *)drive +{ + [self.vault unsubscribeFromDrives:@[ drive ]]; +} + +- (NSArray *)drives +{ + return (self.vault.activeDrives); +} + +- (NSArray *)subscribedDrives +{ + return (self.vault.subscribedDrives); +} + +- (NSArray *)detachedDrives +{ + return (self.vault.detachedDrives); +} + +- (OCDrive *)driveWithIdentifier:(OCDriveID)driveID +{ + if (driveID == nil) { return (nil); } + + return ([self.vault driveWithIdentifier:driveID]); +} + +- (OCDrive *)personalDrive +{ + return ([self.drives firstObjectMatching:^BOOL(OCDrive * _Nonnull drive) { + return ([drive.specialType isEqual:OCDriveSpecialTypePersonal]); + }]); +} + +- (void)_handleDetachedDrivesUpdate:(NSNotification *)notification +{ + if (notification.object != _vault) + { + // Only react to notifications from our vault + return; + } + + [self beginActivity:@"Drive detach handling"]; + + [self queueBlock:^{ + [self incrementSyncAnchorWithProtectedBlock:^NSError *(OCSyncAnchor previousSyncAnchor, OCSyncAnchor newSyncAnchor) { + NSArray *detachedDrives = self.vault.detachedDrives; + __block NSError *returnError = nil; + + for (OCDrive *detachedDrive in detachedDrives) + { + OCDriveID driveID = detachedDrive.identifier; + + if ((detachedDrive.detachedState == OCDriveDetachedStateNew) && (driveID != nil)) + { + [self.database removeCacheItemsWithDriveID:driveID syncAnchor:newSyncAnchor completionHandler:^(OCDatabase *db, NSError *error) { + returnError = error; + }]; + + if (returnError != nil) + { + return (returnError); + } + + [self.vault changeDetachedState:OCDriveDetachedStateItemsRemoved forDriveID:driveID]; + } + } + + return ((NSError *)returnError); + } completionHandler:^(NSError *error, OCSyncAnchor previousSyncAnchor, OCSyncAnchor newSyncAnchor) { + [self endActivity:@"Drive detach handling"]; + }]; + }]; +} + +- (void)_handleSubscribedDrivesUpdate:(NSNotification *)notification +{ + if (notification.object != _vault) + { + // Only react to notifications from our vault + return; + } + + [self beginActivity:@"Subscribed drives update handling"]; + + [self queueBlock:^{ + NSArray *queries; + + @synchronized(self->_queries) + { + queries = [self->_queries copy]; + } + + for (OCQuery *query in queries) + { + if (query.isCustom) + { + // Reload all custom queries as drives are added/removed + [self reloadQuery:query]; + } + } + + [self endActivity:@"Subscribed drives update handling"]; + }]; +} + #pragma mark - Indicating activity requiring the core - (void)performInRunningCore:(void(^)(dispatch_block_t completionHandler))activityBlock withDescription:(NSString *)description { diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.h b/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.h new file mode 100644 index 00000000..3d7fa4a6 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.h @@ -0,0 +1,29 @@ +// +// OCResourceRequestAvatar.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceRequestAvatar : OCResourceRequest + ++ (instancetype)requestAvatarFor:(OCUser *)user maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.m b/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.m new file mode 100644 index 00000000..0e224bdc --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceRequestAvatar.m @@ -0,0 +1,45 @@ +// +// OCResourceRequestAvatar.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceRequestAvatar.h" +#import "OCResource.h" + +@implementation OCResourceRequestAvatar + ++ (instancetype)requestAvatarFor:(OCUser *)user maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(OCResourceRequestChangeHandler)changeHandler +{ + OCResourceRequestAvatar *request; + + if (scale == 0) + { + scale = UIScreen.mainScreen.scale; + } + + request = [[OCResourceRequestAvatar alloc] initWithType:OCResourceTypeAvatar identifier:user.userIdentifier]; + request.reference = user; + + request.maxPointSize = requestedMaximumSizeInPoints; + request.scale = scale; + + request.waitForConnectivity = waitForConnectivity; + request.changeHandler = changeHandler; + + return (request); +} + +@end diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.h b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.h new file mode 100644 index 00000000..03701b40 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceAvatarPlaceholders.h +// ownCloudSDK +// +// Created by Felix Schwarz on 19.01.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 "OCResourceSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceAvatarPlaceholders : OCResourceSource + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierAvatarPlaceholder; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.m b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.m new file mode 100644 index 00000000..6b18d1c7 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatarPlaceholders.m @@ -0,0 +1,72 @@ +// +// OCResourceSourceAvatarPlaceholders.m +// ownCloudSDK +// +// Created by Felix Schwarz on 19.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCResourceSourceAvatarPlaceholders.h" +#import "OCResourceRequestAvatar.h" +#import "OCCore.h" +#import "OCMacros.h" +#import "OCResourceTextPlaceholder.h" +#import "OCAvatar.h" +#import "OCConnection.h" +#import "NSError+OCError.h" + +@implementation OCResourceSourceAvatarPlaceholders + +- (OCResourceType)type +{ + return (OCResourceTypeAvatar); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierAvatarPlaceholder); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityInstant); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request isKindOfClass:OCResourceRequestAvatar.class] && [request.reference isKindOfClass:OCUser.class]) + { + if (OCTypedCast(request.reference, OCUser) != nil) + { + return (OCResourceQualityFallback); + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + OCResourceRequestAvatar *avatarRequest; + OCUser *user; + + if (((avatarRequest = OCTypedCast(request, OCResourceRequestAvatar)) != nil) && + ((user = OCTypedCast(avatarRequest.reference, OCUser)) != nil)) + { + OCResourceTextPlaceholder *resource = [[OCResourceTextPlaceholder alloc] initWithRequest:request]; + + resource.quality = OCResourceQualityFallback; + resource.text = user.localizedInitials; + resource.timestamp = NSDate.date; + + resultHandler(nil, resource); + + return; + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierAvatarPlaceholder = @"core.avatar.placeholder"; diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.h b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.h new file mode 100644 index 00000000..580732fc --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceAvatars.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceAvatars : OCResourceSource + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierAvatar; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.m b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.m new file mode 100644 index 00000000..7d50eecd --- /dev/null +++ b/ownCloudSDK/Core/Resources/Avatars/OCResourceSourceAvatars.m @@ -0,0 +1,166 @@ +// +// OCResourceSourceAvatars.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSourceAvatars.h" +#import "OCResourceRequestAvatar.h" +#import "OCCore.h" +#import "OCMacros.h" +#import "OCResourceImage.h" +#import "OCAvatar.h" +#import "OCConnection.h" +#import "NSError+OCError.h" + +@interface OCResourceSourceAvatars () +{ + NSMutableSet *_forceRefreshedAvatars; +} +@end + +@implementation OCResourceSourceAvatars + +- (OCResourceType)type +{ + return (OCResourceTypeAvatar); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierAvatar); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityRemote); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request isKindOfClass:OCResourceRequestAvatar.class] && [request.reference isKindOfClass:OCUser.class]) + { + OCUser *user; + + if ((user = OCTypedCast(request.reference, OCUser)) != nil) + { + OCResourceImage *avatarImage; + + if ((avatarImage = OCTypedCast(request.resource, OCResourceImage)) != nil) + { + OCUserIdentifier userIdentifier; + + if ((userIdentifier = user.userIdentifier) != nil) + { + if (_forceRefreshedAvatars == nil) + { + _forceRefreshedAvatars = [NSMutableSet new]; + } + + if (![_forceRefreshedAvatars containsObject:userIdentifier]) + { + // Return fake high quality to force refresh once per session + [_forceRefreshedAvatars addObject:userIdentifier]; + + return (OCResourceQualityHigh); + } + + if ((avatarImage.timestamp != nil) && + (-avatarImage.timestamp.timeIntervalSinceNow > (3600.0 * 12.0))) + { + // Avatar is more than 12 hours old -> force refresh by returning a fake high quality + + return (OCResourceQualityHigh); + } + } + } + + return (OCResourceQualityNormal); + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + OCResourceRequestAvatar *avatarRequest; + OCUser *user; + + if (((avatarRequest = OCTypedCast(request, OCResourceRequestAvatar)) != nil) && + ((user = OCTypedCast(avatarRequest.reference, OCUser)) != nil)) + { + OCConnection *connection; + + if ((connection = self.core.connection) != nil) + { + // NSString *specID = item.thumbnailSpecID; + NSProgress *progress = nil; + OCResourceImage *avatarImageResource = [request.resource.type isEqual:OCResourceTypeAvatar] ? OCTypedCast(request.resource, OCResourceImage) : nil; + OCFileETag existingETag = avatarImageResource.version; + + progress = [connection retrieveAvatarForUser:user existingETag:existingETag withSize:avatarRequest.maxPixelSize completionHandler:^(NSError * _Nullable error, BOOL unchanged, OCAvatar * _Nullable avatar) { + if (error != nil) + { + if ([error isOCErrorWithCode:OCErrorResourceDoesNotExist] && (existingETag == nil)) + { + // Clear OCErrorResourceDoesNotExist if there was no previous version of the resource + error = nil; + } + + resultHandler(error, nil); + } + else if (unchanged && (existingETag != nil)) + { + // Return a nil error and update existing avatar timestamp if the avatar has not changed + avatarImageResource.timestamp = [NSDate new]; + + resultHandler(nil, avatarImageResource); + } + else + { + OCResourceImage *resource = [[OCResourceImage alloc] initWithRequest:request]; + + // Map avatar to corresponding resource fields + resource.identifier = avatar.userIdentifier; + resource.version = avatar.eTag; + resource.quality = OCResourceQualityNormal; + + // Transfer avatar properties / data to resource + resource.maxPixelSize = avatar.maxPixelSize; + resource.data = avatar.data; + + resource.timestamp = avatar.timestamp; + + resource.image = avatar; + + resultHandler(nil, resource); + } + }]; + + request.job.cancellationHandler = ^{ + [progress cancel]; + }; + + return; + } + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierAvatar = @"core.avatar"; diff --git a/ownCloudSDK/Core/Resources/Manager/OCResourceManager.h b/ownCloudSDK/Core/Resources/Manager/OCResourceManager.h new file mode 100644 index 00000000..28cb49b4 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Manager/OCResourceManager.h @@ -0,0 +1,62 @@ +// +// OCResourceManager.h +// ownCloudSDK +// +// Created by Felix Schwarz on 21.12.21. +// Copyright © 2021 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 "OCResourceTypes.h" +#import "OCResourceRequest.h" +#import "OCResourceSource.h" +#import "OCResource.h" +#import "OCCache.h" +#import "OCCore.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^OCResourceStoreCompletionHandler)(NSError * _Nullable error); +typedef void(^OCResourceRetrieveCompletionHandler)(NSError * _Nullable error, OCResource * _Nullable resource); + +@protocol OCResourceStorage +- (void)retrieveResourceForRequest:(OCResourceRequest *)request completionHandler:(OCResourceRetrieveCompletionHandler)completionHandler; +- (void)storeResource:(OCResource *)resource completionHandler:(OCResourceStoreCompletionHandler)completionHandler; +- (void)removeResourceOfType:(OCResourceType)type identifier:(OCResourceIdentifier)identifier completionHandler:(OCResourceStoreCompletionHandler)completionHandler; +@end + +@interface OCResourceManager : NSObject + +@property(weak,nullable) id storage; +@property(weak,nullable) OCCore *core; + +@property(assign,nonatomic) OCCoreMemoryConfiguration memoryConfiguration; + +// @property(assign) NSUInteger maximumConcurrentJobs; //!< Maximum number of jobs to work on in parallel. A value of 0 indicates no limit. + +- (instancetype)initWithStorage:(nullable id)storage; + +#pragma mark - Sources +- (void)addSource:(OCResourceSource *)source; +- (void)removeSource:(OCResourceSource *)source; + +#pragma mark - Requests +- (void)startRequest:(OCResourceRequest *)request; +- (void)stopRequest:(OCResourceRequest *)request; + +#pragma mark - Scheduler +- (void)setNeedsScheduling; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Manager/OCResourceManager.m b/ownCloudSDK/Core/Resources/Manager/OCResourceManager.m new file mode 100644 index 00000000..7c681073 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Manager/OCResourceManager.m @@ -0,0 +1,565 @@ +// +// OCResourceManager.m +// ownCloudSDK +// +// Created by Felix Schwarz on 21.12.21. +// Copyright © 2021 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 "OCResourceManager.h" +#import "OCCache.h" +#import "OCResourceManagerJob.h" +#import "OCResourceSourceStorage.h" +#import "OCLogger.h" +#import "NSError+OCError.h" +#import "NSError+OCHTTPStatus.h" + +@interface OCResourceManager () +{ + OCCache *_cache; + + NSMutableDictionary *> *_sourcesByType; + NSMutableArray *_jobs; + + dispatch_queue_t _queue; + BOOL _needsScheduling; +} +@end + +@implementation OCResourceManager + +- (instancetype)initWithStorage:(id)storage +{ + if ((self = [super init]) != nil) + { + _storage = storage; + _sourcesByType = [NSMutableDictionary new]; + _jobs = [NSMutableArray new]; + + _cache = [[OCCache alloc] init]; + + _queue = dispatch_queue_create("OCResourceManager", DISPATCH_QUEUE_SERIAL); + + [self addSource:[OCResourceSourceStorage new]]; + } + + return (self); +} + +- (void)setMemoryConfiguration:(OCCoreMemoryConfiguration)memoryConfiguration +{ + _memoryConfiguration = memoryConfiguration; + + switch (_memoryConfiguration) + { + case OCCoreMemoryConfigurationDefault: + _cache.countLimit = OCCacheLimitNone; + break; + + case OCCoreMemoryConfigurationMinimum: + _cache.countLimit = 1; + break; + } +} + +#pragma mark - Sources +- (void)addSource:(OCResourceSource *)source +{ + @synchronized(_sourcesByType) + { + NSMutableArray *sources; + + if ((sources = _sourcesByType[source.type]) == nil) + { + sources = [NSMutableArray new]; + _sourcesByType[source.type] = sources; + + // For any sources not of type any, add all sources for any upon initialization + if (![source.type isEqual:OCResourceTypeAny]) + { + NSMutableArray *anySources; + + if ((anySources = _sourcesByType[OCResourceTypeAny]) != nil) + { + [sources addObjectsFromArray:anySources]; + } + } + } + else + { + // Check if source with same identifier already exists + for (OCResourceSource *existingSource in sources) + { + if ([existingSource.identifier isEqual:source.identifier]) + { + // If it does, log warning and return + OCLogWarning(@"Did not add resource source %@ because another instance with the same identifier (%@) already exists: %@", source, existingSource.identifier, existingSource); + return; + } + } + } + + if ([source.type isEqual:OCResourceTypeAny]) + { + // Add sources of type any to all source types + for (OCResourceType type in _sourcesByType) + { + if (![type isEqual:OCResourceTypeAny]) + { + [_sourcesByType[type] addObject:source]; + [self _sortSourcesForType:type]; + } + } + } + + source.manager = self; + [sources addObject:source]; + + if (![source.type isEqual:OCResourceTypeAny]) // Sources for type any need no sorting as they are not used directly + { + [self _sortSourcesForType:source.type]; + } + } +} + +- (void)_sortSourcesForType:(OCResourceType)type +{ + [_sourcesByType[type] sortUsingComparator:^NSComparisonResult(OCResourceSource * _Nonnull source1, OCResourceSource * _Nonnull source2) { + OCResourceSourcePriority source1Priority = [source1 priorityForType:type]; + OCResourceSourcePriority source2Priority = [source2 priorityForType:type]; + + if (source1Priority == source2Priority) + { + return (NSOrderedSame); + } + + return ((source1Priority > source2Priority) ? NSOrderedAscending : NSOrderedDescending); // Rank highest priorities first + }]; +} + +- (void)removeSource:(OCResourceSource *)source +{ + @synchronized(_sourcesByType) + { + NSMutableArray *sources; + + if ((sources = _sourcesByType[source.type]) != nil) + { + [sources removeObject:source]; + } + + if ([source.type isEqual:OCResourceTypeAny]) + { + for (OCResourceType type in _sourcesByType) + { + if (![type isEqual:OCResourceTypeAny]) + { + [_sourcesByType[type] removeObject:source]; + } + } + } + + source.manager = nil; + } +} + +#pragma mark - Requests +- (void)startRequest:(OCResourceRequest *)request +{ + dispatch_async(_queue, ^{ + [self _startRequest:request]; + }); +} + +- (void)_startRequest:(OCResourceRequest *)request // RUNS ON _QUEUE +{ + BOOL isNewRequest = YES; + + for (OCResourceManagerJob *job in _jobs) + { + OCResourceRequest *otherRequest; + + if ((otherRequest = job.primaryRequest) != nil) + { + OCResourceRequestRelation relation = [request relationWithRequest:otherRequest]; + + switch (relation) + { + case OCResourceRequestRelationDistinct: + break; + + case OCResourceRequestRelationGroupWith: + isNewRequest = NO; + [job addRequest:request]; + break; + + case OCResourceRequestRelationReplace: + isNewRequest = NO; + [job replacePrimaryRequestWith:request]; + break; + } + } + + if (!isNewRequest) { break; } + } + + if (isNewRequest) + { + // Add new job for request + OCResourceManagerJob *job; + + if ((job = [[OCResourceManagerJob alloc] initWithPrimaryRequest:request forManager:self]) != nil) + { + [_jobs addObject:job]; + } + + [self setNeedsScheduling]; + } +} + +- (void)stopRequest:(OCResourceRequest *)request +{ + dispatch_async(_queue, ^{ + request.cancelled = YES; + [self _stopRequest:request]; + }); +} + +- (void)_stopRequest:(OCResourceRequest *)request // RUNS ON _QUEUE +{ + [request.job removeRequest:request]; + + [self setNeedsScheduling]; +} + +#pragma mark - Scheduling +- (void)setNeedsScheduling +{ + BOOL doSchedule = NO; + + @synchronized(self) + { + if (!_needsScheduling) + { + _needsScheduling = YES; + doSchedule = YES; + } + } + + if (doSchedule) + { + dispatch_async(_queue, ^{ + @synchronized(self) + { + self->_needsScheduling = NO; + } + + [self schedule]; + }); + } +} + +- (void)schedule // RUNS ON _QUEUE +{ + NSMutableArray *removeJobs = nil; + + for (OCResourceManagerJob *job in _jobs) + { + BOOL removeJob = NO; + + OCResourceRequest *primaryRequest; + + if (((primaryRequest = job.primaryRequest) != nil) && !job.cancelled) + { + if (job.state == OCResourceManagerJobStateNew) + { + OCResourceType resourceType; + + // Copy pre-sorted array of sources for this resourse type + if ((resourceType = primaryRequest.type) != nil) + { + job.sources = [_sourcesByType[resourceType] copy]; + } + + // Start going through sources + job.state = OCResourceManagerJobStateInProgress; + } + + if (job.state == OCResourceManagerJobStateInProgress) + { + if (job.sources.count == 0) + { + // Jobs without sources are immediately "complete" + job.state = OCResourceManagerJobStateComplete; + } + else + { + // Start iterating + if (job.sourcesCursorPosition == nil) + { + [self _queryNextSourceForJob:job]; + } + } + } + + if (job.state == OCResourceManagerJobStateComplete) + { + // Job complete + + // Store in database + if ((job.latestResource != nil) && + (job.latestResource.quality >= OCResourceQualityNormal) && // require "normal" as minimum quality + (job.lastStoredResource != job.latestResource)) // ensure we don't save the same resource instance twice + { + OCResource *resource; + + if ((resource = job.latestResource) != nil) + { + job.lastStoredResource = resource; + + if (![resource.originSourceIdentifier isEqual:OCResourceSourceIdentifierStorage]) // Avoid writing back what was also retrieved from storage + { + [self storeResource:job.latestResource completionHandler:^(NSError * _Nullable error) { + if (error != nil) + { + OCTLogError(@[@"ResMan"], @"Error %@ storing resource %@", error, resource); + } + }]; + } + } + } + + // Remove "single-run" requests + [job removeRequestsWithLifetime:OCResourceRequestLifetimeSingleRun]; + + // Remove "empty" jobs + if (job.primaryRequest == nil) + { + removeJob = YES; + } + + // Remove job when complete (preliminary catch-all) + removeJob = YES; + } + } + else + { + // Remove jobs without primary request or that were cancelled + removeJob = YES; + } + + if (removeJob) + { + if (removeJobs == nil) { removeJobs = [NSMutableArray new]; } + [removeJobs addObject:job]; + } + } + + if (removeJobs.count > 0) + { + [_jobs removeObjectsInArray:removeJobs]; + } +} + +- (void)_queryNextSourceForJob:(OCResourceManagerJob *)job // RUNS ON _QUEUE +{ + OCResourceRequest *primaryRequest; + + if ((primaryRequest = job.primaryRequest) == nil) + { + // Jobs without request are complete + job.state = OCResourceManagerJobStateComplete; + } + + if ((job.state != OCResourceManagerJobStateComplete) && (primaryRequest != nil)) + { + // Find next source: + // a) no source yet -> first suitable source + // b) no resource yet -> pick next source that suggests it could provide the resource + // c) existing resource -> find next source that suggests it can provide the resource at a higher quality + + NSNumber *nextCursorPosition = nil; + + primaryRequest = job.primaryRequest; + + NSUInteger srcIdx = ((job.sourcesCursorPosition == nil) ? + 0 : // start with first suitable source + job.sourcesCursorPosition.unsignedIntegerValue + 1); // find next suitable source + + while (srcIdx < job.sources.count) + { + OCResourceQuality sourceQuality = [job.sources[srcIdx] qualityForRequest:job.primaryRequest]; + + if ((sourceQuality != OCResourceQualityNone) && // Source can provide resource + (sourceQuality >= job.minimumQuality) && // Source can provide resource in required quality + ((sourceQuality > job.latestResource.quality) || // Source can provide resource in higher quality than existing resource + (job.latestResource == nil))) // Or no resource yet + { + nextCursorPosition = @(srcIdx); + break; + } + + srcIdx++; + }; + + // If no next source could be found, job is complete + if (nextCursorPosition == nil) + { + job.state = OCResourceManagerJobStateComplete; + } + else + { + job.sourcesCursorPosition = nextCursorPosition; + } + } + + if ((job.state != OCResourceManagerJobStateComplete) && (primaryRequest != nil)) + { + if (job.sourcesCursorPosition.unsignedIntegerValue < job.sources.count) + { + OCResourceSource *source = job.sources[job.sourcesCursorPosition.unsignedIntegerValue]; + + OCResourceManagerJobSeed jobSeed = job.seed; + + [source provideResourceForRequest:primaryRequest resultHandler:^(NSError * _Nullable error, OCResource * _Nullable resource) { + dispatch_async(self->_queue, ^{ + [self _handleError:error resource:resource forJob:job seed:jobSeed from:source]; + }); + }]; + } + else + { + job.state = OCResourceManagerJobStateComplete; + } + } + + [self setNeedsScheduling]; +} + +- (void)_handleError:(nullable NSError *)error resource:(nullable OCResource *)resource forJob:(OCResourceManagerJob *)job seed:(OCResourceManagerJobSeed)originalSeed from:(OCResourceSource *)source // RUNS ON _QUEUE +{ + OCTLogDebug(@[@"ResMan"], @"Source %@ returned resource=%@, error=%@", source.identifier, resource, error); + + if ([error isHTTPStatusErrorWithCode:OCHTTPStatusCodeTOO_EARLY]) + { + // Resource is not available yet (f.ex. still processed by the server): + // - log, but don't try again because resource could remain in processing for a loooong time + // - handle as if resoruce does not exist + OCTLogDebug(@[@"ResMan"], @"Handling source %@ returned resource=%@ error=%@ (!! remote resource processing - will not retry !!) as OCErrorResourceDoesNotExist", source.identifier, resource, error); + + error = OCErrorFromError(OCErrorResourceDoesNotExist, error); + } + + if ([error isOCErrorWithCode:OCErrorResourceDoesNotExist]) + { + // Resource does not exist anymore: delete from cache + restart job + __weak OCResourceManager *weakSelf = self; + [self removeResourceOfType:job.primaryRequest.type identifier:job.primaryRequest.identifier completionHandler:^(NSError * _Nullable error) { + OCResourceManager *strongSelf = weakSelf; + + if ((error == nil) && (strongSelf != nil)) + { + dispatch_async(strongSelf->_queue, ^{ + // Remove any previously found resources from job and requests + job.latestResource = nil; + + NSArray *jobRequests = job.requests.allObjects; // Make a copy to preserve requests for the duration of the iteration and to prevent exceptions caused by possible mutations + + for (OCResourceRequest *request in jobRequests) + { + request.resource = nil; // Updates the resource of the request, which will notify its changeHandler + } + + // Restart job + job.state = OCResourceManagerJobStateNew; + job.sourcesCursorPosition = nil; + + [strongSelf setNeedsScheduling]; + }); + } + }]; + + return; + } + + if ((resource != nil) && // A resource must have been returned + (originalSeed == job.seed) && // The seed must match (otherwise the returned resource has to be considered outdated) + ((job.latestResource == nil) || ((job.latestResource != nil) && (job.latestResource.quality <= resource.quality)))) // First resource for job - or resource has identical or higher quality than existing one + { + job.latestResource = resource; + + NSArray *jobRequests = job.requests.allObjects; // Make a copy to preserve requests for the duration of the iteration and to prevent exceptions caused by possible mutations + + for (OCResourceRequest *request in jobRequests) + { + request.resource = resource; // Updates the resource of the request, which will notify its changeHandler + } + } + + [self _queryNextSourceForJob:job]; +} + +#pragma mark - Storage abstraction +- (void)retrieveResourceForRequest:(OCResourceRequest *)request completionHandler:(OCResourceRetrieveCompletionHandler)completionHandler +{ + NSString *cacheKey = [request.type stringByAppendingFormat:@":%@", request.identifier]; + OCResource *cachedResource; + + // Retrieve resource from cache + if ((cachedResource = [_cache objectForKey:cacheKey]) != nil) + { + // Check that it meets the requirements of the request + if ([request satisfiedByResource:cachedResource]) + { + // Serve from cache + OCTLogDebug(@[@"ResMan"], @"🚀 Serving request %@ with resource from memory cache: %@", request, cachedResource); + completionHandler(nil, cachedResource); + return; + } + } + + // Retrieve resource from storage + [self.storage retrieveResourceForRequest:request completionHandler:^(NSError * _Nullable error, OCResource * _Nullable resource) { + // Store resource in cache + if (resource != nil) + { + [self->_cache setObject:resource forKey:cacheKey]; + } + + // Return to completion handler + OCTLogDebug(@[@"ResMan"], @"💾 Serving request %@ with resource from database: %@", request, resource); + completionHandler(error, resource); + }]; +} + +- (void)storeResource:(OCResource *)resource completionHandler:(OCResourceStoreCompletionHandler)completionHandler +{ + // Store resource in cache + NSString *cacheKey = [resource.type stringByAppendingFormat:@":%@", resource.identifier]; + [_cache setObject:resource forKey:cacheKey]; + + // Store resource in storage + [self.storage storeResource:resource completionHandler:completionHandler]; +} + +- (void)removeResourceOfType:(OCResourceType)type identifier:(OCResourceIdentifier)identifier completionHandler:(OCResourceStoreCompletionHandler)completionHandler; +{ + // Remove from cache + NSString *cacheKey = [type stringByAppendingFormat:@":%@", identifier]; + [_cache removeObjectForKey:cacheKey]; + + // Remove resource from storage + [self.storage removeResourceOfType:type identifier:identifier completionHandler:completionHandler]; +} + +@end diff --git a/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.h b/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.h new file mode 100644 index 00000000..69750ebc --- /dev/null +++ b/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.h @@ -0,0 +1,71 @@ +// +// OCResourceManagerJob.h +// ownCloudSDK +// +// Created by Felix Schwarz on 04.01.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 "OCResourceRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCResourceManager; +@class OCResourceSource; +@class OCResource; + +typedef NS_ENUM(NSInteger, OCResourceManagerJobState) +{ + OCResourceManagerJobStateNew, + OCResourceManagerJobStateInProgress, + OCResourceManagerJobStateComplete +}; + +typedef NSUInteger OCResourceManagerJobSeed; + +typedef void(^OCResourceManagerJobCancellationHandler)(void); + +@interface OCResourceManagerJob : NSObject + +@property(weak,nullable) OCResourceManager *manager; + +@property(assign) OCResourceManagerJobState state; +@property(assign) OCResourceManagerJobSeed seed; + +@property(strong) NSHashTable *requests; +@property(strong) NSMutableArray *managedRequests; +@property(weak,nullable,nonatomic) OCResourceRequest *primaryRequest; + +@property(assign) OCResourceQuality minimumQuality; + +@property(strong,nullable) NSMutableArray *sources; +@property(strong,nullable) NSNumber *sourcesCursorPosition; + +@property(strong,nullable) OCResource *latestResource; +@property(weak,nullable) OCResource *lastStoredResource; + +@property(assign,nonatomic) BOOL cancelled; +@property(copy,nullable) OCResourceManagerJobCancellationHandler cancellationHandler; + +- (instancetype)initWithPrimaryRequest:(OCResourceRequest *)primaryRequest forManager:(OCResourceManager *)manager; + +- (void)addRequest:(OCResourceRequest *)request; //!< Adds an additional request +- (void)replacePrimaryRequestWith:(OCResourceRequest *)request; //!< Replace primary request with this request and start anew +- (void)removeRequest:(OCResourceRequest *)request; //!< Removes a request + +- (void)removeRequestsWithLifetime:(OCResourceRequestLifetime)lifetime; //!< For removal of (managed) requests with a lifetime other than OCResourceRequestLifetimeUntilDeallocation + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.m b/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.m new file mode 100644 index 00000000..aaf64c4c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Manager/OCResourceManagerJob.m @@ -0,0 +1,205 @@ +// +// OCResourceManagerJob.m +// ownCloudSDK +// +// Created by Felix Schwarz on 04.01.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 "OCResourceManagerJob.h" +#import "OCResourceManager.h" +#import "OCResourceRequest.h" +#import "OCResourceSource.h" +#import "OCLogger.h" + +@implementation OCResourceManagerJob + +- (instancetype)initWithPrimaryRequest:(OCResourceRequest *)primaryRequest forManager:(OCResourceManager *)manager; +{ + if ((self = [super init]) != nil) + { + _primaryRequest = primaryRequest; + _manager = manager; + + _requests = [NSHashTable weakObjectsHashTable]; + _sources = [NSMutableArray new]; + + [self addRequest:primaryRequest]; + } + + return (self); +} + +- (void)dealloc +{ + for (OCResourceRequest *request in _requests) + { + [request endRequest]; + } +} + +- (OCResourceRequest *)primaryRequest +{ + @synchronized(self) + { + if (_primaryRequest == nil) + { + _primaryRequest = [[_requests allObjects] firstObject]; + } + + return (_primaryRequest); + } +} + +- (void)_computeMinimumQuality +{ + OCResourceQuality minimumQuality = OCResourceQualityMaximum; + + for (OCResourceRequest *request in _requests) + { + if (request.minimumQuality < minimumQuality) + { + minimumQuality = request.minimumQuality; + } + } + + _minimumQuality = minimumQuality; +} + +- (void)addRequest:(OCResourceRequest *)request +{ + @synchronized(self) + { + request.job = self; + [_requests addObject:request]; + + if (request.lifetime != OCResourceRequestLifetimeUntilDeallocation) + { + if (_managedRequests == nil) + { + _managedRequests = [NSMutableArray new]; + } + + [_managedRequests addObject:request]; + } + + [self _computeMinimumQuality]; + } +} + +- (void)replacePrimaryRequestWith:(OCResourceRequest *)request +{ + @synchronized(self) + { + [self addRequest:request]; + + _primaryRequest = request; + + _state = OCResourceManagerJobStateNew; + + _sources = nil; + _sourcesCursorPosition = nil; + + _seed++; + } + + [self _callCancellationHandlerAndResetIt]; +} + +- (void)setCancelled:(BOOL)cancelled +{ + if (cancelled != _cancelled) + { + _cancelled = cancelled; + + if (cancelled) + { + [self _callCancellationHandlerAndResetIt]; + } + } +} + +- (void)_callCancellationHandlerAndResetIt +{ + OCResourceManagerJobCancellationHandler cancellationHandler = nil; + + @synchronized(self) + { + cancellationHandler = _cancellationHandler; + _cancellationHandler = nil; + } + + if (cancellationHandler != nil) + { + cancellationHandler(); + } +} + +- (void)removeRequest:(OCResourceRequest *)request +{ + BOOL cancelled = YES; + + @synchronized(self) + { + [_requests removeObject:request]; + [_managedRequests removeObject:request]; + + for (OCResourceRequest *request in _requests) + { + if ((request != nil) && !request.cancelled) + { + cancelled = NO; + break; + } + } + + [self _computeMinimumQuality]; + } + + if (cancelled) + { + self.cancelled = YES; + } +} + +- (void)removeRequestsWithLifetime:(OCResourceRequestLifetime)lifetime +{ + @synchronized(self) + { + if (_managedRequests != nil) + { + NSMutableIndexSet *removeIndexes = nil; + NSUInteger idx = 0; + + for (OCResourceRequest *request in _managedRequests) + { + if (request.lifetime == lifetime) + { + if ((removeIndexes = [NSMutableIndexSet new]) != nil) + { + [removeIndexes addIndex:idx]; + } + } + + idx++; + } + + if (removeIndexes != nil) + { + [_managedRequests removeObjectsAtIndexes:removeIndexes]; + } + } + } +} + +@end diff --git a/ownCloudSDK/Core/Resources/OCResourceTypes.h b/ownCloudSDK/Core/Resources/OCResourceTypes.h new file mode 100644 index 00000000..c7eed3ae --- /dev/null +++ b/ownCloudSDK/Core/Resources/OCResourceTypes.h @@ -0,0 +1,48 @@ +// +// OCResourceTypes.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 + +typedef NSString* OCResourceSourceIdentifier NS_TYPED_ENUM; + +typedef NS_ENUM(NSInteger, OCResourceQuality) +{ + OCResourceQualityNone = 0, //!< Resource can't be provided + + // Dynamically generated, may be cached + OCResourceQualityFallback = 25, //!< Fallback content, i.e. generated placeholder thumbnails + + // Stored permanently + OCResourceQualityNormal = 50, //!< Normal quality, i.e. thumbnails from the server + OCResourceQualityHigh = 75, //!< High quality, i.e. a local thumbnail generator for videos, PDFs, office formats + + // Ordering logic only + OCResourceQualityCached = 100, //!< Intermediate quality returned by the cache to ensure the cache response is awaited before any other source is asked to retrieve the resource. Returned resource's quality will differ and represent the actual quality that was stored in the cache. + + // Logically maximum quality + OCResourceQualityMaximum = OCResourceQualityCached +}; + +typedef NSString* OCResourceType NS_TYPED_ENUM; //!< Type of resource, f.ex. thumbnail or avatar +typedef NSString* OCResourceIdentifier; //!< An identifier that identifies the resource, f.ex. the file ID or user name +typedef NSString* OCResourceVersion; //!< A string that can be used to distinguish versions (through equality comparison - *this is the version that counts to detect changes*), f.ex. checksums, timeframe-specific values to limit request frequency or even ETags +typedef NSString* OCResourceRemoteVersion; //!< A string provided by a remote location indicating its version of the resource, f.ex. ETags, purely for use cases to improve request-efficiency (f.ex. via If-Match headers) +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; diff --git a/ownCloudSDK/Core/Resources/README.md b/ownCloudSDK/Core/Resources/README.md new file mode 100644 index 00000000..5e848895 --- /dev/null +++ b/ownCloudSDK/Core/Resources/README.md @@ -0,0 +1,45 @@ +# Unified Resource Retrieval & Caching + +The `OCResource` subsystem provides the infrastructure to +- request resources +- cache downloaded resources and deliver them from cache +- keep (versioned) resources updated with the latest remote version +- receive live updates to resources as they are generated / retrieved + +## Components + +### Manager +Single instance per vault for all resource types. Keeps track of sources, requests and storage. + +### Jobs +Internal object that keeps track of relevant sources and requests (+ helps to group them). + +### Storage +Protocol-based, typically implemented by `OCDatabase`, so single instance per vault for all resource types. Provides a single interface to all sources to retrieve and store resources. + +### Sources +Registered with the manager, provide resources in different qualities (placeholder, remote thumbnail, locally generated thumbnail, …) with different priorities. + +### Requests +Describe the requested resource. Receive updates as long as they are running. + +### Resources +Encapsulates the resource status and the resource itself. + + +## Internal structure +- `OCResourceManager` is created at the `OCVault` level, with `OCDatabase` as storage +- sources are added, single instance of `OCResourceSourceDatabase` looks for versions from the cache + - sources are presorted using `priorityForType:` +- when requests are added, `OCResourceManager` proceeds as follows: + - asks the `OCRequest` for its `relationWithRequest:` with all other running, primary requests + - if `groupWith`, "links" the request to the other request's job + - if `replace`, uses the request as new primary request and resets the job + - if all return `distinct`, adds a job for the new request, with the request as a new primary request + - for new primary requests, creates a new job and schedules it as `new` +- during scheduling, requests are processed as follows: + - jobs with status `new` are assigned a copy of the pre-sorted sources for the request's type + - then works through the sources from highest to lowest priority until + - it receives the resource in a quality >= best quality returned by the sources (except the quality returned by the source returning the resource) + - none of the resources have been able to return the resource +- instances of `OCResource` must be serializable via `NSSecureCoding` so they can be persisted and retrieved in a standardized way, regardless of their internal structure and subclass diff --git a/ownCloudSDK/Core/Resources/Request/OCResourceRequest.h b/ownCloudSDK/Core/Resources/Request/OCResourceRequest.h new file mode 100644 index 00000000..1d69f59c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Request/OCResourceRequest.h @@ -0,0 +1,92 @@ +// +// OCResourceRequest.h +// ownCloudSDK +// +// Created by Felix Schwarz on 30.09.20. +// 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 "OCResourceTypes.h" +#import "OCItem.h" +#import "OCUser.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCResourceManagerJob; +@class OCResourceRequest; +@class OCResource; + +typedef NS_ENUM(NSInteger, OCResourceRequestRelation) +{ + OCResourceRequestRelationDistinct, //!< Distinct requests, no relation (f.ex. for different request types, different resources, etc.) + OCResourceRequestRelationGroupWith, //!< Request points to same resource, should/can be linked with other request + OCResourceRequestRelationReplace //!< Request points to a newer/better version of same resource, should/can replace the other request +}; + +typedef NS_ENUM(NSInteger, OCResourceRequestLifetime) +{ + OCResourceRequestLifetimeUntilDeallocation, + OCResourceRequestLifetimeSingleRun, + OCResourceRequestLifetimeUntilStopped +}; + +typedef void(^OCResourceRequestChangeHandler)(OCResourceRequest *request, NSError * _Nullable error, BOOL isOngoing, OCResource * _Nullable previousResource, OCResource * _Nullable newResource); +typedef NSString* OCResourceRequestGroupIdentifier; + +@protocol OCResourceRequestDelegate +- (void)resourceRequest:(OCResourceRequest *)request didChangeWithError:(nullable NSError *)error isOngoing:(BOOL)isOngoing previousResource:(nullable OCResource *)previousResource newResource:(nullable OCResource *)newResource; +@end + +@interface OCResourceRequest : NSObject + +@property(weak,nullable) OCCore *core; + +@property(weak,nullable,nonatomic) OCResourceManagerJob *job; + +@property(assign) OCResourceRequestLifetime lifetime; //!< Determines how long a request is considered / served. + +@property(strong) OCResourceType type; +@property(strong) OCResourceIdentifier identifier; +@property(strong,nullable) id reference; //!< Depending on resource, instance of a reference, i.e. OCItem, OCUser, ... +@property(assign) OCResourceQuality minimumQuality; //!< Require minimum quality for requested resource. Allows to f.ex. exclude placeholders from being returned. This filtering could also take place via resource.quality, but memory + CPU cycles would be wasted that way. Defaults to OCResourceQualityFallback. + +@property(strong,nullable) OCResourceVersion version; +@property(strong,nullable) OCResourceRemoteVersion remoteVersion; +@property(strong,nullable) OCResourceStructureDescription structureDescription; + +@property(assign) CGSize maxPointSize; //!< Maximum size in points on screen +@property(assign) CGFloat scale; //!< Number of pixels per point +@property(readonly) CGSize maxPixelSize; //!< Computed from maxPointSize and scale + +@property(assign) BOOL waitForConnectivity; //!< Sources that send requests to servers should wait for connectivity + +@property(assign,nonatomic) BOOL cancelled; +@property(readonly) BOOL ended; + +@property(strong,nullable,nonatomic) OCResource *resource; + +@property(copy,nullable) OCResourceRequestChangeHandler changeHandler; +@property(weak,nullable) id delegate; + +- (instancetype)initWithType:(OCResourceType)type identifier:(OCResourceIdentifier)identifier; + +- (OCResourceRequestRelation)relationWithRequest:(OCResourceRequest *)otherRequest; //!< return how this request is related with otherRequest + +- (BOOL)satisfiedByResource:(OCResource *)resource; //!< Return YES if the resource satisfies the request requirements. Used to determine if a cached resource is meeting the request's requirement and can be served. + +- (void)endRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Request/OCResourceRequest.m b/ownCloudSDK/Core/Resources/Request/OCResourceRequest.m new file mode 100644 index 00000000..3b636128 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Request/OCResourceRequest.m @@ -0,0 +1,148 @@ +// +// OCResourceRequest.m +// ownCloudSDK +// +// Created by Felix Schwarz on 30.09.20. +// 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 "OCResourceRequest.h" +#import "OCResourceManagerJob.h" +#import "OCResourceManager.h" +#import "OCResource.h" +#import "OCLogger.h" + +@implementation OCResourceRequest + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _minimumQuality = OCResourceQualityFallback; + } + + return (self); +} + +- (instancetype)initWithType:(OCResourceType)type identifier:(OCResourceIdentifier)identifier +{ + if ((self = [self init]) != nil) + { + _type = type; + _identifier= identifier; + } + + return (self); +} + +- (void)dealloc +{ + OCResourceManager *manager = self.job.manager; + + [self endRequest]; + OCTLogDebug(@[@"ResMan"], @"Deallocating OCResourceRequest"); + + [manager setNeedsScheduling]; +} + +- (OCResourceRequestRelation)relationWithRequest:(OCResourceRequest *)otherRequest +{ + return (OCResourceRequestRelationDistinct); +} + +- (BOOL)satisfiedByResource:(OCResource *)resource +{ + if ([self.type isEqual:resource.type] && + [self.identifier isEqual:resource.identifier] && + [self.version isEqual:resource.version] && + [self.structureDescription isEqual:resource.structureDescription]) + { + return (YES); + } + + return (NO); +} + +- (void)setResource:(OCResource *)resource +{ + OCResource *previousResource = _resource; + + _resource = resource; + + [self notifyWithError:nil isOngoing:YES previousResource:previousResource newResource:resource]; +} + +- (void)notifyWithError:(NSError *)error isOngoing:(BOOL)isOngoing previousResource:(OCResource *)previousResource newResource:(OCResource *)newResource +{ + if (_delegate != nil) + { + [_delegate resourceRequest:self didChangeWithError:error isOngoing:isOngoing previousResource:previousResource newResource:newResource]; + } + + if (_changeHandler != nil) + { + _changeHandler(self, error, isOngoing, previousResource, newResource); + } +} + +- (CGSize)maxPixelSize +{ + CGSize pointSize = self.maxPointSize; + CGFloat scale = self.scale; + + if (scale == 0) + { + scale = 1.0; + } + + return (CGSizeMake(pointSize.width * scale, pointSize.height * scale)); +} + +- (void)setCancelled:(BOOL)cancelled +{ + _cancelled = cancelled; + + if (cancelled) + { + [self endRequest]; + } +} + +- (void)endRequest +{ + OCResourceRequestChangeHandler changeHandler = nil; + OCResource *resource = nil; + + [self willChangeValueForKey:@"ended"]; + + @synchronized(self) + { + changeHandler = _changeHandler; + resource = _resource; + + _changeHandler = nil; + _ended = YES; + } + + [self didChangeValueForKey:@"ended"]; + + if (changeHandler != nil) + { + OCTLogDebug(@[@"ResMan"], @"Ending OCResourceRequest %@", self); + changeHandler(self, nil, NO, resource, resource); + } + + [self notifyWithError:nil isOngoing:NO previousResource:resource newResource:resource]; // only notifies delegate here, as _changeHandler is nil at this point +} + +@end diff --git a/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.h b/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.h new file mode 100644 index 00000000..f863f679 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.h @@ -0,0 +1,31 @@ +// +// OCResourceRequestImage.h +// ownCloudSDK +// +// Created by Felix Schwarz on 12.01.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 "OCResourceRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceRequestImage : OCResourceRequest + +@property(assign) OCImageFillMode fillMode; + +- (BOOL)resourceMeetsSizeRequirement:(OCResource *)resource; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.m b/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.m new file mode 100644 index 00000000..afb1386d --- /dev/null +++ b/ownCloudSDK/Core/Resources/Request/OCResourceRequestImage.m @@ -0,0 +1,47 @@ +// +// OCResourceRequestImage.m +// ownCloudSDK +// +// Created by Felix Schwarz on 12.01.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 "OCResourceRequestImage.h" +#import "OCResourceImage.h" +#import "OCMacros.h" + +@implementation OCResourceRequestImage + +- (BOOL)satisfiedByResource:(OCResource *)resource +{ + if ([super satisfiedByResource:resource]) + { + return ([self resourceMeetsSizeRequirement:resource]); + } + + return (NO); +} + +- (BOOL)resourceMeetsSizeRequirement:(OCResource *)resource +{ + OCResourceImage *image; + + if ((image = OCTypedCast(resource, OCResourceImage)) != nil) + { + return ((self.maxPixelSize.width <= image.maxPixelSize.width) && (self.maxPixelSize.height <= image.maxPixelSize.height)); + } + + return (NO); +} + +@end diff --git a/ownCloudSDK/Core/Resources/Resource/OCResource.h b/ownCloudSDK/Core/Resources/Resource/OCResource.h new file mode 100644 index 00000000..c2d3b596 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResource.h @@ -0,0 +1,58 @@ +// +// OCResource.h +// ownCloudSDK +// +// Created by Felix Schwarz on 30.09.20. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 + +#import "OCResourceTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCResourceRequest; + +@interface OCResource : NSObject + +@property(strong) OCResourceType type; +@property(strong) OCResourceIdentifier identifier; +@property(strong,nullable) OCResourceVersion version; +@property(strong,nullable) OCResourceRemoteVersion remoteVersion; +@property(strong,nullable) OCResourceStructureDescription structureDescription; + +@property(strong,nullable) OCResourceSourceIdentifier originSourceIdentifier; //!< Identifier of the source the resource originated from (optional, NOT serialized) + +@property(assign) OCResourceQuality quality; + +@property(strong,nullable) OCResourceMetadata metaData; +@property(strong,nullable) OCResourceMIMEType mimeType; + +@property(strong,nullable) NSURL *url; //!< URL at which the resource is stored (optional) +@property(strong,nullable,nonatomic) NSData *data; //!< Data of the resource. If data == nil and url != nil, loads contents of url. + +@property(strong,nullable) NSDate *timestamp; + +- (instancetype)initWithRequest:(OCResourceRequest *)request; + +@end + +extern OCResourceType OCResourceTypeAny; +extern OCResourceType OCResourceTypeAvatar; +extern OCResourceType OCResourceTypeItemThumbnail; +extern OCResourceType OCResourceTypeDriveItem; +extern OCResourceType OCResourceTypeURLItem; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Resource/OCResource.m b/ownCloudSDK/Core/Resources/Resource/OCResource.m new file mode 100644 index 00000000..d91b309d --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResource.m @@ -0,0 +1,113 @@ +// +// OCResource.m +// ownCloudSDK +// +// Created by Felix Schwarz on 30.09.20. +// Copyright © 2020 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResource.h" +#import "OCResourceRequest.h" + +@implementation OCResource + +- (instancetype)initWithRequest:(OCResourceRequest *)request +{ + if ((self = [super init]) != nil) + { + _type = request.type; + _identifier = request.identifier; + + _version = request.version; + _remoteVersion = request.remoteVersion; + _structureDescription = request.structureDescription; + } + + return (self); +} + +- (NSData *)data +{ + if ((_data == nil) && (_url != nil)) + { + _data = [[NSData alloc] initWithContentsOfURL:_url]; + } + + return (_data); +} + +#pragma mark - View Provider +- (UIView *)provideViewForSize:(CGSize)size +{ + return (nil); +} + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [coder encodeObject:_type forKey:@"type"]; + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_version forKey:@"version"]; + [coder encodeObject:_remoteVersion forKey:@"remoteVersion"]; + [coder encodeObject:_structureDescription forKey:@"structureDescription"]; + + [coder encodeInteger:_quality forKey:@"quality"]; + + [coder encodeObject:_metaData forKey:@"metaData"]; + [coder encodeObject:_mimeType forKey:@"mimeType"]; + + [coder encodeObject:_url forKey:@"url"]; + if (_url == nil) + { + [coder encodeObject:_data forKey:@"data"]; + } + + [coder encodeObject:_timestamp forKey:@"timestamp"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [self init]) != nil) + { + _type = [coder decodeObjectOfClass:NSString.class forKey:@"type"]; + _identifier = [coder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _version = [coder decodeObjectOfClass:NSString.class forKey:@"version"]; + _remoteVersion = [coder decodeObjectOfClass:NSString.class forKey:@"remoteVersion"]; + _structureDescription = [coder decodeObjectOfClass:NSString.class forKey:@"structureDescription"]; + + _quality = [coder decodeIntegerForKey:@"quality"]; + + _metaData = [coder decodeObjectOfClass:NSString.class forKey:@"metaData"]; + _mimeType = [coder decodeObjectOfClass:NSString.class forKey:@"mimeType"]; + + _url = [coder decodeObjectOfClass:NSURL.class forKey:@"url"]; + _data = [coder decodeObjectOfClass:NSData.class forKey:@"data"]; + + _timestamp = [coder decodeObjectOfClass:NSDate.class forKey:@"timestamp"]; + } + + return (self); +} + +@end + +OCResourceType OCResourceTypeAny = @"*"; +OCResourceType OCResourceTypeAvatar = @"image.avatar"; +OCResourceType OCResourceTypeItemThumbnail = @"image.thumbnail"; +OCResourceType OCResourceTypeDriveItem = @"drive.item"; +OCResourceType OCResourceTypeURLItem = @"url.item"; diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceImage.h b/ownCloudSDK/Core/Resources/Resource/OCResourceImage.h new file mode 100644 index 00000000..cb26e982 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceImage.h @@ -0,0 +1,42 @@ +// +// OCResourceImage.h +// ownCloudSDK +// +// Created by Felix Schwarz on 21.12.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResource.h" +#import "OCImage.h" +#import "OCItemThumbnail.h" +#import "OCAvatar.h" +#import "OCViewProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceImage : OCResource + +@property(assign) CGSize maxPixelSize; //!< Maximum size of the resource in pixels, CGSizeZero otherwise + +@property(assign) OCImageFillMode fillMode; //!< Fill mode of the image + +@property(strong,nullable,nonatomic) OCImage *image; //!< OCImage representation of image, for caching existing instances - or generated on-the-fly. NOT serialized! +@property(strong,readonly,nullable,nonatomic) OCItemThumbnail *thumbnail; //!< OCItemThumbnail representation of .image - generated on-the-fly. NOT serialized! +@property(strong,readonly,nullable,nonatomic) OCAvatar *avatar; //!< OCAvatar representation of .image - generated on-the-fly. NOT serialized! + +// - (void)drawInRect:(CGRect)rect; +// - (UIView *)provideView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceImage.m b/ownCloudSDK/Core/Resources/Resource/OCResourceImage.m new file mode 100644 index 00000000..eb8097d7 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceImage.m @@ -0,0 +1,142 @@ +// +// OCResourceImage.m +// ownCloudSDK +// +// Created by Felix Schwarz on 21.12.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceImage.h" +#import "OCItemThumbnail.h" +#import "OCMacros.h" +#import "OCResourceRequest.h" +#import "OCResourceRequestImage.h" + +@implementation OCResourceImage + +- (instancetype)initWithRequest:(OCResourceRequest *)request +{ + if ((self = [super initWithRequest:request]) != nil) + { + self.maxPixelSize = request.maxPixelSize; + self.fillMode = OCTypedCast(request, OCResourceRequestImage).fillMode; + } + + return (self); +} + +- (id)_createImageWithClass:(Class)imageClass +{ + OCImage *image = [imageClass new]; + + image.mimeType = self.mimeType; + image.maxPixelSize = self.maxPixelSize; + image.fillMode = self.fillMode; + + image.url = self.url; + if (self.url == nil) + { + image.data = self.data; + } + + return (image); +} + +- (OCImage *)image +{ + if (_image == nil) + { + if ([self.type isEqual:OCResourceTypeAvatar]) { return ([self avatar]); } + if ([self.type isEqual:OCResourceTypeItemThumbnail]) { return ([self thumbnail]); } + + _image = [self _createImageWithClass:OCImage.class]; + } + + return (_image); +} + +- (OCItemThumbnail *)thumbnail +{ + if (_image == nil) + { + OCItemThumbnail *thumbnail = [self _createImageWithClass:OCItemThumbnail.class]; + + thumbnail.itemVersionIdentifier = [[OCItemVersionIdentifier alloc] initWithFileID:self.identifier eTag:self.version]; + thumbnail.specID = self.structureDescription; + + _image = thumbnail; + + return (thumbnail); + } + + return (OCTypedCast(_image, OCItemThumbnail)); +} + +- (OCAvatar *)avatar +{ + if (_image == nil) + { + OCAvatar *avatar = [self _createImageWithClass:OCAvatar.class]; + + avatar.userIdentifier = self.identifier; + avatar.eTag = self.version; + + avatar.timestamp = self.timestamp; + + _image = avatar; + + return (avatar); + } + + return (OCTypedCast(_image, OCAvatar)); +} + +#pragma mark - View provider +- (void)provideViewForSize:(CGSize)size inContext:(OCViewProviderContext *)context completion:(void (^)(UIView * _Nullable))completionHandler +{ + if ([self.image conformsToProtocol:@protocol(OCViewProvider)]) + { + [(id)self.image provideViewForSize:size inContext:context completion:completionHandler]; + + return; + } + + completionHandler(nil); +} + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [super encodeWithCoder:coder]; + + [coder encodeCGSize:_maxPixelSize forKey:@"maxPixelSize"]; + [coder encodeInteger:_fillMode forKey:@"fillMode"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [super initWithCoder:coder]) != nil) + { + _maxPixelSize = [coder decodeCGSizeForKey:@"maxPixelSize"]; + _fillMode = [coder decodeIntegerForKey:@"fillMode"]; + } + + return (self); +} + +@end diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceText.h b/ownCloudSDK/Core/Resources/Resource/OCResourceText.h new file mode 100644 index 00000000..eb31de65 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceText.h @@ -0,0 +1,30 @@ +// +// OCResourceText.h +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResource.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceText : OCResource + +@property(strong,nullable) NSString *text; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceText.m b/ownCloudSDK/Core/Resources/Resource/OCResourceText.m new file mode 100644 index 00000000..bf03738c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceText.m @@ -0,0 +1,63 @@ +// +// OCResourceText.m +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResourceText.h" +#import "OCDataTypes.h" + +@implementation OCResourceText + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [super encodeWithCoder:coder]; + + [coder encodeObject:_text forKey:@"text"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [super initWithCoder:coder]) != nil) + { + _text = [coder decodeObjectOfClass:NSString.class forKey:@"text"]; + } + + 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/Core/Resources/Resource/OCResourceTextPlaceholder.h b/ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.h new file mode 100644 index 00000000..7514472c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.h @@ -0,0 +1,29 @@ +// +// OCResourceTextPlaceholder.h +// ownCloudSDK +// +// Created by Felix Schwarz on 19.01.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceTextPlaceholder : OCResource + +@property(strong,nullable) NSString *text; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.m b/ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.m new file mode 100644 index 00000000..37b096fb --- /dev/null +++ b/ownCloudSDK/Core/Resources/Resource/OCResourceTextPlaceholder.m @@ -0,0 +1,46 @@ +// +// OCResourceTextPlaceholder.m +// ownCloudSDK +// +// Created by Felix Schwarz on 19.01.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 "OCResourceTextPlaceholder.h" + +@implementation OCResourceTextPlaceholder + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [super encodeWithCoder:coder]; + + [coder encodeObject:_text forKey:@"text"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [super initWithCoder:coder]) != nil) + { + _text = [coder decodeObjectOfClass:NSString.class forKey:@"text"]; + } + + return (self); +} + +@end diff --git a/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.h b/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.h new file mode 100644 index 00000000..c923f65f --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.h @@ -0,0 +1,28 @@ +// +// OCDatabase+ResourceStorage.h +// ownCloudSDK +// +// Created by Felix Schwarz on 10.01.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 "OCDatabase.h" +#import "OCResourceManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCDatabase (ResourceStorage) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.m b/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.m new file mode 100644 index 00000000..c42fb9fb --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCDatabase+ResourceStorage.m @@ -0,0 +1,207 @@ +// +// OCDatabase+ResourceStorage.m +// ownCloudSDK +// +// Created by Felix Schwarz on 10.01.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 "OCDatabase+ResourceStorage.h" +#import "OCSQLiteDB.h" +#import "OCLogger.h" +#import "OCResourceImage.h" +#import "OCMacros.h" +#import "OCSQLiteTransaction.h" +#import "NSError+OCError.h" + +@implementation OCDatabase (ResourceStorage) + +- (void)storeResource:(nonnull OCResource *)resource completionHandler:(nonnull OCResourceStoreCompletionHandler)completionHandler +{ + if ((resource.type == nil) || (resource.identifier == nil) || (resource.version == nil)) + { + OCTLogError(@[@"ResMan"], @"Error storing resource %@ because it lacks type, identifier or version.", OCLogPrivate(resource)); + return; + } + + NSError *error = nil; + NSData *resourceData = [NSKeyedArchiver archivedDataWithRootObject:resource requiringSecureCoding:YES error:&error]; + + if (resourceData == nil) + { + OCTLogError(@[@"ResMan"], @"Error storing resource %@ because it can't be serialized (error=%@).", OCLogPrivate(resource), error); + return; + } + + OCResourceImage *imageResource = OCTypedCast(resource, OCResourceImage); + + [self.sqlDB executeTransaction:[OCSQLiteTransaction transactionWithQueries:@[ + // Remove outdated versions and smaller resource sizes + [OCSQLiteQuery query:@"DELETE FROM thumb.resources WHERE identifier = :identifier AND type = :type AND ((version != :version) OR (maxWidth <= :maxWidth AND maxHeight <= :maxHeight) OR (structDesc != :structDesc))" // relatedTo:OCDatabaseTableNameResources + withNamedParameters:@{ + @"type" : resource.type, + + @"identifier" : resource.identifier, + @"version" : OCSQLiteNullProtect(resource.version), + @"structDesc" : OCSQLiteNullProtect(resource.structureDescription), + + @"maxWidth" : @((imageResource != nil) ? imageResource.maxPixelSize.width : 0), + @"maxHeight" : @((imageResource != nil) ? imageResource.maxPixelSize.height : 0), + } resultHandler:nil], + + // Insert new resource + [OCSQLiteQuery queryInsertingIntoTable:OCDatabaseTableNameResources + rowValues:@{ + @"type" : resource.type, + + @"identifier" : resource.identifier, + @"version" : OCSQLiteNullProtect(resource.version), + @"structDesc" : OCSQLiteNullProtect(resource.structureDescription), + + @"maxWidth" : ((imageResource != nil) ? @(imageResource.maxPixelSize.width) : NSNull.null), + @"maxHeight" : ((imageResource != nil) ? @(imageResource.maxPixelSize.height) : NSNull.null), + + @"metaData" : ((resource.metaData != nil) ? resource.metaData : NSNull.null), + @"data" : resourceData + } resultHandler:nil] + ] type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + if (completionHandler != nil) + { + completionHandler(error); + } + }]]; +} + +- (void)retrieveResourceForRequest:(nonnull OCResourceRequest *)request completionHandler:(nonnull OCResourceRetrieveCompletionHandler)completionHandler +{ + /* + // This is a bit more complex SQL statement. Here's how it was tested and what it is meant to achieve: + + // Table creation and test data set + CREATE TABLE thumb.thumbnails (tnID INTEGER PRIMARY KEY, maxWidth INTEGER NOT NULL, maxHeight INTEGER NOT NULL); + INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (10,10); + INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (15,15); + INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (20,20); + INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (25,25); + + SELECT * FROM thumbnails ORDER BY (maxWidth = 8 AND maxHeight = 8) DESC, (maxWidth >= 8 AND maxHeight >= 8) DESC, (((maxWidth < 8 AND maxHeight < 8) * -1000 + 1) * ((maxWidth * maxHeight) - ( 8* 8))) ASC LIMIT 0,1; + // Returns (10,10) (smaller than smallest => next bigger) + + SELECT * FROM thumbnails ORDER BY (maxWidth = 14 AND maxHeight = 14) DESC, (maxWidth >= 14 AND maxHeight >= 14) DESC, (((maxWidth < 14 AND maxHeight < 14) * -1000 + 1) * ((maxWidth * maxHeight) - (14*14))) ASC LIMIT 0,1; + // Returns (15,15) (smaller than biggest, but smaller ones also there, no exact match => next bigger) + + SELECT * FROM thumbnails ORDER BY (maxWidth = 15 AND maxHeight = 15) DESC, (maxWidth >= 15 AND maxHeight >= 15) DESC, (((maxWidth < 15 AND maxHeight < 15) * -1000 + 1) * ((maxWidth * maxHeight) - (15*15))) ASC LIMIT 0,1; + // Returns (15,15) (=> exact match) + + SELECT * FROM thumbnails ORDER BY (maxWidth = 16 AND maxHeight = 16) DESC, (maxWidth >= 16 AND maxHeight >= 16) DESC, (((maxWidth < 16 AND maxHeight < 16) * -1000 + 1) * ((maxWidth * maxHeight) - (16*16))) ASC LIMIT 0,1; + // Returns (20,20) (smaller than biggest, but smaller ones also there, no exact match => next bigger) + + SELECT * FROM thumbnails ORDER BY (maxWidth = 30 AND maxHeight = 30) DESC, (maxWidth >= 30 AND maxHeight >= 30) DESC, (((maxWidth < 30 AND maxHeight < 30) * -1000 + 1) * ((maxWidth * maxHeight) - (30*30))) ASC LIMIT 0,1; + // Returns (25,25) (bigger than biggest => return biggest) + + Explaining the ORDER part, where the magic takes place: + + (maxWidth = 30 AND maxHeight = 30) DESC, // prefer exact match + (maxWidth >= 30 AND maxHeight >= 30) DESC, // if no exact match, prefer bigger ones + + (((maxWidth < 30 AND maxHeight < 30) * -1000 + 1) * // make sure those smaller than needed score the largest negative values and move to the end of the list + ((maxWidth * maxHeight) - (30*30))) ASC // the closer the size is to the one needed, the higher it should rank + + Wouldn't this filtering and sorting be easier in ObjC code going through the results? + + Yes, BUT by performing this in SQLite we save the overhead/memory of loading irrelevant data, and since the WHERE is very specific, the set that SQLite needs to sort + this way will be tiny and shouldn't have any measurable performance impact. + */ + + if ((request.type==nil) || (request.identifier==nil)) + { + if (completionHandler!=nil) + { + completionHandler(OCError(OCErrorInsufficientParameters), nil); + } + return; + } + + OCSQLiteDBResultHandler resultHandler = ^(OCSQLiteDB *db, NSError *error, OCSQLiteTransaction *transaction, OCSQLiteResultSet *resultSet) { + NSError *returnError = error; + __block BOOL calledCompletionHandler = NO; + + if (returnError == nil) + { + [resultSet iterateUsing:^(OCSQLiteResultSet *resultSet, NSUInteger line, NSDictionary *rowDictionary, BOOL *stop) { + NSData *data = nil; + + if ((data = rowDictionary[@"data"])!=nil) + { + NSError *error; + OCResource *resource; + + if ((resource = [NSKeyedUnarchiver unarchivedObjectOfClass:OCResource.class fromData:data error:&error]) != nil) + { + completionHandler(nil, resource); + + calledCompletionHandler = YES; + *stop = YES; + } + } + } error:&returnError]; + } + + if (!calledCompletionHandler) + { + completionHandler(returnError, nil); + } + }; + + if ((request.maxPixelSize.width == 0) || (request.maxPixelSize.height == 0)) + { + [self.sqlDB executeQuery:[OCSQLiteQuery query:[NSString stringWithFormat:@"SELECT data FROM thumb.resources WHERE identifier = :identifier AND type = :type %@ %@ LIMIT 0,1", + ((request.version != nil) ? @"AND version = :version" : @""), + ((request.structureDescription != nil) ? @"AND structDesc = :structDesc" : @"")] + withNamedParameters:@{ + @"type" : request.type, + @"identifier" : request.identifier, + @"version" : OCSQLiteNullProtect(request.version), + @"structDesc" : OCSQLiteNullProtect(request.structureDescription), + } resultHandler:resultHandler]]; + } + else + { + [self.sqlDB executeQuery:[OCSQLiteQuery query:[NSString stringWithFormat:@"SELECT maxWidth, maxHeight, data FROM thumb.resources WHERE identifier = :identifier AND type = :type %@ %@ ORDER BY (maxWidth = :maxWidth AND maxHeight = :maxHeight) DESC, (maxWidth >= :maxWidth AND maxHeight >= :maxHeight) DESC, (((maxWidth < :maxWidth AND maxHeight < :maxHeight) * -1000 + 1) * ((maxWidth * maxHeight) - (:maxWidth * :maxHeight))) ASC LIMIT 0,1", + ((request.version != nil) ? @"AND version = :version" : @""), + ((request.structureDescription != nil) ? @"AND structDesc = :structDesc" : @"")] + withNamedParameters:@{ + @"type" : request.type, + @"identifier" : request.identifier, + @"version" : OCSQLiteNullProtect(request.version), + @"structDesc" : OCSQLiteNullProtect(request.structureDescription), + @"maxWidth" : @(request.maxPixelSize.width), + @"maxHeight" : @(request.maxPixelSize.height), + } resultHandler:resultHandler]]; + } +} + +- (void)removeResourceOfType:(OCResourceType)type identifier:(OCResourceIdentifier)identifier completionHandler:(OCResourceStoreCompletionHandler)completionHandler +{ + [self.sqlDB executeQuery:[OCSQLiteQuery query:@"DELETE FROM thumb.resources WHERE identifier = :identifier AND type = :type" withNamedParameters:@{ // relatedTo:OCDatabaseTableNameResources + @"type" : type, + @"identifier" : identifier, + } resultHandler:^(OCSQLiteDB *db, NSError *error, OCSQLiteTransaction *transaction, OCSQLiteResultSet *resultSet) { + if (completionHandler != nil) + { + completionHandler(error); + } + }]]; +} + +@end diff --git a/ownCloudSDK/Core/Resources/Sources/OCResourceSource.h b/ownCloudSDK/Core/Resources/Sources/OCResourceSource.h new file mode 100644 index 00000000..03a0334c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCResourceSource.h @@ -0,0 +1,68 @@ +// +// OCResourceSource.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceTypes.h" +#import "OCResource.h" +#import "OCResourceRequest.h" +#import "OCEvent.h" +#import "OCResourceManagerJob.h" + +@class OCCore; +@class OCResourceManager; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^OCResourceSourceResultHandler)(NSError * _Nullable error, OCResource * _Nullable resource); + +typedef NS_ENUM(NSInteger, OCResourceSourcePriority) +{ + OCResourceSourcePriorityNone = 0, //!< Do not use source + OCResourceSourcePriorityRemote = 100, //!< Source fetches resource remotely + OCResourceSourcePriorityLocalFallback = 200, //!< Source returns a locally generated fallback resource + OCResourceSourcePriorityLocal = 300, //!< Source returns a locally generated resource + OCResourceSourcePriorityLocalCache = 400, //!< Source returns resource from local cache + OCResourceSourcePriorityInstant = 500 //!< Source makes a resource available instantly (for trivial fallback resources) +}; + +@interface OCResourceSource : NSObject + +@property(weak,nullable) OCResourceManager *manager; + +@property(strong,readonly) OCResourceSourceIdentifier identifier; +@property(strong,readonly) OCResourceType type; + +@property(weak,nullable) OCCore *core; +@property(readonly,strong) OCEventHandlerIdentifier eventHandlerIdentifier; + +- (instancetype)initWithCore:(OCCore *)core; + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type; //!< Returns the priority with which the source should be used, allowing to establish an ordering +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request; //!< Returns which quality the source can deliver the requested resource in + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler; //!< Returns the resource for a request + +#pragma mark - Event handler convenience integration +@property(readonly,nonatomic) BOOL shouldRegisterEventHandler; + +- (void)registerEventHandler; +- (void)unregisterEventHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Sources/OCResourceSource.m b/ownCloudSDK/Core/Resources/Sources/OCResourceSource.m new file mode 100644 index 00000000..c60027b4 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCResourceSource.m @@ -0,0 +1,106 @@ +// +// OCResourceSource.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSource.h" +#import "NSError+OCError.h" +#import "OCCore.h" +#import "OCCore+Internal.h" +#import "OCMacros.h" + +@implementation OCResourceSource + +- (instancetype)initWithCore:(OCCore *)core +{ + if ((self = [super init]) != nil) + { + _core = core; + _eventHandlerIdentifier = [NSString stringWithFormat:@"%@-%@-%@", NSStringFromClass(self.class), self.identifier, core.bookmark.uuid.UUIDString]; + } + + return (self); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityNone); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + if (resultHandler != nil) + { + resultHandler(OCError(OCErrorFeatureNotImplemented), nil); + } +} + +#pragma mark - Event handling integration +- (BOOL)shouldRegisterEventHandler +{ + return (NO); +} + +- (void)registerEventHandler +{ + [OCEvent registerEventHandler:self forIdentifier:self.eventHandlerIdentifier]; +} + +- (void)unregisterEventHandler +{ + [OCEvent registerEventHandler:nil forIdentifier:self.eventHandlerIdentifier]; +} + +- (void)handleEvent:(OCEvent *)event sender:(id)sender +{ + NSString *eventActivityString = [[NSString alloc] initWithFormat:@"Handling event %@ (resource source)", event]; + + [self.core beginActivity:eventActivityString]; + + NSString *selectorName; + + if ((selectorName = OCTypedCast(event.userInfo[OCEventUserInfoKeySelector], NSString)) != nil) + { + // Selector specified -> route event directly to selector + SEL eventHandlingSelector; + + if ((eventHandlingSelector = NSSelectorFromString(selectorName)) != NULL) + { + // Below is identical to [self performSelector:eventHandlingSelector withObject:event withObject:sender], but in an ARC-friendly manner. + void (*impFunction)(id, SEL, OCEvent *, id) = (void *)[((NSObject *)self) methodForSelector:eventHandlingSelector]; + + [self.core queueBlock:^{ + if (impFunction != NULL) + { + impFunction(self, eventHandlingSelector, event, sender); + } + + [self.core endActivity:eventActivityString]; + }]; + } + } + else + { + OCTLogError(@[@"ResMan"], @"Unroutable resource source event: %@", event); + } +} + +@end diff --git a/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.h b/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.h new file mode 100644 index 00000000..c2b54f98 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceStorage.h +// ownCloudSDK +// +// Created by Felix Schwarz on 03.01.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 "OCResourceSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceStorage : OCResourceSource + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierStorage; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.m b/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.m new file mode 100644 index 00000000..ab9051c8 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Sources/OCResourceSourceStorage.m @@ -0,0 +1,88 @@ +// +// OCResourceSourceStorage.m +// ownCloudSDK +// +// Created by Felix Schwarz on 03.01.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 "OCResourceSourceStorage.h" +#import "OCResourceManager.h" +#import "OCMacros.h" +#import "OCItem.h" + +@implementation OCResourceSourceStorage + +- (OCResourceType)type +{ + return (OCResourceTypeAny); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierStorage); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityLocalCache); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request.type isEqual:OCResourceTypeItemThumbnail]) + { + if ((request.maxPixelSize.width > 0) && (request.maxPixelSize.height > 0)) + { + OCItem *item; + + if ((item = OCTypedCast(request.reference, OCItem)) != nil) + { + if (item.type == OCItemTypeFile) + { + return (OCResourceQualityCached); + } + } + } + + return (OCResourceQualityNone); + } + + if ([request.type isEqual:OCResourceTypeAvatar]) + { + if ((request.maxPixelSize.width > 0) && (request.maxPixelSize.height > 0)) + { + if (OCTypedCast(request.reference, OCUser) != nil) + { + return (OCResourceQualityCached); + } + } + + return (OCResourceQualityNone); + } + + return (OCResourceQualityCached); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + [self.manager retrieveResourceForRequest:request completionHandler:^(NSError * _Nullable error, OCResource * _Nullable resource) { + resource.originSourceIdentifier = OCResourceSourceIdentifierStorage; + + resultHandler(error, resource); + }]; +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierStorage = @"vault.storage"; diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.h b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.h new file mode 100644 index 00000000..56e285cd --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.h @@ -0,0 +1,31 @@ +// +// OCResourceRequestItemThumbnail.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceRequestImage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceRequestItemThumbnail : OCResourceRequestImage + +@property(strong,readonly,nonatomic) OCItem *item; + ++ (instancetype)requestThumbnailFor:(OCItem *)item maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.m b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.m new file mode 100644 index 00000000..15961d85 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceRequestItemThumbnail.m @@ -0,0 +1,55 @@ +// +// OCResourceRequestItemThumbnail.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceRequestItemThumbnail.h" +#import "OCResource.h" +#import "OCItem+OCThumbnail.h" + +@implementation OCResourceRequestItemThumbnail + ++ (instancetype)requestThumbnailFor:(OCItem *)item maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(OCResourceRequestChangeHandler)changeHandler +{ + OCResourceRequestItemThumbnail *request = [[self alloc] initWithType:OCResourceTypeItemThumbnail identifier:item.fileID]; + + if (scale == 0) + { + scale = UIScreen.mainScreen.scale; + } + + request.version = item.eTag; + request.remoteVersion = item.eTag; + request.structureDescription = item.thumbnailSpecID; + + request.reference = item; + + request.maxPointSize = requestedMaximumSizeInPoints; + request.scale = scale; + + request.waitForConnectivity = waitForConnectivity; + + request.changeHandler = changeHandler; + + return (request); +} + +- (OCItem *)item +{ + return (self.reference); +} + +@end diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.h b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.h new file mode 100644 index 00000000..f4b7ae5c --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceItemLocalThumbnails.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceItemLocalThumbnails : OCResourceSource + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierItemLocalThumbnails; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.m b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.m new file mode 100644 index 00000000..22fe5687 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemLocalThumbnails.m @@ -0,0 +1,144 @@ +// +// OCResourceSourceItemLocalThumbnails.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSourceItemLocalThumbnails.h" +#import "OCResourceRequestItemThumbnail.h" +#import "OCCore.h" +#import "OCMacros.h" +#import "OCResourceImage.h" +#import "OCItem+OCThumbnail.h" +#import "OCItem.h" +#import "NSError+OCError.h" + +#import +#import + +@implementation OCResourceSourceItemLocalThumbnails + +- (OCResourceType)type +{ + return (OCResourceTypeItemThumbnail); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierItemLocalThumbnails); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityLocal); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if (@available(iOS 13, macOS 10.15, *)) + { + if ([request isKindOfClass:OCResourceRequestItemThumbnail.class] && [request.reference isKindOfClass:OCItem.class]) + { + OCItem *item; + + if ((item = OCTypedCast(request.reference, OCItem)) != nil) + { + if ((item.type == OCItemTypeFile) && + ([self.core localCopyOfItem:item] != nil)) + { + return (OCResourceQualityHigh); + } + } + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + if (@available(iOS 13, macOS 10.15, *)) + { + OCItem *item; + + if ((OCTypedCast(request, OCResourceRequestItemThumbnail) != nil) && + ((item = OCTypedCast(request.reference, OCItem)) != nil)) + { + NSURL *localURL; + + if ((localURL = [[self.core localCopyOfItem:item] absoluteURL]) != nil) // absoluteURL is needed. For relative URLs QLThumbnailGenerator will return error: QLThumbnailErrorDomain, Code=3 "No thumbnail in the cloud…" + { + QLThumbnailGenerationRequest *thumbnailRequest = [[QLThumbnailGenerationRequest alloc] initWithFileAtURL:localURL size:request.maxPointSize scale:request.scale representationTypes:(QLThumbnailGenerationRequestRepresentationTypeLowQualityThumbnail | QLThumbnailGenerationRequestRepresentationTypeThumbnail)]; + + [QLThumbnailGenerator.sharedGenerator generateBestRepresentationForRequest:thumbnailRequest completionHandler:^(QLThumbnailRepresentation * _Nullable thumbnail, NSError * _Nullable error) { + CGImageRef imageRef; + + if (error != nil) + { + resultHandler(error, nil); + return; + } + + if ((imageRef = thumbnail.CGImage) != NULL) + { + NSMutableData *imageData = [NSMutableData new]; + + CGImageDestinationRef imageDestinationRef; + + if ((imageDestinationRef = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, kUTTypeJPEG, 1, NULL)) != NULL) + { + CGImageDestinationSetProperties(imageDestinationRef, (__bridge CFDictionaryRef)@{ (__bridge id)kCGImageDestinationLossyCompressionQuality : @(1.0) }); + CGImageDestinationAddImage(imageDestinationRef, imageRef, NULL); + + bool success = CGImageDestinationFinalize(imageDestinationRef); + + CFRelease(imageDestinationRef); + imageDestinationRef = NULL; + + if (success) + { + OCResourceImage *thumbnailImage; + + if ((thumbnailImage = [[OCResourceImage alloc] initWithRequest:request]) != nil) + { + thumbnailImage.quality = OCResourceQualityHigh; + + thumbnailImage.mimeType = @"image/jpeg"; + thumbnailImage.data = imageData; + + // thumbnailImage.thumbnail.image = thumbnail.UIImage; + + resultHandler(nil, thumbnailImage); + + return; + } + } + } + } + + resultHandler(OCError(OCErrorFeatureNotSupportedForItem), nil); + }]; + + return; + } + } + } + + resultHandler(OCError(OCErrorFeatureNotSupportedForItem), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierItemLocalThumbnails = @"core.item-local-thumbnails"; diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.h b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.h new file mode 100644 index 00000000..069f6804 --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceItemThumbnails.h +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSource.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceItemThumbnails : OCResourceSource + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierItemThumbnails; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.m b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.m new file mode 100644 index 00000000..1f1d81ed --- /dev/null +++ b/ownCloudSDK/Core/Resources/Thumbnails/OCResourceSourceItemThumbnails.m @@ -0,0 +1,126 @@ +// +// OCResourceSourceItemThumbnails.m +// ownCloudSDK +// +// Created by Felix Schwarz on 27.02.21. +// Copyright © 2021 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2021, 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 "OCResourceSourceItemThumbnails.h" +#import "OCResourceRequestItemThumbnail.h" +#import "OCCore.h" +#import "OCMacros.h" +#import "OCResourceImage.h" +#import "OCItem+OCThumbnail.h" +#import "OCItem.h" +#import "NSError+OCError.h" + +@implementation OCResourceSourceItemThumbnails + +- (OCResourceType)type +{ + return (OCResourceTypeItemThumbnail); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierItemThumbnails); +} + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityRemote); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request isKindOfClass:OCResourceRequestItemThumbnail.class] && [request.reference isKindOfClass:OCItem.class]) + { + OCItem *item; + + if ((item = OCTypedCast(request.reference, OCItem)) != nil) + { + if (item.type == OCItemTypeFile) + { + return (OCResourceQualityNormal); + } + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + OCResourceRequestItemThumbnail *thumbnailRequest; + OCItem *item; + + if (((thumbnailRequest = OCTypedCast(request, OCResourceRequestItemThumbnail)) != nil) && + ((item = OCTypedCast(thumbnailRequest.reference, OCItem)) != nil)) + { + OCConnection *connection; + + if (item.thumbnailAvailability == OCItemThumbnailAvailabilityNone) + { + // Do not initiate a thumbnail request for items that indicate no thumbnail is available + resultHandler(nil, nil); + return; + } + + if ((connection = self.core.connection) != nil) + { + NSString *specID = item.thumbnailSpecID; + NSProgress *progress = nil; + + progress = [connection retrieveThumbnailFor:item to:nil maximumSize:request.maxPixelSize waitForConnectivity:request.waitForConnectivity resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + OCItemThumbnail *thumbnail; + + if (event.error != nil) + { + resultHandler(event.error, nil); + } + else if ((thumbnail = event.result) != nil) + { + OCResourceImage *resource = [[OCResourceImage alloc] initWithRequest:request]; + + // Map thumbnail to corresponding resource fields + resource.identifier = thumbnail.itemVersionIdentifier.fileID; + resource.version = thumbnail.itemVersionIdentifier.eTag; + resource.structureDescription = specID; + + // Transfer thumbnail image properties / data to resource + resource.maxPixelSize = thumbnail.maxPixelSize; + resource.data = thumbnail.data; + + resource.image = thumbnail; + + resource.quality = OCResourceQualityNormal; + + resultHandler(nil, resource); + } + } userInfo:nil ephermalUserInfo:nil]]; + + request.job.cancellationHandler = ^{ + [progress cancel]; + }; + + return; + } + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierItemThumbnails = @"core.item-thumbnails"; diff --git a/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.h b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.h new file mode 100644 index 00000000..431de632 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.h @@ -0,0 +1,33 @@ +// +// OCResourceRequestDriveItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResourceRequest.h" +#import "OCDrive.h" +#import "GADriveItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceRequestDriveItem : OCResourceRequest + +@property(strong,readonly,nonatomic) GADriveItem *driveItem; + ++ (instancetype)requestDriveItem:(GADriveItem *)driveItem waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.m b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.m new file mode 100644 index 00000000..d45d2822 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceRequestDriveItem.m @@ -0,0 +1,46 @@ +// +// OCResourceRequestDriveItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResourceRequestDriveItem.h" +#import "OCResource.h" +#import "GAOpenGraphFile.h" + +@implementation OCResourceRequestDriveItem + ++ (instancetype)requestDriveItem:(GADriveItem *)driveItem waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler +{ + OCResourceRequestDriveItem *request = [[self alloc] initWithType:OCResourceTypeDriveItem identifier:driveItem.identifier]; + + request.version = driveItem.eTag; + request.structureDescription = driveItem.file.mimeType; + + request.reference = driveItem; + + request.waitForConnectivity = waitForConnectivity; + + request.changeHandler = changeHandler; + + return (request); +} + +- (GADriveItem *)driveItem +{ + return (self.reference); +} + +@end diff --git a/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.h b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.h new file mode 100644 index 00000000..9a8a0eb2 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceDriveItems.h +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResourceSourceURL.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceDriveItems : OCResourceSourceURL + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierDriveItem; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.m b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.m new file mode 100644 index 00000000..9ee62878 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/DriveItems/OCResourceSourceDriveItems.m @@ -0,0 +1,73 @@ +// +// OCResourceSourceDriveItems.m +// ownCloudSDK +// +// Created by Felix Schwarz on 12.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 "OCResourceSourceDriveItems.h" +#import "OCMacros.h" +#import "OCResource.h" +#import "OCResourceTypes.h" +#import "OCResourceImage.h" +#import "OCResourceText.h" +#import "OCResourceRequestDriveItem.h" +#import "OCCore.h" +#import "NSError+OCError.h" + +@implementation OCResourceSourceDriveItems + +- (OCResourceType)type +{ + return (OCResourceTypeDriveItem); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierDriveItem); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request isKindOfClass:OCResourceRequestDriveItem.class] && [request.reference isKindOfClass:GADriveItem.class]) + { + if (OCTypedCast(request.reference, GADriveItem) != nil) + { + return (OCResourceQualityNormal); + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + OCResourceRequestDriveItem *driveItemRequest; + GADriveItem *driveItem; + NSURL *url; + + if (((driveItemRequest = OCTypedCast(request, OCResourceRequestDriveItem)) != nil) && + ((driveItem = OCTypedCast(driveItemRequest.reference, GADriveItem)) != nil) && + ((url = driveItem.webDavUrl) != nil)) + { + [super provideResourceForRequest:driveItemRequest url:url eTag:nil resultHandler:resultHandler]; + return; + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierDriveItem = @"core.driveItem"; diff --git a/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.h b/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.h new file mode 100644 index 00000000..daff5e4a --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.h @@ -0,0 +1,30 @@ +// +// OCResourceSourceURL.h +// ownCloudSDK +// +// Created by Felix Schwarz on 22.05.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCResourceSource.h" +#import "OCTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceURL : OCResourceSource + +- (void)provideResourceForRequest:(OCResourceRequest *)request url:(NSURL *)url eTag:(nullable OCFileETag)eTag resultHandler:(OCResourceSourceResultHandler)resultHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.m b/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.m new file mode 100644 index 00000000..bd53dfe6 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/OCResourceSourceURL.m @@ -0,0 +1,132 @@ +// +// OCResourceSourceURL.m +// ownCloudSDK +// +// Created by Felix Schwarz on 22.05.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCResourceSourceURL.h" +#import "OCCore.h" +#import "OCConnection.h" +#import "OCMacros.h" +#import "OCResourceImage.h" +#import "OCResourceText.h" +#import "NSError+OCError.h" + +@implementation OCResourceSourceURL + +- (OCResourceSourcePriority)priorityForType:(OCResourceType)type +{ + return (OCResourceSourcePriorityRemote); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)resourceRequest url:(NSURL *)url eTag:(OCFileETag)eTag resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + if (url != nil) + { + OCConnection *connection; + + if ((connection = self.core.connection) != nil) + { + NSProgress *progress = nil; + OCResource *existingResource = resourceRequest.resource; + OCFileETag existingETag = existingResource.remoteVersion; + + OCHTTPRequest *httpRequest = [OCHTTPRequest requestWithURL:url]; + httpRequest.requiredSignals = resourceRequest.waitForConnectivity ? connection.actionSignals : connection.propFindSignals; + httpRequest.redirectPolicy = OCHTTPRequestRedirectPolicyAllowSameHost; + + if (existingETag != nil) + { + [httpRequest addHeaderFields:@{ + @"If-None-Match" : existingETag + }]; + } + + progress = [connection sendRequest:httpRequest ephermalCompletionHandler:^(OCHTTPRequest * _Nonnull request, OCHTTPResponse * _Nullable response, NSError * _Nullable error) { + if (error != nil) + { + resultHandler(error, nil); + } + else if (response.status.code == OCHTTPStatusCodeOK) + { + NSString *contentType = response.contentType; + OCResource *returnResource = nil; + + resourceRequest.remoteVersion = response.headerFields[OCHTTPHeaderFieldNameETag]; // propagate ETag to resource init calls + + if ([contentType hasPrefix:@"image/"]) + { + // Image + OCResourceImage *resource = [[OCResourceImage alloc] initWithRequest:resourceRequest]; + + resource.data = response.bodyData; + + returnResource = resource; + } + + if ([contentType hasPrefix:@"text/"]) + { + // Text + OCResourceText *resource = [[OCResourceText alloc] initWithRequest:resourceRequest]; + + resource.text = [response bodyAsStringWithFallbackEncoding:NSUTF8StringEncoding]; // takes encoding passed in Content-Type into account, defaults to UTF-8 + + returnResource = resource; + } + + if (returnResource == nil) + { + // Data + returnResource = [[OCResource alloc] initWithRequest:resourceRequest]; + } + + returnResource.quality = OCResourceQualityNormal; + returnResource.mimeType = contentType; + + resultHandler(nil, returnResource); + } + else if (response.status.code == OCHTTPStatusCodeNOT_MODIFIED) + { + // Remote URL resource has not been modified (If-None-Match fulfilled) + // + // Passing back no resource since the existing resource already is identical: + // - considered passing back the existing resource with an updated version, but this + // would likely only create unnecessary overhead for storing the same data again and + // sending out updates + // - resource job processing should complete regardless because the result handler was called + resultHandler(nil, nil); + } + else if (response.status.code == OCHTTPStatusCodeNOT_FOUND) + { + resultHandler(OCError(OCErrorResourceDoesNotExist), nil); + } + else + { + resultHandler(response.status.error, nil); + } + }]; + + resourceRequest.job.cancellationHandler = ^{ + [progress cancel]; + }; + + return; + } + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end diff --git a/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.h b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.h new file mode 100644 index 00000000..57ad7247 --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.h @@ -0,0 +1,36 @@ +// +// OCResourceRequestURLItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 07.09.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceRequestURLItem : OCResourceRequest + +@property(strong,readonly,nonatomic) NSURL *url; + +@property(class,strong,readonly,nonatomic) OCResourceVersion daySpecificVersion; //!< Generated resource version that changes every day. Can be used to force resource refreshes once every day. +@property(class,strong,readonly,nonatomic) OCResourceVersion weekSpecificVersion; //!< Generated resource version that changes every week. Can be used to force resource refreshes once every week. +@property(class,strong,readonly,nonatomic) OCResourceVersion monthSpecificVersion; //!< Generated resource version that changes every month. Can be used to force resource refreshes once every month. +@property(class,strong,readonly,nonatomic) OCResourceVersion yearSpecificVersion; //!< Generated resource version that changes every year. Can be used to force resource refreshes once every year. + ++ (instancetype)requestURLItem:(NSURL *)url identifier:(nullable OCResourceIdentifier)identifier version:(nullable OCResourceVersion)version structureDescription:(nullable OCResourceStructureDescription)structureDescription waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.m b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.m new file mode 100644 index 00000000..442da39a --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceRequestURLItem.m @@ -0,0 +1,73 @@ +// +// OCResourceRequestURLItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 07.09.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 "OCResourceRequestURLItem.h" +#import "OCResource.h" +#import "NSDate+OCDateParser.h" + +@implementation OCResourceRequestURLItem + ++ (OCResourceVersion)daySpecificVersion +{ + return ([NSDate new].compactUTCStringDateOnly); +} + ++ (OCResourceVersion)weekSpecificVersion +{ + NSDateComponents *components = [NSCalendar.autoupdatingCurrentCalendar components:NSCalendarUnitWeekOfYear|NSCalendarUnitYearForWeekOfYear fromDate:[NSDate new]]; + return ([NSString stringWithFormat:@"W%ldY%ld", components.weekOfYear, components.yearForWeekOfYear]); +} + ++ (OCResourceVersion)monthSpecificVersion +{ + NSDateComponents *components = [NSCalendar.autoupdatingCurrentCalendar components:NSCalendarUnitWeekOfMonth|NSCalendarUnitYear fromDate:[NSDate new]]; + return ([NSString stringWithFormat:@"M%ldY%ld", components.month, components.year]); +} + ++ (OCResourceVersion)yearSpecificVersion +{ + NSDateComponents *components = [NSCalendar.autoupdatingCurrentCalendar components:NSCalendarUnitYear fromDate:[NSDate new]]; + return ([NSString stringWithFormat:@"Y%ld", components.year]); +} + ++ (instancetype)requestURLItem:(NSURL *)url identifier:(nullable OCResourceIdentifier)identifier version:(nullable OCResourceVersion)version structureDescription:(nullable OCResourceStructureDescription)structureDescription waitForConnectivity:(BOOL)waitForConnectivity changeHandler:(nullable OCResourceRequestChangeHandler)changeHandler +{ + if (identifier == nil) { + identifier = url.absoluteString; + } + + OCResourceRequestURLItem *request = [[self alloc] initWithType:OCResourceTypeURLItem identifier:identifier]; + + request.version = version; + request.structureDescription = structureDescription; + + request.reference = url; + + request.waitForConnectivity = waitForConnectivity; + + request.changeHandler = changeHandler; + + return (request); +} + +- (NSURL *)url +{ + return (self.reference); +} + +@end diff --git a/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.h b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.h new file mode 100644 index 00000000..c0b1376c --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.h @@ -0,0 +1,29 @@ +// +// OCResourceSourceURLItems.h +// ownCloudSDK +// +// Created by Felix Schwarz on 07.09.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 "OCResourceSourceURL.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCResourceSourceURLItems : OCResourceSourceURL + +@end + +extern OCResourceSourceIdentifier OCResourceSourceIdentifierURLItem; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.m b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.m new file mode 100644 index 00000000..84409e0f --- /dev/null +++ b/ownCloudSDK/Core/Resources/URL-based/URLItems/OCResourceSourceURLItems.m @@ -0,0 +1,64 @@ +// +// OCResourceSourceURLItems.m +// ownCloudSDK +// +// Created by Felix Schwarz on 07.09.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 "OCResourceSourceURLItems.h" +#import "OCResourceRequestURLItem.h" + +@implementation OCResourceSourceURLItems + +- (OCResourceType)type +{ + return (OCResourceTypeURLItem); +} + +- (OCResourceSourceIdentifier)identifier +{ + return (OCResourceSourceIdentifierURLItem); +} + +- (OCResourceQuality)qualityForRequest:(OCResourceRequest *)request +{ + if ([request isKindOfClass:OCResourceRequestURLItem.class] && [request.reference isKindOfClass:NSURL.class]) + { + if (OCTypedCast(request.reference, NSURL) != nil) + { + return (OCResourceQualityNormal); + } + } + + return (OCResourceQualityNone); +} + +- (void)provideResourceForRequest:(OCResourceRequest *)request resultHandler:(OCResourceSourceResultHandler)resultHandler +{ + OCResourceRequestURLItem *urlItemRequest; + NSURL *url; + + if (((urlItemRequest = OCTypedCast(request, OCResourceRequestURLItem)) != nil) && + ((url = OCTypedCast(urlItemRequest.reference, NSURL)) != nil)) + { + [super provideResourceForRequest:urlItemRequest url:url eTag:nil resultHandler:resultHandler]; + return; + } + + resultHandler(OCError(OCErrorInsufficientParameters), nil); +} + +@end + +OCResourceSourceIdentifier OCResourceSourceIdentifierURLItem = @"core.urlItem"; diff --git a/ownCloudSDK/Core/Sharing/OCCore+Sharing.m b/ownCloudSDK/Core/Sharing/OCCore+Sharing.m index f824c5f2..2094a158 100644 --- a/ownCloudSDK/Core/Sharing/OCCore+Sharing.m +++ b/ownCloudSDK/Core/Sharing/OCCore+Sharing.m @@ -25,6 +25,9 @@ #import "NSURL+OCPrivateLink.h" #import "NSProgress+OCExtensions.h" #import "OCLogger.h" +#import "NSError+OCHTTPStatus.h" +#import "NSArray+OCFiltering.h" +#import "OCMacros.h" @implementation OCCore (Sharing) @@ -155,36 +158,35 @@ - (void)_pollForSharesWithScope:(OCShareScope)scope item:(OCItem *)item completi if (core != nil) { - if (error == nil) + if (error != nil) { - [core beginActivity:@"Updating retrieved shares"]; + // Output error only if not due to status 404 + if (![error isHTTPStatusErrorWithCode:OCHTTPStatusCodeNOT_FOUND]) + { + OCLogError(@"Error retrieving shares of scope %ld for %@: %@", scope, item, error); + } + } - [core queueBlock:^{ - OCCore *core = weakCore; + [core beginActivity:@"Updating retrieved shares"]; - if (core != nil) - { - for (OCShareQuery *query in core->_shareQueries) - { - [query _updateWithRetrievedShares:shares forItem:item scope:scope]; - } - } + [core queueBlock:^{ + OCCore *core = weakCore; - if (completionHandler != nil) + if (core != nil) + { + for (OCShareQuery *query in core->_shareQueries) { - completionHandler(); + [query _updateWithRetrievedShares:((error == nil) ? shares : @[]) forItem:item scope:scope]; } + } - [core endActivity:@"Updating retrieved shares"]; - }]; - } - else - { if (completionHandler != nil) { completionHandler(); } - } + + [core endActivity:@"Updating retrieved shares"]; + }]; } else { @@ -211,25 +213,25 @@ - (void)_updateShareQueriesWithAddedShare:(nullable OCShare *)addedShare updated if (removedShare != nil) { // Update parent path of removed items to quickly bring the item back in sync - if (removedShare.itemPath.parentPath != nil) + if (removedShare.itemLocation.parentLocation != nil) { - [self scheduleItemListTaskForPath:removedShare.itemPath.parentPath forDirectoryUpdateJob:nil withMeasurement:nil]; + [self scheduleItemListTaskForLocation:removedShare.itemLocation.parentLocation forDirectoryUpdateJob:nil withMeasurement:nil]; } } else if (updatedShare != nil) { - if ([updatedShare.state isEqual:OCShareStateRejected]) + if ([updatedShare.state isEqual:OCShareStateDeclined]) { // Update parent path of removed items to quickly bring the item back in sync - if (updatedShare.itemPath.parentPath != nil) + if (updatedShare.itemLocation.parentLocation != nil) { - [self scheduleItemListTaskForPath:updatedShare.itemPath.parentPath forDirectoryUpdateJob:nil withMeasurement:nil]; + [self scheduleItemListTaskForLocation:updatedShare.itemLocation.parentLocation forDirectoryUpdateJob:nil withMeasurement:nil]; } } else { // Update item metadata to quickly bring the item up-to-date - [self scheduleItemListTaskForPath:updatedShare.itemPath forDirectoryUpdateJob:nil withMeasurement:nil]; + [self scheduleItemListTaskForLocation:updatedShare.itemLocation forDirectoryUpdateJob:nil withMeasurement:nil]; } } else if (addedShare != nil) @@ -238,12 +240,12 @@ - (void)_updateShareQueriesWithAddedShare:(nullable OCShare *)addedShare updated if ([addedShare.owner isEqual:self->_connection.loggedInUser]) { // Shared by user - [self scheduleItemListTaskForPath:addedShare.itemPath forDirectoryUpdateJob:nil withMeasurement:nil]; + [self scheduleItemListTaskForLocation:addedShare.itemLocation forDirectoryUpdateJob:nil withMeasurement:nil]; } else { // Shared with user (typically added to root dir. Should it ever not, will still trigger retrieval of updates.) - [self scheduleItemListTaskForPath:@"/" forDirectoryUpdateJob:nil withMeasurement:nil]; + [self scheduleItemListTaskForLocation:[[OCLocation alloc] initWithDriveID:addedShare.itemLocation.driveID path:@"/"] forDirectoryUpdateJob:nil withMeasurement:nil]; } } }]; @@ -302,6 +304,7 @@ - (nullable NSProgress *)makeDecisionOnShare:(OCShare *)share accept:(BOOL)accep { OCProgress *progress; + progress = [self.connection makeDecisionOnShare:share accept:accept resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { if (event.error == nil) { @@ -309,11 +312,11 @@ - (nullable NSProgress *)makeDecisionOnShare:(OCShare *)share accept:(BOOL)accep { case OCShareTypeUserShare: case OCShareTypeGroupShare: - share.state = accept ? OCShareStateAccepted : OCShareStateRejected; + share.state = accept ? OCShareStateAccepted : OCShareStateDeclined; [self _updateShareQueriesWithAddedShare:nil updatedShare:share removedShare:nil limitScope:@(OCShareScopeSharedWithUser)]; break; - case OCShareTypeRemote: + case OCShareTypeRemote: { share.accepted = @(accept); if (accept) @@ -326,6 +329,23 @@ - (nullable NSProgress *)makeDecisionOnShare:(OCShare *)share accept:(BOOL)accep [self _updateShareQueriesWithAddedShare:nil updatedShare:nil removedShare:share limitScope:@(OCShareScopeAcceptedCloudShares)]; [self _updateShareQueriesWithAddedShare:nil updatedShare:nil removedShare:share limitScope:@(OCShareScopePendingCloudShares)]; } + + // Accepting a share can change a share's path (f.ex. if an item with the same name already exists in the target folder), so directly perform a refresh + if (accept) + { + OCShareQuery *acceptedCloudSharesQuery = self->_acceptedCloudSharesQuery; + if (acceptedCloudSharesQuery != nil) + { + [self reloadQuery:acceptedCloudSharesQuery]; + } + + OCShareQuery *pendingCloudSharesQuery = self->_pendingCloudSharesQuery; + if (pendingCloudSharesQuery != nil) + { + [self reloadQuery:pendingCloudSharesQuery]; + } + } + } break; default: break; @@ -338,6 +358,236 @@ - (nullable NSProgress *)makeDecisionOnShare:(OCShare *)share accept:(BOOL)accep return (progress.progress); } +#pragma mark - Roles +- (nullable NSArray *)availableShareRolesForType:(OCShareType)shareType location:(OCLocation *)location +{ + NSArray *roles = nil; + OCLocationType locationType = location.type; + OCShareTypesMask shareTypeMask = [OCShare maskForType:shareType]; + + if (locationType == OCLocationTypeUnknown) + { + return(nil); + } + + @synchronized(_shareRoles) + { + if (_shareRoles.count == 0) + { + // Roles as described in + // - https://github.com/owncloud/ocis/issues/4848#issuecomment-1283678879 + // - https://github.com/owncloud/web/blob/master/packages/web-client/src/helpers/share/role.ts + [_shareRoles addObjectsFromArray:@[ + // # USERS & GROUPS + // ## Viewer + // - files, folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeViewer + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFile|OCLocationTypeFolder + symbolName:@"eye.fill" + localizedName:OCLocalized(@"Viewer") + localizedDescription:OCLocalized(@"Download, preview and share")], + + // - drives + [[OCShareRole alloc] initWithType:OCShareRoleTypeViewer + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeDrive + symbolName:@"eye.fill" + localizedName:OCLocalized(@"Viewer") + localizedDescription:OCLocalized(@"Download and preview")], + + // ## Editor + // - files + [[OCShareRole alloc] initWithType:OCShareRoleTypeEditor + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFile + symbolName:@"pencil" + localizedName:OCLocalized(@"Editor") + localizedDescription:OCLocalized(@"Edit, download, preview and share")], + + // - folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeEditor + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFolder + symbolName:@"pencil" + localizedName:OCLocalized(@"Editor") + localizedDescription:OCLocalized(@"Upload, edit, delete, download, preview and share")], + + // - drives + [[OCShareRole alloc] initWithType:OCShareRoleTypeEditor + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeDrive + symbolName:@"pencil" + localizedName:OCLocalized(@"Editor") + localizedDescription:OCLocalized(@"Upload, edit, delete, download and preview")], + + // ## Manager + // - drives + [[OCShareRole alloc] initWithType:OCShareRoleTypeManager + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeDrive + symbolName:@"person.fill" + localizedName:OCLocalized(@"Manager") + localizedDescription:OCLocalized(@"Upload, edit, delete, download, preview and share")], + + // ## Custom + // - files + [[OCShareRole alloc] initWithType:OCShareRoleTypeCustom + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskUpdate|OCSharePermissionsMaskShare + locations:OCLocationTypeFile + symbolName:@"gearshape.fill" + localizedName:OCLocalized(@"Custom") + localizedDescription:OCLocalized(@"Set detailed permissions")], + + // - folders, drives + [[OCShareRole alloc] initWithType:OCShareRoleTypeCustom + shareTypes:OCShareTypesMaskUserShare|OCShareTypesMaskGroupShare + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete|OCSharePermissionsMaskShare + customizablePermissions:OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete|OCSharePermissionsMaskShare + locations:OCLocationTypeFolder|OCLocationTypeDrive + symbolName:@"gearshape.fill" + localizedName:OCLocalized(@"Custom") + localizedDescription:OCLocalized(@"Set detailed permissions")], + ]]; + + // # LINKS + if (self.useDrives) + { + // ## Internal + // - files, folders + [_shareRoles addObjectsFromArray:@[ + [[OCShareRole alloc] initWithType:OCShareRoleTypeInternal + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskInternal + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFile|OCLocationTypeFolder + symbolName:@"person.fill" + localizedName:OCLocalized(@"Invited persons") + localizedDescription:OCLocalized(@"Only invited persons have access. Login required.")] + ]]; + } + + [_shareRoles addObjectsFromArray:@[ + // ## Viewer + // - files, folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeViewer + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskRead + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFile|OCLocationTypeFolder + symbolName:@"eye.fill" + localizedName:OCLocalized(@"Viewer") + localizedDescription:OCLocalized(@"Recipients can view and download contents.")], + + // ## Uploader + // - folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeUploader + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskCreate + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFolder + symbolName:@"arrow.up.circle.fill" + localizedName:OCLocalized(@"Uploader") + localizedDescription:OCLocalized(@"Recipients can upload but existing contents are not revealed.")], + + // ## Contributor + // - folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeContributor + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskCreate + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFolder + symbolName:@"person.2" + localizedName:OCLocalized(@"Contributor") + localizedDescription:OCLocalized(@"Recipients can view, download and upload contents.")], + + // ## Editor + // - files + [[OCShareRole alloc] initWithType:OCShareRoleTypeEditor + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFile + symbolName:@"pencil" + localizedName:OCLocalized(@"Editor") + localizedDescription:OCLocalized(@"Recipients can view, download and edit contents.")], + + // - folders + [[OCShareRole alloc] initWithType:OCShareRoleTypeEditor + shareTypes:OCShareTypesMaskLink + permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskUpdate|OCSharePermissionsMaskCreate|OCSharePermissionsMaskDelete + customizablePermissions:OCSharePermissionsMaskNone + locations:OCLocationTypeFolder + symbolName:@"pencil" + localizedName:OCLocalized(@"Editor") + localizedDescription:OCLocalized(@"Recipients can view, download, edit, delete and upload contents.")] + ]]; + } + + roles = [_shareRoles filteredArrayUsingBlock:^BOOL(OCShareRole * _Nonnull role, BOOL * _Nonnull stop) { + return ( + // Role supports location + ((role.locations & locationType) != 0) && + + // Role supports share type + ((role.shareTypes & shareTypeMask) == shareTypeMask) + ); + }]; + } + + return (roles); +} + +- (nullable OCShareRole *)matchingShareRoleForShare:(OCShare *)share +{ + NSArray *roles = [self availableShareRolesForType:share.type location:share.itemLocation]; + OCShareRole *customRole = nil; + OCShareRole *exactMatchingRole = nil; + + for (OCShareRole *role in roles) + { + if (exactMatchingRole == nil) + { + if (role.permissions == share.permissions) + { + exactMatchingRole = role; + } + } + + if (customRole == nil) + { + if ((share.permissions & role.permissions) == share.permissions) + { + if ([role.type isEqual:OCShareRoleTypeCustom]) + { + customRole = role; + } + } + } + } + + if (exactMatchingRole == nil) + { + return (customRole); + } + + return (exactMatchingRole); +} + #pragma mark - Recipient access - (OCRecipientSearchController *)recipientSearchControllerForItem:(OCItem *)item { @@ -400,7 +650,7 @@ - (nullable NSProgress *)_retrieveItemForPrivateLink:(NSURL *)privateLink comple __block BOOL trackingCompleted = NO; OCCoreItemTracking tracking; - if ((tracking = [self trackItemAtPath:path trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial) { + if ((tracking = [self trackItemAtLocation:[OCLocation legacyRootPath:path] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial) { BOOL endTracking = NO; if (trackingCompleted) diff --git a/ownCloudSDK/Core/Sharing/OCRecipientSearchController.h b/ownCloudSDK/Core/Sharing/OCRecipientSearchController.h index c05257f9..14bd9550 100644 --- a/ownCloudSDK/Core/Sharing/OCRecipientSearchController.h +++ b/ownCloudSDK/Core/Sharing/OCRecipientSearchController.h @@ -17,7 +17,7 @@ */ #import "OCCore.h" -#import "OCRecipient.h" +#import "OCIdentity.h" NS_ASSUME_NONNULL_BEGIN @@ -43,7 +43,8 @@ NS_ASSUME_NONNULL_BEGIN @property(assign,nonatomic) NSUInteger maximumResultCount; //!< The maximum number of results to return @property(assign,nonatomic) BOOL isWaitingForResults; //!< YES if the search controller is waiting for a result. -@property(nullable,strong,nonatomic) NSArray *recipients; //!< The recipients returned from the server +@property(nullable,strong,nonatomic) NSArray *recipients; //!< The recipients returned from the server +@property(nullable,strong,nonatomic) OCDataSource *recipientsDataSource; //!< Data source wrapping .recipients and .isWaitingForResults @property(weak) id delegate; //!< Delegate receiving events. Alternatively, it's also possible to KVO-observe this class' properties. diff --git a/ownCloudSDK/Core/Sharing/OCRecipientSearchController.m b/ownCloudSDK/Core/Sharing/OCRecipientSearchController.m index 1933a10c..c7da9798 100644 --- a/ownCloudSDK/Core/Sharing/OCRecipientSearchController.m +++ b/ownCloudSDK/Core/Sharing/OCRecipientSearchController.m @@ -20,21 +20,21 @@ #import "OCRateLimiter.h" #import "OCCore+Internal.h" #import "OCMacros.h" +#import "OCDataSourceArray.h" +#import "OCIdentity+DataItem.h" -@interface OCRecipientSearchController () +@implementation OCRecipientSearchController { OCRateLimiter *_rateLimiter; OCItemType _itemType; NSUInteger _activeRetrievalCounter; + OCDataSourceArray *_recipientsDataSource; + NSUInteger _lastSearchID; NSUInteger _searchIDCounter; } -@end - -@implementation OCRecipientSearchController - - (instancetype)initWithCore:(OCCore *)core item:(nonnull OCItem *)item { if ((self = [self init]) != nil) @@ -111,7 +111,9 @@ - (void)_search searchID = _searchIDCounter++; } - [core.connection retrieveRecipientsForItemType:_itemType ofShareType:self.shareTypes searchTerm:self.searchTerm maximumNumberOfRecipients:self.maximumResultCount completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [core.connection retrieveRecipientsForItemType:_itemType ofShareType:self.shareTypes searchTerm:self.searchTerm maximumNumberOfRecipients:self.maximumResultCount completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + OCDataSourceArray *recipientsDataSource = nil; + @synchronized(self) { self->_activeRetrievalCounter--; @@ -129,9 +131,12 @@ - (void)_search } self->_lastSearchID = searchID; + + recipientsDataSource = self->_recipientsDataSource; } self.recipients = recipients; + [recipientsDataSource setVersionedItems:recipients]; if ((self.delegate!=nil) && [self.delegate respondsToSelector:@selector(searchControllerHasNewResults:error:)]) { @@ -141,4 +146,21 @@ - (void)_search } } +- (OCDataSource *)recipientsDataSource +{ + OCDataSource *recipientsDataSource = nil; + + @synchronized(self) + { + if (_recipientsDataSource == nil) + { + _recipientsDataSource = [[OCDataSourceArray alloc] initWithItems:self.recipients]; + } + + recipientsDataSource = _recipientsDataSource; + } + + return (recipientsDataSource); +} + @end diff --git a/ownCloudSDK/Core/Sharing/OCShareQuery.h b/ownCloudSDK/Core/Sharing/OCShareQuery.h index 7ad70fd5..06119701 100644 --- a/ownCloudSDK/Core/Sharing/OCShareQuery.h +++ b/ownCloudSDK/Core/Sharing/OCShareQuery.h @@ -20,6 +20,7 @@ #import "OCCoreQuery.h" #import "OCItem.h" #import "OCShare.h" +#import "OCDataSourceArray.h" NS_ASSUME_NONNULL_BEGIN @@ -41,6 +42,8 @@ typedef void(^OCShareQueryChangesAvailableNotificationHandler)(OCShareQuery *que @property(nullable,copy) OCShareQueryChangesAvailableNotificationHandler initialPopulationHandler; //!< If set, this block is called when the query results have been initially populated with the first server response triggered by the query. Once the block was called, this property is set to nil. @property(nullable,copy) OCShareQueryChangesAvailableNotificationHandler changesAvailableNotificationHandler; //!< If set, this block is called whenever the .queryResults have changed. +@property(readonly,nonatomic) OCDataSourceArray *dataSource; //!< Data source (created on demand) serving query results + + (nullable instancetype)queryWithScope:(OCShareScope)scope item:(nullable OCItem *)item; @end diff --git a/ownCloudSDK/Core/Sharing/OCShareQuery.m b/ownCloudSDK/Core/Sharing/OCShareQuery.m index 778e40c6..c1b1d02b 100644 --- a/ownCloudSDK/Core/Sharing/OCShareQuery.m +++ b/ownCloudSDK/Core/Sharing/OCShareQuery.m @@ -19,17 +19,20 @@ #import "OCShareQuery.h" #import "OCShareQuery+Internal.h" #import "OCLogger.h" +#import "OCDrive.h" +#import "OCShare+OCDataItem.h" +#import "OCCore+DataSources.h" -@interface OCShareQuery () +@implementation OCShareQuery { NSMutableDictionary *_sharesByID; NSMutableArray *_shares; NSArray *_queryResults; -} -@end + OCDataSourceArray *_dataSource; +} -@implementation OCShareQuery +@dynamic dataSource; #pragma mark - Convenience initializers + (instancetype)queryWithScope:(OCShareScope)scope item:(OCItem *)item @@ -89,6 +92,12 @@ - (void)updateQueryResultsWithBlock:(dispatch_block_t)updateBlock { updateBlock(); } + + // Update dataSource (if exists) + if (_dataSource != nil) + { + [_dataSource setVersionedItems:[self->_shares sortedArrayUsingComparator:OCCore.sharesSortComparator]]; + } } [self didChangeValueForKey:@"queryResults"]; @@ -205,14 +214,14 @@ - (void)_updateWithAddedShare:(nullable OCShare *)addedShare updatedShare:(nulla case OCShareScopeItemWithReshares: if (_item.path != nil) { - doAdd = [addedShare.itemPath isEqual:_item.path]; + doAdd = [addedShare.itemLocation isEqual:_item.location]; } break; case OCShareScopeSubItems: if (_item.path != nil) { - doAdd = [addedShare.itemPath hasPrefix:_item.path]; + doAdd = [addedShare.itemLocation isLocatedIn:_item.location]; } break; } @@ -288,4 +297,19 @@ - (void)_updateWithAddedShare:(nullable OCShare *)addedShare updatedShare:(nulla return (queryResults); } +#pragma mark - Data source +- (OCDataSourceArray *)dataSource +{ + @synchronized(self) + { + if (_dataSource == nil) + { + _dataSource = [[OCDataSourceArray alloc] initWithItems:_queryResults]; + _dataSource.trackItemVersions = YES; // Track item versions, so changes in status can be detected as actual changes + } + } + + return (_dataSource); +} + @end diff --git a/ownCloudSDK/Core/Sync/Actions/CopyMove/OCSyncActionCopyMove.m b/ownCloudSDK/Core/Sync/Actions/CopyMove/OCSyncActionCopyMove.m index 5c69e0dd..e13f4324 100644 --- a/ownCloudSDK/Core/Sync/Actions/CopyMove/OCSyncActionCopyMove.m +++ b/ownCloudSDK/Core/Sync/Actions/CopyMove/OCSyncActionCopyMove.m @@ -18,6 +18,7 @@ #import "OCSyncActionCopyMove.h" #import "NSError+OCNetworkFailure.h" +#import "OCLocaleFilterVariables.h" @interface OCSyncActionCopyMove () { @@ -78,12 +79,22 @@ - (void)preflightWithContext:(OCSyncContext *)syncContext { OCItem *placeholderItem = [OCItem placeholderItemOfType:sourceItem.type]; + // Prevent copying an item into itself + if ([_targetParentItem.location isLocatedIn:sourceItem.location]) + { + syncContext.error = OCErrorWithDescription(OCErrorItemOperationForbidden, OCLocalizedFormat(@"{{itemName}} can't be copied into itself.", @{ + @"itemName" : ((self.localItem.name != nil) ? self.localItem.name : @"Item") + })); + return; + } + // Copy filesystem metadata from existing item [placeholderItem copyFilesystemMetadataFrom:sourceItem]; // Set path and parent folder placeholderItem.parentFileID = self.targetParentItem.fileID; placeholderItem.parentLocalID = self.targetParentItem.localID; + placeholderItem.driveID = self.targetParentItem.driveID; placeholderItem.path = targetPath; // Copy actual file if it exists locally @@ -141,6 +152,15 @@ - (void)preflightWithContext:(OCSyncContext *)syncContext { OCItem *updatedItem; + // Prevent moving an item into itself + if ([_targetParentItem.location isLocatedIn:sourceItem.location]) + { + syncContext.error = OCErrorWithDescription(OCErrorItemOperationForbidden, OCLocalizedFormat(@"{{itemName}} can't be moved into itself.", @{ + @"itemName" : ((self.localItem.name != nil) ? self.localItem.name : @"Item") + })); + return; + } + // Add sync record reference to source item [sourceItem addSyncRecordID:syncContext.syncRecord.recordID activity:OCItemSyncActivityUpdating]; @@ -167,7 +187,7 @@ - (void)preflightWithContext:(OCSyncContext *)syncContext NSMutableArray *updatedItems = [syncContext.updatedItems mutableCopy]; NSMutableArray *updatedLocalIDs = [NSMutableArray new]; - [self.core.vault.database retrieveCacheItemsRecursivelyBelowPath:sourceItem.path includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.core.vault.database retrieveCacheItemsRecursivelyBelowLocation:sourceItem.location includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { for (OCItem *item in items) { item.previousPath = item.path; diff --git a/ownCloudSDK/Core/Sync/Actions/CreateFolder/OCSyncActionCreateFolder.m b/ownCloudSDK/Core/Sync/Actions/CreateFolder/OCSyncActionCreateFolder.m index 7c03d06e..85cef575 100644 --- a/ownCloudSDK/Core/Sync/Actions/CreateFolder/OCSyncActionCreateFolder.m +++ b/ownCloudSDK/Core/Sync/Actions/CreateFolder/OCSyncActionCreateFolder.m @@ -40,6 +40,7 @@ - (instancetype)initWithParentItem:(OCItem *)parentItem folderName:(NSString *)f { placeholderItem.parentFileID = parentItem.fileID; placeholderItem.parentLocalID = parentItem.localID; + placeholderItem.driveID = parentItem.driveID; placeholderItem.path = [parentItem.path pathForSubdirectoryWithName:folderName]; placeholderItem.lastModified = [NSDate date]; placeholderItem.permissions = OCItemPermissionCreateFile|OCItemPermissionCreateFolder|OCItemPermissionDelete|OCItemPermissionRename|OCItemPermissionMove; diff --git a/ownCloudSDK/Core/Sync/Actions/Delete/OCSyncActionDelete.m b/ownCloudSDK/Core/Sync/Actions/Delete/OCSyncActionDelete.m index d6628413..b96cb7d4 100644 --- a/ownCloudSDK/Core/Sync/Actions/Delete/OCSyncActionDelete.m +++ b/ownCloudSDK/Core/Sync/Actions/Delete/OCSyncActionDelete.m @@ -82,7 +82,7 @@ - (void)preflightWithContext:(OCSyncContext *)syncContext NSMutableArray *removedItems = [syncContext.removedItems mutableCopy]; NSMutableArray *removedLocalIDs = [NSMutableArray new]; - [self.core.vault.database retrieveCacheItemsRecursivelyBelowPath:itemToDelete.path includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.core.vault.database retrieveCacheItemsRecursivelyBelowLocation:itemToDelete.location includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { for (OCItem *item in items) { [item addSyncRecordID:syncContext.syncRecord.recordID activity:OCItemSyncActivityDeleting]; @@ -90,7 +90,10 @@ - (void)preflightWithContext:(OCSyncContext *)syncContext OCLogDebug(@"Preflight: delete contained %@", OCLogPrivate(item.path)); [removedItems addObject:item]; - [removedLocalIDs addObject:item.localID]; + if (item.localID != nil) + { + [removedLocalIDs addObject:item.localID]; + } } }]; @@ -229,7 +232,7 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext // Items that are still contained in the deleted item itself if (self.localItem.type == OCItemTypeCollection) { - [self.core.vault.database retrieveCacheItemsRecursivelyBelowPath:self.localItem.path includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.core.vault.database retrieveCacheItemsRecursivelyBelowLocation:self.localItem.location includingPathItself:NO includingRemoved:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { for (OCItem *item in items) { OCLogDebug(@"Success: remove delete contained %@", OCLogPrivate(item.path)); @@ -300,11 +303,11 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext // => also fetch an update of the containing dir, as the missing file could also just have been moved / renamed { - NSString *parentPath = self.localItem.path.parentPath; + OCLocation *parentLocation = self.localItem.location.parentLocation; - if (parentPath != nil) + if (parentLocation != nil) { - syncContext.refreshPaths = @[ parentPath ]; + syncContext.refreshLocations = @[ parentLocation ]; } } diff --git a/ownCloudSDK/Core/Sync/Actions/Download/OCSyncActionDownload.m b/ownCloudSDK/Core/Sync/Actions/Download/OCSyncActionDownload.m index d334ce63..cb9400f9 100644 --- a/ownCloudSDK/Core/Sync/Actions/Download/OCSyncActionDownload.m +++ b/ownCloudSDK/Core/Sync/Actions/Download/OCSyncActionDownload.m @@ -593,9 +593,9 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext // The item wasn't found on the server (either a 404 or a failed precondition HTTP status with respective Sabre error message) // Request refresh of parent path - if (item.path.parentPath != nil) + if (item.location.parentLocation != nil) { - syncContext.refreshPaths = @[ item.path.parentPath ]; + syncContext.refreshLocations = @[ item.location.parentLocation ]; } if ([self.options[OCCoreOptionDownloadTriggerID] isEqual:OCItemDownloadTriggerIDAvailableOffline]) @@ -630,9 +630,9 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext OCLogError(@"Download %@ error %@ => ETag on the server likely changed from the last known ETag", item, downloadError); // Request refresh of parent path - if (item.path.parentPath != nil) + if (item.location.parentLocation != nil) { - syncContext.refreshPaths = @[ item.path.parentPath ]; + syncContext.refreshLocations = @[ item.location.parentLocation ]; } // For anything else: wait for metadata update to happen @@ -643,7 +643,7 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext syncContext.updateStoredSyncRecordAfterItemUpdates = YES; // Update sync record in db, so resolutionRetries is persisted [syncContext transitionToState:OCSyncRecordStateReady withWaitConditions:@[ - [[OCWaitConditionMetaDataRefresh waitForPath:item.path versionOtherThan:item.itemVersionIdentifier until:[NSDate dateWithTimeIntervalSinceNow:120.0]] withLocalizedDescription:OCLocalized(@"Waiting for metadata refresh")] + [[OCWaitConditionMetaDataRefresh waitForLocation:item.location versionOtherThan:item.itemVersionIdentifier until:[NSDate dateWithTimeIntervalSinceNow:120.0]] withLocalizedDescription:OCLocalized(@"Waiting for metadata refresh")] ]]; handledError = YES; @@ -666,7 +666,7 @@ - (OCCoreSyncInstruction)handleResultWithContext:(OCSyncContext *)syncContext syncContext.updateStoredSyncRecordAfterItemUpdates = YES; // Update sync record in db, so resolutionRetries is persisted [syncContext transitionToState:OCSyncRecordStateReady withWaitConditions:@[ - [[OCWaitConditionMetaDataRefresh waitForPath:item.path versionOtherThan:item.itemVersionIdentifier until:[NSDate dateWithTimeIntervalSinceNow:10.0]] withLocalizedDescription:[NSString stringWithFormat:OCLocalized(@"Waiting to retry (%ld of %ld)"), _resolutionRetries, maxResolutionRetries]] + [[OCWaitConditionMetaDataRefresh waitForLocation:item.location versionOtherThan:item.itemVersionIdentifier until:[NSDate dateWithTimeIntervalSinceNow:10.0]] withLocalizedDescription:[NSString stringWithFormat:OCLocalized(@"Waiting to retry (%ld of %ld)"), _resolutionRetries, maxResolutionRetries]] ]]; // NSLog(@"Retry:retries=%lu", (unsigned long)_resolutionRetries); diff --git a/ownCloudSDK/Core/Sync/Actions/Upload/OCCore+CommandLocalImport.m b/ownCloudSDK/Core/Sync/Actions/Upload/OCCore+CommandLocalImport.m index 62c8eecb..24908b84 100644 --- a/ownCloudSDK/Core/Sync/Actions/Upload/OCCore+CommandLocalImport.m +++ b/ownCloudSDK/Core/Sync/Actions/Upload/OCCore+CommandLocalImport.m @@ -53,7 +53,7 @@ - (nullable NSProgress *)importFileNamed:(nullable NSString *)newFileName at:(OC __block NSString *outSuggestedName = nil; OCSyncExec(checkForExistingItems, { - [self suggestUnusedNameBasedOn:newFileName atPath:parentItem.path isDirectory:NO usingNameStyle:nameStyleNumber.unsignedIntegerValue filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { + [self suggestUnusedNameBasedOn:newFileName atLocation:parentItem.location isDirectory:NO usingNameStyle:nameStyleNumber.unsignedIntegerValue filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { outSuggestedName = suggestedName; OCSyncExecDone(checkForExistingItems); }]; @@ -71,6 +71,7 @@ - (nullable NSProgress *)importFileNamed:(nullable NSString *)newFileName at:(OC placeholderItem.parentLocalID = parentItem.localID; placeholderItem.parentFileID = parentItem.fileID; + placeholderItem.driveID = parentItem.driveID; placeholderItem.path = [parentItem.path stringByAppendingPathComponent:newFileName]; // Move file into the vault for uploading diff --git a/ownCloudSDK/Core/Sync/Actions/Upload/OCSyncActionUpload.m b/ownCloudSDK/Core/Sync/Actions/Upload/OCSyncActionUpload.m index 4e69f794..30c131ed 100644 --- a/ownCloudSDK/Core/Sync/Actions/Upload/OCSyncActionUpload.m +++ b/ownCloudSDK/Core/Sync/Actions/Upload/OCSyncActionUpload.m @@ -407,7 +407,7 @@ - (OCItem *)_preExistingItem if ((localItemLocalID = self.localItem.localID) != nil) { - [self.core.vault.database retrieveCacheItemsAtPath:self.localItem.path itemOnly:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.core.vault.database retrieveCacheItemsAtLocation:self.localItem.location itemOnly:NO completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { for (OCItem *item in items) { if (![item.localID isEqual:localItemLocalID]) @@ -482,10 +482,10 @@ - (NSError *)resolveIssue:(OCSyncIssue *)issue withChoice:(OCSyncIssueChoice *)c syncContext.updatedItems = @[ self.localItem ]; // Initiate scan to get the item that took this item's place - OCPath parentPath; - if ((parentPath = self.localItem.path.parentPath) != nil) + OCLocation *parentLocation; + if ((parentLocation = self.localItem.location.parentLocation) != nil) { - syncContext.refreshPaths = @[ parentPath ]; + syncContext.refreshLocations = @[ parentLocation ]; } syncContext.updateStoredSyncRecordAfterItemUpdates = YES; } diff --git a/ownCloudSDK/Core/Sync/Context/OCSyncContext.h b/ownCloudSDK/Core/Sync/Context/OCSyncContext.h index acd60e2f..dd38a56b 100644 --- a/ownCloudSDK/Core/Sync/Context/OCSyncContext.h +++ b/ownCloudSDK/Core/Sync/Context/OCSyncContext.h @@ -41,10 +41,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) OCSyncIssue *issue; //!< Sync issue that should be relayed to the user [Result Handler] // Item changes properties -@property(nullable,strong) NSArray *refreshPaths; //!< List of paths for which a refresh should be requested by the Sync Engine -@property(nullable,strong) NSArray *addedItems; //!< Newly created items (f.ex. after creating a directory or uploading a file), used to update database and queries -@property(nullable,strong) NSArray *removedItems; //!< Removed items (f.ex. after deleting an item), used to update database and queries -@property(nullable,strong) NSArray *updatedItems; //!< Updated items (f.ex. after renaming an item), used to update database and queries +@property(nullable,strong) NSArray *refreshLocations; //!< List of locations for which a refresh should be requested by the Sync Engine +@property(nullable,strong) NSArray *addedItems; //!< Newly created items (f.ex. after creating a directory or uploading a file), used to update database and queries +@property(nullable,strong) NSArray *removedItems; //!< Removed items (f.ex. after deleting an item), used to update database and queries +@property(nullable,strong) NSArray *updatedItems; //!< Updated items (f.ex. after renaming an item), used to update database and queries @property(assign) BOOL updateStoredSyncRecordAfterItemUpdates; //!< After processing newItems, removedItems, updatedItems (but not refreshPaths), send .syncRecord to the database for updating (NO by default) diff --git a/ownCloudSDK/Core/Sync/OCCore+SyncEngine.m b/ownCloudSDK/Core/Sync/OCCore+SyncEngine.m index c10f8d85..4d567c0f 100644 --- a/ownCloudSDK/Core/Sync/OCCore+SyncEngine.m +++ b/ownCloudSDK/Core/Sync/OCCore+SyncEngine.m @@ -160,7 +160,7 @@ - (OCItem *)retrieveLatestVersionAtPathOfItem:(OCItem *)item withError:(NSError OCSyncExec(databaseRetrieval, { [self beginActivity:@"Retrieve latest version of item"]; - [self.database retrieveCacheItemsAtPath:item.path itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [self.database retrieveCacheItemsAtLocation:item.location itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { if (outError != NULL) { *outError = error; @@ -374,7 +374,7 @@ - (void)submitSyncRecord:(OCSyncRecord *)record withPreflightResultHandler:(OCCo recordRemovedSelf = YES; } - OCLogDebug(@"record %@ returns from preflight with addedItems=%@, removedItems=%@, updatedItems=%@, refreshPaths=%@, removeRecords=%@, updateStoredSyncRecordAfterItemUpdates=%d, error=%@", record, syncContext.addedItems, syncContext.removedItems, syncContext.updatedItems, syncContext.refreshPaths, syncContext.removeRecords, syncContext.updateStoredSyncRecordAfterItemUpdates, syncContext.error); + OCLogDebug(@"record %@ returns from preflight with addedItems=%@, removedItems=%@, updatedItems=%@, refreshLocations=%@, removeRecords=%@, updateStoredSyncRecordAfterItemUpdates=%d, error=%@", record, syncContext.addedItems, syncContext.removedItems, syncContext.updatedItems, syncContext.refreshLocations, syncContext.removeRecords, syncContext.updateStoredSyncRecordAfterItemUpdates, syncContext.error); } } else @@ -420,9 +420,10 @@ - (void)submitSyncRecord:(OCSyncRecord *)record withPreflightResultHandler:(OCCo { OCLogDebug(@"record %@ completed preflight with error=%@", record, blockError); - if (record.recordID != nil) + if ((record.recordID != nil) && !record.removed) { - // Record still has a recordID, so wasn't included in syncContext.removeRecords. Remove now. + // Record still has a recordID and has not been removed, so wasn't included in syncContext.removeRecords. + // -> remove now [self removeSyncRecords:@[ record ] completionHandler:nil]; } } @@ -511,7 +512,7 @@ - (NSError *)_descheduleSyncRecord:(OCSyncRecord *)syncRecord completeWithError: // Run descheduler [syncAction descheduleWithContext:syncContext]; - OCLogDebug(@"record %@ returns from post-deschedule with addedItems=%@, removedItems=%@, updatedItems=%@, refreshPaths=%@, removeRecords=%@, updateStoredSyncRecordAfterItemUpdates=%d, error=%@", syncRecord, syncContext.addedItems, syncContext.removedItems, syncContext.updatedItems, syncContext.refreshPaths, syncContext.removeRecords, syncContext.updateStoredSyncRecordAfterItemUpdates, syncContext.error); + OCLogDebug(@"record %@ returns from post-deschedule with addedItems=%@, removedItems=%@, updatedItems=%@, refreshLocations=%@, removeRecords=%@, updateStoredSyncRecordAfterItemUpdates=%d, error=%@", syncRecord, syncContext.addedItems, syncContext.removedItems, syncContext.updatedItems, syncContext.refreshLocations, syncContext.removeRecords, syncContext.updateStoredSyncRecordAfterItemUpdates, syncContext.error); // Sync record is about to be removed, so no need to try updating it syncContext.updateStoredSyncRecordAfterItemUpdates = NO; @@ -777,7 +778,25 @@ - (void)processSyncRecords UpdateRunningActionCategories(actionCategories, 1); // Process sync record - nextInstruction = [self processSyncRecord:syncRecord error:&error]; + @try + { + nextInstruction = [self processSyncRecord:syncRecord error:&error]; + } + @catch (NSException *exception) + { + // In case of an exception, log the exception, deschedule the record, return an error and proceed + OCLogError(@"Exception processing sync record:\nReason: %@\nCall stack symbols:\n%@", exception.reason, exception.callStackSymbols); + OCLogError(@"REMOVING sync record due to exception: %@", syncRecord); + + NSString *errorDescription = [NSString stringWithFormat:OCLocalized(@"An exception occured attempting to perform an action (\"%@\"). The action has been removed from the sync queue and may not have completed. If logging is enabled, the exception has been logged."), syncRecord.action.localizedDescription]; + + [self descheduleSyncRecord:syncRecord completeWithError:OCError(OCErrorException) parameter:nil]; + + [self sendError:OCError(OCErrorException) issue:[OCIssue issueWithLocalizedTitle:OCLocalized(@"Exception occured performing action") localizedDescription:errorDescription level:OCIssueLevelError issueHandler:^(OCIssue * _Nonnull issue, OCIssueDecision decision) { + }]]; + + nextInstruction = OCCoreSyncInstructionProcessNext; + } OCLogDebug(@"Processing of sync record finished with nextInstruction=%lu", nextInstruction); @@ -1444,7 +1463,7 @@ - (void)performSyncContextActions:(OCSyncContext *)syncContext [self _scheduleNextWaitConditionRunForRecord:syncContext.syncRecord]; - [self performUpdatesForAddedItems:syncContext.addedItems removedItems:syncContext.removedItems updatedItems:syncContext.updatedItems refreshPaths:syncContext.refreshPaths newSyncAnchor:nil beforeQueryUpdates:beforeQueryUpdateAction afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:syncContext.addedItems removedItems:syncContext.removedItems updatedItems:syncContext.updatedItems refreshLocations:syncContext.refreshLocations newSyncAnchor:nil beforeQueryUpdates:beforeQueryUpdateAction afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } - (void)_scheduleNextWaitConditionRunForRecord:(OCSyncRecord *)syncRecord @@ -1665,15 +1684,21 @@ - (BOOL)_isConnectivityError:(NSError *)error; #pragma mark - Sync action utilities - (OCEventTarget *)_eventTargetWithSyncRecord:(OCSyncRecord *)syncRecord userInfo:(nullable NSDictionary *)userInfo ephermal:(nullable NSDictionary *)ephermalUserInfo { - NSDictionary *syncRecordUserInfo = @{ OCEventUserInfoKeySyncRecordID : syncRecord.recordID }; + OCSyncRecordID syncRecordID; + NSMutableDictionary *syncRecordUserInfo = [NSMutableDictionary new]; - if (userInfo != nil) + if ((syncRecordID = syncRecord.recordID) != nil) { - NSMutableDictionary *mergedDict = [[NSMutableDictionary alloc] initWithDictionary:syncRecordUserInfo]; - - [mergedDict addEntriesFromDictionary:userInfo]; + syncRecordUserInfo[OCEventUserInfoKeySyncRecordID] = syncRecordID; + } + else + { + OCLogError(@"Event target for Sync Record lacks recordID - response events will likely be leaking and lead to a hang: %@", syncRecord); + } - syncRecordUserInfo = mergedDict; + if (userInfo != nil) + { + [syncRecordUserInfo addEntriesFromDictionary:userInfo]; } return ([OCEventTarget eventTargetWithEventHandlerIdentifier:self.eventHandlerIdentifier userInfo:syncRecordUserInfo ephermalUserInfo:ephermalUserInfo]); @@ -1691,7 +1716,22 @@ - (void)addSyncRecords:(NSArray *)syncRecords completionHandler for (OCSyncRecord *syncRecord in syncRecords) { - [self.activityManager update:[OCActivityUpdate publishingActivityFor:syncRecord]]; + BOOL publish = NO; + OCSyncRecordID syncRecordID = syncRecord.recordID; + + @synchronized(_publishedActivitySyncRecordIDs) + { + if ((syncRecordID != nil) && (!syncRecord.removed) && (![_publishedActivitySyncRecordIDs containsObject:syncRecordID])) + { + [_publishedActivitySyncRecordIDs addObject:syncRecordID]; + publish = YES; + } + } + + if (publish) + { + [self.activityManager update:[OCActivityUpdate publishingActivityFor:syncRecord]]; + } } [self setNeedsToBroadcastSyncRecordActivityUpdate]; @@ -1729,7 +1769,12 @@ - (void)removeSyncRecords:(NSArray *)syncRecords completionHand - (void)updatePublishedSyncRecordActivities { [self.database retrieveSyncRecordsForPath:nil action:nil inProgressSince:nil completionHandler:^(OCDatabase *db, NSError *error, NSArray *syncRecords) { - NSMutableSet *removedSyncRecordIDs = [[NSMutableSet alloc] initWithSet:self->_publishedActivitySyncRecordIDs]; + NSMutableSet *removedSyncRecordIDs = nil; + + @synchronized(self->_publishedActivitySyncRecordIDs) + { + removedSyncRecordIDs = [[NSMutableSet alloc] initWithSet:self->_publishedActivitySyncRecordIDs]; + } for (OCSyncRecord *syncRecord in syncRecords) { @@ -1737,11 +1782,19 @@ - (void)updatePublishedSyncRecordActivities syncRecord.action.core = self; - if (recordID != nil) + if ((recordID != nil) && !syncRecord.removed) { + BOOL publish = NO; + [removedSyncRecordIDs removeObject:recordID]; - if ([self->_publishedActivitySyncRecordIDs containsObject:syncRecord.recordID]) + @synchronized(self->_publishedActivitySyncRecordIDs) + { + publish = ![self->_publishedActivitySyncRecordIDs containsObject:recordID]; + [self->_publishedActivitySyncRecordIDs addObject:recordID]; + } + + if (!publish) { // Update published activities NSProgress *progress = [syncRecord.progress resolveWith:nil]; @@ -1758,7 +1811,6 @@ - (void)updatePublishedSyncRecordActivities { // Publish new activities [self.activityManager update:[OCActivityUpdate publishingActivityFor:syncRecord]]; - [self->_publishedActivitySyncRecordIDs addObject:recordID]; syncRecord.action.core = self; [syncRecord.action restoreProgressRegistrationForSyncRecord:syncRecord]; @@ -1772,7 +1824,10 @@ - (void)updatePublishedSyncRecordActivities [self.activityManager update:[OCActivityUpdate unpublishActivityForIdentifier:[OCSyncRecord activityIdentifierForSyncRecordID:syncRecordID]]]; } - [self->_publishedActivitySyncRecordIDs minusSet:removedSyncRecordIDs]; + @synchronized(self->_publishedActivitySyncRecordIDs) + { + [self->_publishedActivitySyncRecordIDs minusSet:removedSyncRecordIDs]; + } }]; } @@ -1866,7 +1921,7 @@ - (void)scrubItemSyncStatus { OCLogDebug(@"Updated items: %@", updateItems); - [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshPaths:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; + [self performUpdatesForAddedItems:nil removedItems:nil updatedItems:updateItems refreshLocations:nil newSyncAnchor:nil beforeQueryUpdates:nil afterQueryUpdates:nil queryPostProcessor:nil skipDatabase:NO]; } } diff --git a/ownCloudSDK/Core/Sync/Record/OCSyncRecord.h b/ownCloudSDK/Core/Sync/Record/OCSyncRecord.h index b18236f4..6008b6ba 100644 --- a/ownCloudSDK/Core/Sync/Record/OCSyncRecord.h +++ b/ownCloudSDK/Core/Sync/Record/OCSyncRecord.h @@ -109,6 +109,8 @@ typedef NS_ENUM(NSInteger, OCSyncRecordState) - (void)completeWithError:(nullable NSError *)error core:(OCCore *)core item:(nullable OCItem *)item parameter:(nullable id)parameter; //!< Calls the resultHandler and subsequently drops it. You're responsible from updating the record in the database. +@property(assign) BOOL removed; //!< YES if this instance has been removed via -[OCDatabase removeSyncRecords:completionHandler:] + #pragma mark - Progress convenience method - (void)addProgress:(OCProgress *)progress; diff --git a/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.h b/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.h index 00fa1d8a..33f27c6c 100644 --- a/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.h +++ b/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.h @@ -20,10 +20,4 @@ NS_ASSUME_NONNULL_BEGIN -@interface OCCore (ThumbnailInternals) - -- (void)_handleRetrieveThumbnailEvent:(OCEvent *)event sender:(id)sender; - -@end - NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.m b/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.m index 37033eae..1b17b775 100644 --- a/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.m +++ b/ownCloudSDK/Core/Thumbnails/OCCore+Thumbnails.m @@ -23,6 +23,9 @@ #import "NSError+OCError.h" #import "NSProgress+OCExtensions.h" #import "OCLogger.h" +#import "OCResourceImage.h" +#import "OCResourceRequestItemThumbnail.h" +#import "OCResourceManager.h" @implementation OCCore (Thumbnails) @@ -63,271 +66,5 @@ + (BOOL)thumbnailSupportedForMIMEType:(NSString *)mimeType return (NO); } - -#pragma mark - Command: Retrieve Thumbnail -- (nullable NSProgress *)retrieveThumbnailFor:(OCItem *)item maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale retrieveHandler:(OCCoreThumbnailRetrieveHandler)retrieveHandler -{ - return ([self retrieveThumbnailFor:item maximumSize:requestedMaximumSizeInPoints scale:scale waitForConnectivity:YES retrieveHandler:retrieveHandler]); -} - -- (nullable NSProgress *)retrieveThumbnailFor:(OCItem *)item maximumSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale waitForConnectivity:(BOOL)waitForConnectivity retrieveHandler:(OCCoreThumbnailRetrieveHandler)retrieveHandler -{ - NSProgress *progress = [NSProgress indeterminateProgress]; - OCFileID fileID = item.fileID; - OCItemVersionIdentifier *versionIdentifier = item.itemVersionIdentifier; - NSString *specID = item.thumbnailSpecID; - NSArray *thumbnailRequestTags = nil; - NSString *thumbnailRequestUUID = [NSString stringWithFormat:@"%p", retrieveHandler]; - CGSize requestedMaximumSizeInPixels; - - retrieveHandler = [retrieveHandler copy]; - - if (scale == 0) - { - scale = UIScreen.mainScreen.scale; - } - - requestedMaximumSizeInPixels = CGSizeMake(floor(requestedMaximumSizeInPoints.width * scale), floor(requestedMaximumSizeInPoints.height * scale)); - - progress.eventType = OCEventTypeRetrieveThumbnail; - progress.localizedDescription = OCLocalizedString(@"Retrieving thumbnail…", @""); - - OCTLogVerbose((thumbnailRequestTags = @[OCLogTagTypedID(@"ThumbnailRequest", thumbnailRequestUUID)]), @"Starting retrieval of thumbnail for %@, maximumSize:%@ scale:%f", item, NSStringFromCGSize(requestedMaximumSizeInPoints), scale); - - if (fileID != nil) - { - [self queueBlock:^{ - OCItemThumbnail *thumbnail; - BOOL requestThumbnail = YES; - - // Is there a thumbnail for this file in the cache? - if ((thumbnail = [self->_thumbnailCache objectForKey:item.fileID]) != nil) - { - // Yes! But is it the version we want? - if ([thumbnail.itemVersionIdentifier isEqual:item.itemVersionIdentifier] && [thumbnail.specID isEqual:item.thumbnailSpecID]) - { - // Yes it is! - if ([thumbnail canProvideForMaximumSizeInPixels:requestedMaximumSizeInPixels]) - { - // The size is fine, too! - OCTLogVerbose(thumbnailRequestTags, @"Providing final thumbnail from cache: %@", thumbnail); - - retrieveHandler(nil, self, item, thumbnail, NO, progress); - - requestThumbnail = NO; - } - else - { - // The size isn't sufficient - retrieveHandler(nil, self, item, thumbnail, YES, progress); - - OCTLogVerbose(thumbnailRequestTags, @"Returning smaller-sized thumbnail %@ as preview", thumbnail); - } - } - else - { - // No it's not => remove outdated version from cache - OCTLogVerbose(thumbnailRequestTags, @"Removing outdated/different thumbnail %@ from cache: item=(%@, %@), thumbnail=(%@, %@)", thumbnail, item.itemVersionIdentifier, item.thumbnailSpecID, thumbnail.itemVersionIdentifier, thumbnail.specID); - - [self->_thumbnailCache removeObjectForKey:item.fileID]; - - thumbnail = nil; - } - } - - // Should a thumbnail be requested? - if (requestThumbnail) - { - OCTLogVerbose(thumbnailRequestTags, @"Starting thumbnail request"); - - if (!progress.cancelled) - { - // Thumbnail database - OCTLogVerbose(thumbnailRequestTags, @"Starting thumbnail database request for version=%@, specID=%@, maximumSizeInPixels=%@", versionIdentifier, specID, NSStringFromCGSize(requestedMaximumSizeInPixels)); - - [self.vault.database retrieveThumbnailDataForItemVersion:versionIdentifier specID:specID maximumSizeInPixels:requestedMaximumSizeInPixels completionHandler:^(OCDatabase *db, NSError *error, CGSize maxSize, NSString *mimeType, NSData *thumbnailData) { - OCItemThumbnail *cachedThumbnail = nil; - - if (thumbnailData != nil) - { - // Create OCItemThumbnail from data returned from database - OCItemThumbnail *cachedThumbnail = [OCItemThumbnail new]; - - cachedThumbnail.maximumSizeInPixels = maxSize; - cachedThumbnail.mimeType = mimeType; - cachedThumbnail.data = thumbnailData; - cachedThumbnail.specID = specID; - cachedThumbnail.itemVersionIdentifier = versionIdentifier; - - OCTLogVerbose(thumbnailRequestTags, @"Retrieved thumbnail from database: %@", cachedThumbnail); - - if ([cachedThumbnail canProvideForMaximumSizeInPixels:requestedMaximumSizeInPixels]) - { - [self queueBlock:^{ - OCTLogVerbose(thumbnailRequestTags, @"Providing final thumbnail from database: %@", cachedThumbnail); - - [self->_thumbnailCache setObject:cachedThumbnail forKey:fileID cost:(maxSize.width * maxSize.height * 4)]; - retrieveHandler(nil, self, item, cachedThumbnail, NO, progress); - }]; - - return; - } - else - { - OCTLogVerbose(thumbnailRequestTags, @"Size of retrieved thumbnail from database does not match requested size: %@", cachedThumbnail); - } - } - else - { - OCTLogVerbose(thumbnailRequestTags, @"No matching thumbnail found in database"); - } - - // Update the retrieveHandler with a thumbnail if it doesn't already have one - if ((thumbnail == nil) && (cachedThumbnail != nil)) - { - OCTLogVerbose(thumbnailRequestTags, @"Returning preview thumbnail from database: %@", cachedThumbnail); - retrieveHandler(nil, self, item, cachedThumbnail, YES, progress); - } - - // Request a thumbnail from the server if the operation hasn't been cancelled yet. - if (!progress.cancelled) - { - NSString *requestID = [NSString stringWithFormat:@"%@:%@-%@-%fx%f", versionIdentifier.fileID, versionIdentifier.eTag, specID, requestedMaximumSizeInPixels.width, requestedMaximumSizeInPixels.height]; - - [self queueBlock:^{ - BOOL sendRequest = YES; - - // Queue retrieve handlers - NSMutableArray *retrieveHandlersQueue; - - if ((retrieveHandlersQueue = self->_pendingThumbnailRequests[requestID]) == nil) - { - retrieveHandlersQueue = [NSMutableArray new]; - - self->_pendingThumbnailRequests[requestID] = retrieveHandlersQueue; - - OCTLogVerbose(thumbnailRequestTags, @"Creating request queue for thumbnail for %@", item.path); - } - - if (retrieveHandlersQueue.count != 0) - { - // Another request is already pending - sendRequest = NO; - - OCTLogVerbose(thumbnailRequestTags, @"Another thumbnail request is already running for %@, enqueueing this request", item.path); - } - - [retrieveHandlersQueue addObject:retrieveHandler]; - - if (sendRequest) - { - OCEventTarget *target; - NSProgress *retrieveProgress; - - OCTLogVerbose(thumbnailRequestTags, @"Requesting thumbnail for %@ from server", item.path); - - // Define result event target - target = [OCEventTarget eventTargetWithEventHandlerIdentifier:self.eventHandlerIdentifier userInfo:@{ - @"requestedMaximumSize" : [NSValue valueWithCGSize:requestedMaximumSizeInPixels], - @"scale" : @(scale), - OCEventUserInfoKeyItemVersionIdentifier : item.itemVersionIdentifier, - @"specID" : item.thumbnailSpecID, - OCEventUserInfoKeyItem : item, - } ephermalUserInfo:@{ - @"requestID" : requestID - }]; - - // Request thumbnail from connection - retrieveProgress = [self.connection retrieveThumbnailFor:item to:nil maximumSize:requestedMaximumSizeInPixels waitForConnectivity:waitForConnectivity resultTarget:target]; - - if (retrieveProgress != nil) - { - [progress addChild:retrieveProgress withPendingUnitCount:0]; - } - } - }]; - } - else - { - OCTLogVerbose(thumbnailRequestTags, @"Thumbnail retrieval has been cancelled (1)"); - - if (retrieveHandler != nil) - { - retrieveHandler(OCError(OCErrorRequestCancelled), self, item, nil, NO, progress); - } - } - }]; - } - else - { - OCTLogVerbose(thumbnailRequestTags, @"Thumbnail retrieval has been cancelled (2)"); - - if (retrieveHandler != nil) - { - retrieveHandler(OCError(OCErrorRequestCancelled), self, item, nil, NO, progress); - } - } - } - }]; - } - - return(progress); -} - @end -@implementation OCCore (ThumbnailInternals) - -- (void)_handleRetrieveThumbnailEvent:(OCEvent *)event sender:(id)sender -{ - [self queueBlock:^{ - OCItemThumbnail *thumbnail = event.result; - // CGSize requestedMaximumSize = ((NSValue *)event.userInfo[@"requestedMaximumSize"]).CGSizeValue; - // CGFloat scale = ((NSNumber *)event.userInfo[@"scale"]).doubleValue; - OCItemVersionIdentifier *itemVersionIdentifier = OCTypedCast(event.userInfo[OCEventUserInfoKeyItemVersionIdentifier], OCItemVersionIdentifier); - OCItem *item = OCTypedCast(event.userInfo[OCEventUserInfoKeyItem], OCItem); - NSString *specID = OCTypedCast(event.userInfo[@"specID"], NSString); - NSString *requestID = OCTypedCast(event.ephermalUserInfo[@"requestID"], NSString); - - OCLogVerbose(@"Received thumbnail from server for %@, specID=%@, requestID=%@", item.path, specID, requestID); - - if ((event.error == nil) && (event.result != nil)) - { - // Update cache - OCLogVerbose(@"Updating thumbnail cache with %@", thumbnail); - [self->_thumbnailCache setObject:thumbnail forKey:itemVersionIdentifier.fileID]; - - // Store in database - OCLogVerbose(@"Updating database with %@", thumbnail); - [self.vault.database storeThumbnailData:thumbnail.data withMIMEType:thumbnail.mimeType specID:specID forItemVersion:itemVersionIdentifier maximumSizeInPixels:thumbnail.maximumSizeInPixels completionHandler:nil]; - } - - // Call all retrieveHandlers - if (requestID != nil) - { - NSMutableArray *retrieveHandlersQueue = self->_pendingThumbnailRequests[requestID]; - - if (retrieveHandlersQueue != nil) - { - [self->_pendingThumbnailRequests removeObjectForKey:requestID]; - } - - item.thumbnail = thumbnail; - - dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - for (OCCoreThumbnailRetrieveHandler retrieveHandler in retrieveHandlersQueue) - { - NSString *thumbnailRequestUUID = [NSString stringWithFormat:@"%p", retrieveHandler]; - OCTLogVerbose(@[OCLogTagTypedID(@"ThumbnailRequest", thumbnailRequestUUID)], @"Providing final thumbnail from server: %@", thumbnail); - retrieveHandler(event.error, self, item, thumbnail, NO, nil); - } - }); - } - else - { - OCLogVerbose(@"Can't handle thumbnail response because of missing requestID"); - } - }]; -} - -@end diff --git a/ownCloudSDK/Data Sources/CONCEPT.md b/ownCloudSDK/Data Sources/CONCEPT.md new file mode 100644 index 00000000..423a81af --- /dev/null +++ b/ownCloudSDK/Data Sources/CONCEPT.md @@ -0,0 +1,110 @@ +# Data Sources + +## Concept / Objectives +- mechanism to provide data items in a structured way + - supports updates + - supports hierarchies +- optimized for low memory footprint + - avoid duplication of data in memory + - items can be implemented by protocol compliance + - enclosing types are only used temporarily and in low numbers +- optimized for performance + - multiple changes to the content within one runloop cycle trigger only a single update +- modular, pipeline-based rendering + - converters + - convert from an input to an output type, f.ex. + - to convert an OCItem into a dictionary of [title, description, thumbnail, [icons]] + - to convert that dictionary into an actual table/collection view cell + - can be chained together + - providing conversion from an input to an output type + - performance / memory optimized + - memory: converters don't save state, so can be used in multiple renderers + - performance: options dictionary allows passing f.ex. existing cells for possible reuse + - presentables + - standardized intermediate representation + - allows simplified display and conversion from many input to many output types + - renderers + - manage data sources + - render items to the desired output type using converters / converter pipelines + - can dynamically assemble new pipelines from a combination of existing converters / converter pipelines + - cell configurations allow temporarily passing additional objects to cell renderers alongside item references, where it is otherwise not possible +- compositions + - allow combining different data sources into a consolidated, "composed" data source + - allow sorting and filtering through blocks, either on item references or item records + - leveraging converters and presentable intermediates allows implementing filters and sorting that work across data types + +- mapped + - maps/converts a source datasource's items into other items and allows keeping them in sync with minimum effort + +## Composition example +The following code shows examples for various customizations. NOTE: not all of the customizations shown make sense at the same time. + +```swift +// Create a composed data source concating core.personalAndSharedDrivesDataSource and core.projectDrivesDataSource +let composedDataSource = OCDataSourceComposition(sources: [ + core.personalAndSharedDrivesDataSource, + core.projectDrivesDataSource +], applyCustomizations: { (composedDataSource) in + // Apply customizations before first assembly + + // Sort concated items + composedDataSource.sortComparator = OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in + var presentable1 : OCDataItemPresentable? + var presentable2 : OCDataItemPresentable? + + if let item = record1?.item { + presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + if let item = record2?.item { + presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + let title1 = presentable1?.title ?? "" + let title2 = presentable2?.title ?? "" + + return title1.localizedCompare(title2) + }) + + // Filter concated items (only items with a title starting with "A") + composedDataSource.filter = OCDataSourceComposition.itemFilter(recordFilter: { record in + if let item = record?.item, + let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, + let startsWithA = presentable.title?.starts(with: "A") { + return startsWithA + } + + return false + }) + + // Sort items inside core.projectDrivesDataSource by last character + composedDataSource.setSortComparator(OCDataSourceComposition.itemComparator(withItemRetrieval: false, fromRecordComparator: { record1, record2 in + var presentable1 : OCDataItemPresentable? + var presentable2 : OCDataItemPresentable? + + if let item = record1?.item { + presentable1 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + if let item = record2?.item { + presentable2 = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable + } + + let title1 = presentable1?.title?.last?.lowercased() ?? "" + let title2 = presentable2?.title?.last?.lowercased() ?? "" + + return title1.localizedCompare(title2) + }), for: core.projectDrivesDataSource) + + // Filter items inside core.projectDrivesDataSource (only items with a title starting with "A") + composedDataSource.setFilter(OCDataSourceComposition.itemFilter(withItemRetrieval: false, fromRecordFilter: { (record) in + if let item = record?.item, + let presentable = OCDataRenderer.default.renderItem(item, asType: .presentable, error: nil) as? OCDataItemPresentable, + let startsWithA = presentable.title?.starts(with: "A") { + return startsWithA + } + + return false + }), for: core.projectDrivesDataSource) +}) +``` diff --git a/ownCloudSDK/Data Sources/Converters/OCDataConverter.h b/ownCloudSDK/Data Sources/Converters/OCDataConverter.h new file mode 100644 index 00000000..181c3db4 --- /dev/null +++ b/ownCloudSDK/Data Sources/Converters/OCDataConverter.h @@ -0,0 +1,43 @@ +// +// OCDataConverter.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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" + +@class OCDataRenderer; +@class OCDataConverter; + +NS_ASSUME_NONNULL_BEGIN + +typedef id _Nullable(^OCDataConversion)(OCDataConverter *converter, id _Nullable input, OCDataRenderer * _Nullable renderer, NSError * _Nullable * _Nullable outError, OCDataViewOptions _Nullable options); + +@interface OCDataConverter : NSObject +{ + OCDataConversion _conversion; +} + +@property(readonly,nonatomic) OCDataItemType inputType; //!< type this converter converts from +@property(readonly,nonatomic) OCDataItemType outputType; //!< type this converter converts to + +- (instancetype)initWithInputType:(OCDataItemType)inputType outputType:(OCDataItemType)outputType conversion:(OCDataConversion)conversion; //!< Create a new converter using a conversion-block. + +- (nullable id)convert:(nullable id)input renderer:(nullable OCDataRenderer *)renderer error:(NSError * _Nullable * _Nullable)outError withOptions:(nullable OCDataViewOptions)options; //!< Converts the input object (of .inputType) to an object of .outputType. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Converters/OCDataConverter.m b/ownCloudSDK/Data Sources/Converters/OCDataConverter.m new file mode 100644 index 00000000..6b883909 --- /dev/null +++ b/ownCloudSDK/Data Sources/Converters/OCDataConverter.m @@ -0,0 +1,51 @@ +// +// OCDataConverter.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 "OCDataConverter.h" +#import "NSError+OCError.h" + +@implementation OCDataConverter + +- (instancetype)initWithInputType:(OCDataItemType)inputType outputType:(OCDataItemType)outputType conversion:(OCDataConversion)conversion +{ + if ((self = [super init]) != nil) + { + _inputType = inputType; + _outputType = outputType; + _conversion = [conversion copy]; + } + + return (self); +} + +- (id)convert:(id)input renderer:(OCDataRenderer *)renderer error:(NSError * _Nullable __autoreleasing *)outError withOptions:(OCDataViewOptions)options +{ + if (_conversion == nil) + { + if (outError != NULL) + { + *outError = OCError(OCErrorFeatureNotImplemented); + } + + return (nil); + } + + return (_conversion(self, input, renderer, outError, options)); +} + +@end diff --git a/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.h b/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.h new file mode 100644 index 00000000..a9ce809c --- /dev/null +++ b/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.h @@ -0,0 +1,33 @@ +// +// OCDataConverterPipeline.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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" +#import "OCDataConverter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCDataConverterPipeline : OCDataConverter + +@property (strong,readonly) NSArray *converters; + +- (instancetype)initWithConverters:(NSArray *)converters; //!< Pipes + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.m b/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.m new file mode 100644 index 00000000..6d8916e7 --- /dev/null +++ b/ownCloudSDK/Data Sources/Converters/OCDataConverterPipeline.m @@ -0,0 +1,77 @@ +// +// OCDataConverterPipeline.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 "OCDataConverterPipeline.h" + +@implementation OCDataConverterPipeline + +- (instancetype)initWithConverters:(NSArray *)converters +{ + if ((self = [super init]) != nil) + { + _converters = converters; + } + + return (self); +} + +- (OCDataItemType)inputType +{ + return (_converters.firstObject.inputType); +} + +- (OCDataItemType)outputType +{ + return (_converters.lastObject.outputType); +} + +- (id)convert:(id)originalInput renderer:(OCDataRenderer *)renderer error:(NSError * _Nullable __autoreleasing *)outError withOptions:(OCDataViewOptions)options +{ + id output = nil; + id input = originalInput; + + for (OCDataConverter *converter in _converters) + { + NSError *converterError = nil; + + output = [converter convert:input renderer:renderer error:&converterError withOptions:options]; + + // If a converter returns an error, no further conversion takes place - we return nil and the error directly + if (converterError != nil) + { + if (outError != NULL) + { + *outError = converterError; + } + + return (nil); + } + + // If a converter returns nil, no further conversion takes place - we return nil directly + if (output == nil) + { + return (nil); + } + + input = output; + } + + return (output); +} + +@end diff --git a/ownCloudSDK/Data Sources/OCDataTypes.h b/ownCloudSDK/Data Sources/OCDataTypes.h new file mode 100644 index 00000000..3eccbb51 --- /dev/null +++ b/ownCloudSDK/Data Sources/OCDataTypes.h @@ -0,0 +1,81 @@ +// +// OCDataTypes.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 + +typedef NSObject* OCDataItemReference; //!< Unique reference that uniquely identifies an item within a data source, typically a string or number +typedef NSString* OCDataItemType NS_TYPED_ENUM; //!< The item type, i.e. OCItem, OCDrive, ItemDescription, Section, TableCell, CollectionCell, View +typedef id OCDataItemVersion; //!< The version of the item, i.e. a string or a number. Can be used by data sources to find changed items in small data sets. + +typedef NSString* OCDataSourceUUID; //!< Unique data source UUID +typedef NSString* OCDataSourceType NS_TYPED_ENUM; //!< Optional data source type, i.e. List, SectionedList + +typedef NSString* OCDataViewOption NS_TYPED_ENUM; //!< Options for view pipeline, i.e. reusable view +typedef NSDictionary* OCDataViewOptions; + +typedef NSString* OCDataSourceSpecialItem NS_TYPED_ENUM; + +NS_ASSUME_NONNULL_BEGIN + +@class OCDataSource; +@class OCDataItemRecord; + +@protocol OCDataItem + +@property(readonly) OCDataItemType dataItemType; +@property(readonly) OCDataItemReference dataItemReference; + +@optional +- (BOOL)hasChildrenUsingSource:(OCDataSource *)source; //!< Indiciates if the item has children +- (nullable OCDataSource *)dataSourceForChildrenUsingSource:(OCDataSource *)source; //!< Provides a datasource containing the children of the item. The parent item's data source is provided as parameter. + +@end + +@protocol OCDataItemVersioning +@property(readonly) OCDataItemVersion dataItemVersion; +@end + +typedef BOOL(^OCDataItemHasChildrenProvider)(OCDataSource *dataSource, id item); +typedef OCDataSource * _Nullable(^OCDataItemChildrenDataSourceProvider)(OCDataSource *parentItemDataSource, id parentItem); + +typedef BOOL(^OCDataSourceItemFilter)(OCDataSource *source, OCDataItemReference itemRef); +typedef NSComparisonResult(^OCDataSourceItemComparator)(OCDataSource *source1, OCDataItemReference itemRef1, OCDataSource *source2, OCDataItemReference itemRef2); + +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 OCDataItemTypeLocation; //!< Item of type OCLocation +extern OCDataItemType OCDataItemTypePresentable; //!< Item of type OCDataItemPresentable +extern OCDataItemType OCDataItemTypeTextResource; //!< Item of type OCResourceText +extern OCDataItemType OCDataItemTypeMessage; //!< Item of type message +extern OCDataItemType OCDataItemTypeAction; //!< Item of type OCAction +extern OCDataItemType OCDataItemTypeSavedSearch; //!< Item of type OCSavedSearch +extern OCDataItemType OCDataItemTypeStatistic; //!< Item of type OCStatistic +extern OCDataItemType OCDataItemTypeView; //!< Item of type UIView +extern OCDataItemType OCDataItemTypeBookmark; //!< Item of type OCBookmark +extern OCDataItemType OCDataItemTypeItemPolicy; //!< Item of type OCItemPolicy +extern OCDataItemType OCDataItemTypeShare; //!< Item of type OCShare +extern OCDataItemType OCDataItemTypeShareRole; //!< Item of type OCShareRole +extern OCDataItemType OCDataItemTypeIdentity; //!< Item of type OCIdentity + +extern OCDataViewOption OCDataViewOptionCore; //!< OCCore instance +extern OCDataViewOption OCDataViewOptionListContentConfiguration; //!< UIListContentConfiguration instance to fill + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/OCDataTypes.m b/ownCloudSDK/Data Sources/OCDataTypes.m new file mode 100644 index 00000000..57ee8dd3 --- /dev/null +++ b/ownCloudSDK/Data Sources/OCDataTypes.m @@ -0,0 +1,38 @@ +// +// OCDataItemType.m +// ownCloudSDK +// +// Created by Felix Schwarz on 28.03.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 "OCDataTypes.h" + +OCDataItemType OCDataItemTypeItem = @"item"; +OCDataItemType OCDataItemTypeDrive = @"drive"; +OCDataItemType OCDataItemTypeLocation = @"location"; +OCDataItemType OCDataItemTypePresentable = @"presentable"; +OCDataItemType OCDataItemTypeTextResource = @"textResource"; +OCDataItemType OCDataItemTypeMessage = @"message"; +OCDataItemType OCDataItemTypeAction = @"action"; +OCDataItemType OCDataItemTypeSavedSearch = @"savedSearch"; +OCDataItemType OCDataItemTypeStatistic = @"statistic"; +OCDataItemType OCDataItemTypeView = @"view"; +OCDataItemType OCDataItemTypeBookmark = @"bookmark"; +OCDataItemType OCDataItemTypeItemPolicy = @"itemPolicy"; +OCDataItemType OCDataItemTypeShare = @"share"; +OCDataItemType OCDataItemTypeShareRole = @"shareRole"; +OCDataItemType OCDataItemTypeIdentity = @"identity"; + +OCDataViewOption OCDataViewOptionCore = @"core"; +OCDataViewOption OCDataViewOptionListContentConfiguration = @"listContentConfiguration"; diff --git a/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.h b/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.h new file mode 100644 index 00000000..a5d519d7 --- /dev/null +++ b/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.h @@ -0,0 +1,65 @@ +// +// OCDataItemPresentable.h +// ownCloudSDK +// +// Created by Felix Schwarz on 28.03.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 +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCDataItemPresentable; +@class OCResourceRequest; + +typedef NSString* OCDataItemPresentableResource NS_TYPED_ENUM; +typedef OCResourceRequest * _Nullable(^OCDataItemPresentableResourceRequestProvider)(OCDataItemPresentable *presentable, OCDataItemPresentableResource presentableResource, OCDataViewOptions _Nullable options, NSError * _Nullable * _Nullable outError); + +@interface OCDataItemPresentable : NSObject + +#pragma mark - OCDataItem conformance +@property(strong) OCDataItemReference dataItemReference; +@property(nullable,strong) OCDataItemType originalDataItemType; + +#pragma mark - OCDataItemVersion conformance +@property(nullable,strong) OCDataItemVersion dataItemVersion; + +#pragma mark - Presentable properties +@property(strong,nullable) UIImage *image; +@property(strong,nullable) NSString *title; +@property(strong,nullable) NSString *subtitle; + +#pragma mark - Initialization +- (instancetype)init NS_UNAVAILABLE; //!< Always returns nil. Please use the designated initializer instead. +- (instancetype)initWithReference:(OCDataItemReference)reference originalDataItemType:(nullable OCDataItemType)originalDataItemType version:(nullable OCDataItemVersion)dataItemVersion NS_DESIGNATED_INITIALIZER; //!< Initialize with reference of item from which the presentable representation is derived +- (instancetype)initWithItem:(id)item NS_DESIGNATED_INITIALIZER; //!< Initialize with reference, originalDataItemType and (where available) dataItemVersion of item from which the presentable representation is derived + +#pragma mark - Asynchronous resource retrieval +@property(strong,nullable) NSArray *availableResources; //!< Set of available resources that can be requested +@property(copy,nullable) OCDataItemPresentableResourceRequestProvider resourceRequestProvider; //!< Resource request provider, used by - provideResourceRequest:withOptions:completionHandler: + +- (nullable OCResourceRequest *)provideResourceRequest:(OCDataItemPresentableResource)presentableResource withOptions:(nullable OCDataViewOptions)options error:(NSError * _Nullable * _Nullable)outError; //!< Returns an OCResourceRequest for a particular resource associated with the presentable, i.e. a thumbnail, using the presentable's resourceRequestProvider + +#pragma mark - Children data source provider +@property(copy,nullable) OCDataItemHasChildrenProvider hasChildrenProvider; +@property(copy,nullable) OCDataItemChildrenDataSourceProvider childrenDataSourceProvider; + +@end + +extern OCDataItemPresentableResource OCDataItemPresentableResourceCoverImage; +extern OCDataItemPresentableResource OCDataItemPresentableResourceCoverDescription; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.m b/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.m new file mode 100644 index 00000000..37343672 --- /dev/null +++ b/ownCloudSDK/Data Sources/Presentable/OCDataItemPresentable.m @@ -0,0 +1,128 @@ +// +// OCDataItemPresentable.m +// ownCloudSDK +// +// Created by Felix Schwarz on 28.03.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 "OCDataItemPresentable.h" +#import "NSError+OCError.h" + +@implementation OCDataItemPresentable + +- (instancetype)init +{ + return (nil); +} + +- (instancetype)initWithReference:(OCDataItemReference)reference originalDataItemType:(OCDataItemType)originalDataItemType version:(OCDataItemVersion)dataItemVersion +{ + if ((self = [super init]) != nil) + { + _dataItemReference = reference; + _originalDataItemType = originalDataItemType; + _dataItemVersion = dataItemVersion; + } + + return (self); +} + +- (instancetype)initWithItem:(id)item +{ + if ((self = [super init]) != nil) + { + _originalDataItemType = item.dataItemType; + _dataItemReference = item.dataItemReference; + + if ([item conformsToProtocol:@protocol(OCDataItemVersioning)]) + { + _dataItemVersion = ((id)item).dataItemVersion; + } + } + + return (self); +} + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypePresentable); +} + +- (nullable OCResourceRequest *)provideResourceRequest:(OCDataItemPresentableResource)presentableResource withOptions:(nullable OCDataViewOptions)options error:(NSError * _Nullable * _Nullable)outError +{ + if (presentableResource != nil) + { + OCDataItemPresentableResourceRequestProvider requestProvider; + + if (![_availableResources containsObject:presentableResource]) + { + // Resource is not available + return(nil); + } + + if ((requestProvider = self.resourceRequestProvider) != nil) + { + // Provide resource + return (requestProvider(self, presentableResource, options, outError)); + } + } + + // No resource request provider or no resource provided + return (nil); +} + +- (BOOL)respondsToSelector:(SEL)selector +{ + if (selector == @selector(hasChildrenUsingSource:)) + { + return (_hasChildrenProvider != nil); + } + + if (selector == @selector(dataSourceForChildrenUsingSource:)) + { + return (_childrenDataSourceProvider != nil); + } + + return ([super respondsToSelector:selector]); +} + +- (BOOL)hasChildrenUsingSource:(OCDataSource *)source +{ + if (_hasChildrenProvider != nil) + { + return (_hasChildrenProvider(source, self)); + } + + if (_childrenDataSourceProvider != nil) + { + return (YES); + } + + return (NO); +} + +- (nullable OCDataSource *)dataSourceForChildrenUsingSource:(OCDataSource *)source +{ + if (_childrenDataSourceProvider != nil) + { + return (_childrenDataSourceProvider(source, self)); + } + + return (nil); +} + +@end + +OCDataItemPresentableResource OCDataItemPresentableResourceCoverImage = @"coverImage"; +OCDataItemPresentableResource OCDataItemPresentableResourceCoverDescription = @"coverDescription"; diff --git a/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.h b/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.h new file mode 100644 index 00000000..1cfe0ced --- /dev/null +++ b/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.h @@ -0,0 +1,43 @@ +// +// OCDataRenderer.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 + +@class OCDataSource; +@class OCDataConverter; + +@interface OCDataRenderer : NSObject + +@property(readonly,class,nonatomic,strong) OCDataRenderer *defaultRenderer; //!< Globally shared default renderer + +- (instancetype)initWithConverters:(nullable NSArray *)converters; //!< Initialize a new renderer with an array of converters + +- (void)addConverters:(NSArray *)converters; //!< Add one or more converter(s) + +- (nullable OCDataConverter *)assembledConverterFrom:(OCDataItemType)inputType to:(OCDataItemType)outputType; //!< Returns existing converters from inputType to outputType. If none is found, attempts to assemble a new pipeline from existing converters. Returns nil if none was found or could be assembled. + +- (nullable id)renderObject:(id)object ofType:(OCDataItemType)inputType asType:(OCDataItemType)outputType error:(NSError * _Nullable * _Nullable)outError withOptions:(nullable OCDataViewOptions)options; //!< Render an input object (of inputType) as an object of type outputType using the renderer's converters + +- (nullable id)renderItem:(id)item asType:(OCDataItemType)outputType error:(NSError * _Nullable * _Nullable)outError withOptions:(nullable OCDataViewOptions)options; //!< Render an item (of item.dataItemType) as an object of type outputType using the renderer's converters. Calls -renderObject: internally. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.m b/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.m new file mode 100644 index 00000000..df7f27e6 --- /dev/null +++ b/ownCloudSDK/Data Sources/Renderer/OCDataRenderer.m @@ -0,0 +1,215 @@ +// +// OCDataRenderer.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 "OCDataRenderer.h" +#import "OCDataConverter.h" +#import "OCDataConverterPipeline.h" +#import "NSError+OCError.h" +#import "OCMacros.h" + +typedef NSString* OCDataItemTypePair; + +@interface OCDataRenderer () +{ + NSMutableDictionary *_convertersByTypePair; +} +@end + +@implementation OCDataRenderer + ++ (instancetype)defaultRenderer +{ + static dispatch_once_t onceToken; + static OCDataRenderer *defaultRenderer; + + dispatch_once(&onceToken, ^{ + defaultRenderer = [OCDataRenderer new]; + }); + + return (defaultRenderer); + +} + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _convertersByTypePair = [NSMutableDictionary new]; + } + + return (self); +} + +- (instancetype)initWithConverters:(NSArray *)converters +{ + if ((self = [self init]) != nil) + { + if (converters != nil) + { + [self addConverters:converters]; + } + } + + return (self); +} + +- (void)addConverters:(NSArray *)converters +{ + for (OCDataConverter *converter in converters) + { + OCDataItemTypePair pair = [converter.inputType stringByAppendingFormat:@":%@", converter.outputType]; + _convertersByTypePair[pair] = converter; + } +} + +- (OCDataConverter *)converterFrom:(OCDataItemType)inputType to:(OCDataItemType)outputType +{ + OCDataItemTypePair pair; + + if ((pair = [inputType stringByAppendingFormat:@":%@", outputType]) == nil) + { + return (nil); + } + + return (_convertersByTypePair[pair]); +} + +- (OCDataConverter *)assembledConverterFrom:(OCDataItemType)inputType to:(OCDataItemType)outputType +{ + return ([self _assembledConverterFrom:inputType to:outputType topLevel:YES]); + +} + +- (OCDataConverter *)_assembledConverterFrom:(OCDataItemType)inputType to:(OCDataItemType)outputType topLevel:(BOOL)topLevel +{ + OCDataConverter *converter = nil; + + // Return existing converter for input/output pair + if ((converter = [self converterFrom:inputType to:outputType]) != nil) + { + return (converter); + } + + // Attempt to assemble new pair + NSArray *converters = _convertersByTypePair.allValues; + for (OCDataConverter *inputConverter in converters) + { + if ([inputConverter.inputType isEqual:inputType]) + { + // Input type matches + OCDataConverter *outputConverter; + + if ((outputConverter = [self _assembledConverterFrom:inputConverter.outputType to:outputType topLevel:NO]) != nil) + { + OCDataConverterPipeline *outputPipeline = OCTypedCast(outputConverter, OCDataConverterPipeline); + NSArray *assembledConverters = nil; + + if ((outputPipeline != nil) && (outputPipeline.converters.count > 0)) + { + // Flatten pipelines + assembledConverters = [@[ inputConverter ] arrayByAddingObjectsFromArray:outputPipeline.converters]; + } + else + { + // Assemble from converters + assembledConverters = @[ + inputConverter, outputConverter + ]; + } + + // Prefer shorter pipelines + if ((converter == nil) || ((converter != nil) && (((OCDataConverterPipeline *)converter).converters.count > assembledConverters.count))) + { + converter = [[OCDataConverterPipeline alloc] initWithConverters:assembledConverters]; + } + } + } + } + + if (topLevel && (converter != nil)) + { + // Add assembled converter + [self addConverters:@[ + converter + ]]; + } + + return (converter); +} + +- (nullable id)renderObject:(id)object ofType:(OCDataItemType)inputType asType:(OCDataItemType)outputType error:(NSError * _Nullable * _Nullable)outError withOptions:(nullable OCDataViewOptions)options +{ + OCDataConverter *converter; + + // Catch lack of input type + if (inputType == nil) + { + if (outError != NULL) + { + *outError = OCError(OCErrorDataItemTypeUnavailable); + } + + return (nil); + } + + // Check if input and output type are identical + if ([inputType isEqual:outputType]) + { + // object already has requested type - return it directly + return (object); + } + + // Find suitable converter + if ((converter = [self assembledConverterFrom:inputType to:outputType]) != nil) + { + // Perform conversion + return ([converter convert:object renderer:self error:outError withOptions:options]); + } + else + { + // No converter available for conversion from inputType to outputType + if (outError != NULL) + { + *outError = OCError(OCErrorDataConverterUnavailable); + } + } + + return (nil); +} + +- (nullable id)renderItem:(id)item asType:(OCDataItemType)outputType error:(NSError * _Nullable * _Nullable)outError withOptions:(nullable OCDataViewOptions)options +{ + OCDataItemType inputType; + + if ((inputType = item.dataItemType) != nil) + { + return ([self renderObject:item ofType:inputType asType:outputType error:outError withOptions:options]); + } + else + { + // Item does not return a dataItemType + if (outError != NULL) + { + *outError = OCError(OCErrorDataItemTypeUnavailable); + } + } + + return (nil); +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.h b/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.h new file mode 100644 index 00000000..9503cfa2 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.h @@ -0,0 +1,40 @@ +// +// OCDataSourceArray.h +// ownCloudSDK +// +// Created by Felix Schwarz on 22.03.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 "OCDataSource.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCDataSourceArray : OCDataSource +{ + NSMapTable> *_itemsByReference; + NSMapTable *_versionsByReference; +} + +@property(copy,nullable) OCDataItemHasChildrenProvider dataItemHasChildrenProvider; +@property(assign) BOOL trackItemVersions; //!< If YES, tracks versions internally, allowing to detect/track changes to the same object + +- (instancetype)initWithItems:(nullable NSArray> *)items; + +- (void)setItems:(nullable NSArray> *)items updated:(nullable NSSet> *)updatedItems; //!< Uses item IDs to populate the data source +- (void)setVersionedItems:(nullable NSArray> *)items; //!< Uses item versions and item IDs to populate the data source + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.m b/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.m new file mode 100644 index 00000000..702acd75 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Array Backed/OCDataSourceArray.m @@ -0,0 +1,171 @@ +// +// OCDataSourceArray.m +// ownCloudSDK +// +// Created by Felix Schwarz on 22.03.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 "OCDataSourceArray.h" + +@implementation OCDataSourceArray + +- (instancetype)initWithItems:(nullable NSArray> *)items +{ + if ((self = [self init]) != nil) + { + [self setItems:items updated:nil]; + } + + return (self); +} + +- (void)setItems:(nullable NSArray> *)items updated:(nullable NSSet> *)updatedItems +{ + NSMapTable> *itemsByReference = [NSMapTable strongToStrongObjectsMapTable]; + NSMutableArray *itemReferences = [NSMutableArray new]; + NSMutableSet *updatedItemReferences = (updatedItems != nil) ? [NSMutableSet new] : nil; + + for (id item in items) + { + OCDataItemReference itemRef; + + if ((itemRef = item.dataItemReference) != nil) + { + [itemsByReference setObject:item forKey:itemRef]; + [itemReferences addObject:itemRef]; + } + } + + if (updatedItems != nil) + { + for (id item in updatedItems) + { + OCDataItemReference itemRef; + + if ((itemRef = item.dataItemReference) != nil) + { + [updatedItemReferences addObject:itemRef]; + } + } + } + + @synchronized(_subscriptions) + { + _itemsByReference = itemsByReference; + [self setItemReferences:itemReferences updated:updatedItemReferences]; + } +} + +- (void)setVersionedItems:(nullable NSArray> *)items +{ + NSMutableSet> *updatedItems = nil; + + @synchronized(_subscriptions) + { + if (_trackItemVersions) + { + if (_versionsByReference == nil) { + _versionsByReference = [NSMapTable weakToStrongObjectsMapTable]; + } + } + + for (id item in items) + { + OCDataItemReference itemRef; + + if ((itemRef = item.dataItemReference) != nil) + { + id oldItem; + + OCDataItemVersion newVersion = item.dataItemVersion; + + if ((oldItem = (id)[_itemsByReference objectForKey:itemRef]) != nil) + { + OCDataItemVersion newVersion = item.dataItemVersion; + OCDataItemVersion oldVersion = _trackItemVersions ? [_versionsByReference objectForKey:itemRef] : oldItem.dataItemVersion; + + if (![newVersion isEqual:oldVersion]) + { + if (updatedItems == nil) { updatedItems = [NSMutableSet new]; } + + [updatedItems addObject:item]; + } + } + + if (_trackItemVersions) + { + [_versionsByReference setObject:newVersion forKey:itemRef]; + } + } + } + } + + [self setItems:items updated:updatedItems]; +} + +- (nullable OCDataItemRecord *)recordForItemRef:(OCDataItemReference)itemRef error:(NSError * _Nullable * _Nullable)error +{ + id item; + + @synchronized(_subscriptions) + { + item = [_itemsByReference objectForKey:itemRef]; + } + + if (item != nil) + { + BOOL hasChildren = NO; + + if (_dataItemHasChildrenProvider != nil) + { + hasChildren = _dataItemHasChildrenProvider(self, item); + } + else + { + if ([item respondsToSelector:@selector(hasChildrenUsingSource:)]) + { + hasChildren = [item hasChildrenUsingSource:self]; + } + } + + return ([[OCDataItemRecord alloc] initWithSource:self item:item hasChildren:hasChildren]); + } + + return (nil); +} + +- (void)retrieveItemForRef:(OCDataItemReference)itemRef reusingRecord:(OCDataItemRecord *)reuseRecord completionHandler:(OCDataSourceItemForReferenceCompletionHandler)completionHandler +{ + id item; + OCDataItemRecord *itemRecord = reuseRecord; + NSError *error = nil; + + @synchronized(_subscriptions) + { + item = [_itemsByReference objectForKey:itemRef]; + } + + if (reuseRecord != nil) + { + reuseRecord.item = item; + } + else + { + itemRecord = [self recordForItemRef:itemRef error:&error]; + } + + completionHandler(error, itemRecord); +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.h b/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.h new file mode 100644 index 00000000..04a9334a --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.h @@ -0,0 +1,53 @@ +// +// OCDataSourceComposition.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.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 "OCDataSourceArray.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCDataSourceComposition : OCDataSourceArray + +@property(strong,nonatomic) NSArray *sources; + +#pragma mark - Filter and sorting helpers ++ (OCDataSourceItemFilter)itemFilterWithItemRetrieval:(BOOL)itemRetrieval fromRecordFilter:(OCDataSourceItemRecordFilter)itemRecordFilter; //!< Adds boilerplate code to itemRecordFilter to retrieve the item records for the item references. If itemRetrieval is true, will synchronously retrieve the item before invoking itemRecordFilter. ++ (OCDataSourceItemComparator)itemComparatorWithItemRetrieval:(BOOL)itemRetrieval fromRecordComparator:(OCDataSourceItemRecordComparator)itemRecordComparator; //!< Adds boilerplate code to itemRecordComparator to retrieve the item records for the item references. If itemRetrieval is true, will synchronously retrieve the items before invoking itemRecordComparator. + +#pragma mark - Initialization +- (instancetype)initWithSources:(NSArray *)sources applyCustomizations:(nullable void(^)(OCDataSourceComposition *))customizationApplicator; //!< Creates a new data source composed from other data sources. The passed customizationApplicator will be called before the initial composition, allowing to apply customizations like filters and sort comparators. + +#pragma mark - Lookup +- (nullable OCDataSource *)dataSourceForItemReference:(OCDataItemReference)itemRef; //!< Returns the data source that stores itemRef - or nil if none of the data sources contained the item + +#pragma mark - Add/Remove sources +- (void)addSources:(NSArray *)sources; +- (void)insertSources:(NSArray *)sources after:(OCDataSource *)otherSource; +- (void)removeSources:(NSArray *)source; + +#pragma mark - Filtering and sorting (merged item set) +@property(copy,nullable,nonatomic) OCDataSourceItemFilter filter; //!< Filter to apply to the combined item set +@property(copy,nullable,nonatomic) OCDataSourceItemComparator sortComparator; //!< Sort comparator to apply to the combined item set + +#pragma mark - Filtering and sorting (individual sources) +- (void)setInclude:(BOOL)include forSource:(OCDataSource *)source; //!< Include or exclude items from an individual source +- (void)setFilter:(nullable OCDataSourceItemFilter)filter forSource:(OCDataSource *)source; //!< Filter to apply to an individual source +- (void)setSortComparator:(nullable OCDataSourceItemComparator)sortComparator forSource:(OCDataSource *)source; //!< Filter to apply to an individual source + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.m b/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.m new file mode 100644 index 00000000..9c5df153 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Composition/OCDataSourceComposition.m @@ -0,0 +1,605 @@ +// +// OCDataSourceComposition.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.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 "OCDataSourceComposition.h" +#import "NSArray+OCFiltering.h" +#import "OCMacros.h" + +#pragma mark - Record definition +@interface OCDataSourceCompositionRecord : NSObject + +@property(assign) NSRange itemRange; + +@property(weak,nullable) OCDataSourceComposition *composition; + +@property(strong,readonly) OCDataSource *source; +@property(strong,nullable) OCDataSourceSubscription *subscription; +@property(strong,nullable) OCDataSourceSnapshot *activeSnapshot; + +@property(copy,nullable) OCDataSourceItemFilter filter; +@property(copy,nullable) OCDataSourceItemComparator sortComparator; + +@property(assign) BOOL include; + +@property(assign) BOOL hasUpdates; + +- (instancetype)initWithSource:(OCDataSource *)source composition:(OCDataSourceComposition *)composition; + +@end + +#pragma mark - Composition +@interface OCDataSourceComposition () +{ + NSMutableArray *_sourceRecords; + + NSMapTable *_compositionRecordByItemReference; + + dispatch_queue_t _compositionQueue; + + BOOL _compositionNeedsUpdate; + + BOOL _supressNeedsCompositionUpdates; +} +@end + +@implementation OCDataSourceComposition + ++ (dispatch_queue_t)sharedCompositionQueue +{ + static dispatch_once_t onceToken; + static dispatch_queue_t sharedCompositionQueue; + + dispatch_once(&onceToken, ^{ + sharedCompositionQueue = dispatch_queue_create("DataSourceComposition queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + }); + + return (sharedCompositionQueue); +} + +- (instancetype)initWithSources:(NSArray *)sources applyCustomizations:(nullable void (^)(OCDataSourceComposition * _Nonnull))customizationApplicator +{ + if ((self = [super init]) != nil) + { + _sourceRecords = [NSMutableArray new]; + + _compositionQueue = OCDataSourceComposition.sharedCompositionQueue; + + if (customizationApplicator != nil) + { + // Set up sources without initial composition update + _supressNeedsCompositionUpdates = YES; + + self.sources = sources; + + // Apply customizations + customizationApplicator(self); + + // Initial composition update + _supressNeedsCompositionUpdates = NO; + [self setNeedsCompositionUpdate]; + } + else + { + // Set up sources with initial composition update + self.sources = sources; + } + } + + return (self); +} + +#pragma mark - Filter and sorting helpers ++ (OCDataSourceItemFilter)itemFilterWithItemRetrieval:(BOOL)itemRetrieval fromRecordFilter:(OCDataSourceItemRecordFilter)itemRecordFilter +{ + return (^(OCDataSource *source, OCDataItemReference itemRef) { + __block OCDataItemRecord *record; + + record = [source recordForItemRef:itemRef error:NULL]; + + if ((record.item == nil) && itemRetrieval) + { + OCSyncExec(RetrieveItem, { + [record retrieveItemWithCompletionHandler:^(NSError * _Nullable error, OCDataItemRecord * _Nullable retrievedRecord) { + OCSyncExecDone(RetrieveItem); + + record = retrievedRecord; + }]; + }); + } + + return (itemRecordFilter(record)); + }); +} + ++ (OCDataSourceItemComparator)itemComparatorWithItemRetrieval:(BOOL)itemRetrieval fromRecordComparator:(OCDataSourceItemRecordComparator)itemRecordComparator +{ + return (^(OCDataSource *source1, OCDataItemReference itemRef1, OCDataSource *source2, OCDataItemReference itemRef2) { + __block OCDataItemRecord *record1, *record2; + + record1 = [source1 recordForItemRef:itemRef1 error:NULL]; + if ((record1.item == nil) && itemRetrieval) + { + OCSyncExec(RetrieveItem, { + [record1 retrieveItemWithCompletionHandler:^(NSError * _Nullable error, OCDataItemRecord * _Nullable retrievedRecord) { + OCSyncExecDone(RetrieveItem); + + record1 = retrievedRecord; + }]; + }); + } + + record2 = [source2 recordForItemRef:itemRef2 error:NULL]; + if ((record2.item == nil) && itemRetrieval) + { + OCSyncExec(RetrieveItem, { + [record2 retrieveItemWithCompletionHandler:^(NSError * _Nullable error, OCDataItemRecord * _Nullable retrievedRecord) { + OCSyncExecDone(RetrieveItem); + + record2 = retrievedRecord; + }]; + }); + } + + return (itemRecordComparator(record1,record2)); + }); +} + +- (void)setFilter:(OCDataSourceItemFilter)filter +{ + _filter = [filter copy]; + [self setNeedsCompositionUpdate]; +} + +- (void)setSortComparator:(OCDataSourceItemComparator)sortComparator +{ + _sortComparator = [sortComparator copy]; + [self setNeedsCompositionUpdate]; +} + +- (void)setSortComparator:(OCDataSourceItemComparator)sortComparator forSource:(OCDataSource *)source +{ + @synchronized(_sourceRecords) + { + [self recordForSource:source].sortComparator = sortComparator; + } + + [self setNeedsCompositionUpdate]; +} + +- (void)setFilter:(OCDataSourceItemFilter)filter forSource:(OCDataSource *)source +{ + @synchronized(_sourceRecords) + { + [self recordForSource:source].filter = filter; + } + + [self setNeedsCompositionUpdate]; +} + +- (void)setInclude:(BOOL)include forSource:(OCDataSource *)source +{ + BOOL didChange = NO; + + @synchronized(_sourceRecords) + { + if ([self recordForSource:source].include != include) + { + [self recordForSource:source].include = include; + didChange = YES; + } + } + + if (didChange) + { + [self setNeedsCompositionUpdate]; + } +} + +#pragma mark - Sources +- (NSMutableArray *)_sourceRecordsForNewSources:(NSArray *)sources +{ + NSMutableArray *sourceRecords = [NSMutableArray new]; + + for (OCDataSource *source in sources) + { + OCDataSourceCompositionRecord *record; + + if ((record = [self recordForSource:source]) == nil) + { + record = [[OCDataSourceCompositionRecord alloc] initWithSource:source composition:self]; + } + + if (record != nil) + { + [sourceRecords addObject:record]; + } + } + + return (sourceRecords); +} + +- (void)setSources:(NSArray *)sources +{ + NSMutableArray *newSourceRecords = [self _sourceRecordsForNewSources:sources]; + + @synchronized(_sourceRecords) + { + _sources = sources; + [_sourceRecords setArray:newSourceRecords]; + } + + [self setNeedsCompositionUpdate]; +} + +- (void)addSources:(NSArray *)sources +{ + NSMutableArray *newSourceRecords = [self _sourceRecordsForNewSources:sources]; + + if (newSourceRecords.count > 0) + { + @synchronized(_sourceRecords) + { + [_sourceRecords addObjectsFromArray:newSourceRecords]; + } + + [self setNeedsCompositionUpdate]; + } +} + +- (void)insertSources:(NSArray *)sources after:(OCDataSource *)otherSource +{ + NSMutableArray *newSourceRecords = [self _sourceRecordsForNewSources:sources]; + + if (newSourceRecords.count > 0) + { + NSLog(@"BEF: Before: %@", _sourceRecords); + + @synchronized(_sourceRecords) + { + NSUInteger afterIndex = [_sourceRecords indexOfObjectPassingTest:^BOOL(OCDataSourceCompositionRecord * _Nonnull sourceRecord, NSUInteger idx, BOOL * _Nonnull stop) { + return (sourceRecord.source == otherSource); + }]; + + if (afterIndex != NSNotFound) + { + [_sourceRecords insertObjects:newSourceRecords atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(afterIndex, newSourceRecords.count)]]; + } + } + + NSLog(@"BEF: After: %@", _sourceRecords); + + [self setNeedsCompositionUpdate]; + } +} + +- (void)removeSources:(NSArray *)removeSources +{ + @synchronized(_sourceRecords) + { + NSArray *filteredRecords = nil; + + filteredRecords = [_sourceRecords filteredArrayUsingBlock:^BOOL(OCDataSourceCompositionRecord * _Nonnull sourceRecord, BOOL * _Nonnull stop) { + return ([removeSources indexOfObjectIdenticalTo:sourceRecord.source] == NSNotFound); + }]; + + [_sourceRecords setArray:filteredRecords]; + } + + [self setNeedsCompositionUpdate]; +} + +#pragma mark - Composition +- (dispatch_queue_t)compositionQueue +{ + return (_compositionQueue); +} + +- (OCDataSourceCompositionRecord *)recordForSource:(OCDataSource *)source +{ + @synchronized(_sourceRecords) + { + return ([_sourceRecords firstObjectMatching:^BOOL(OCDataSourceCompositionRecord * _Nonnull object) { + return (object.source == source); + }]); + } +} + +- (void)setNeedsCompositionUpdate +{ + if (_supressNeedsCompositionUpdates) + { + return; + } + + @synchronized(self) + { + _compositionNeedsUpdate = YES; + } + + __weak OCDataSourceComposition *weakSelf = self; + + dispatch_group_t synchronizationGroup = self.synchronizationGroup; + + if (synchronizationGroup != nil) + { + dispatch_group_enter(synchronizationGroup); + } + + dispatch_async(_compositionQueue, ^{ + OCDataSourceComposition *strongSelf; + + if ((strongSelf = weakSelf) != nil) + { + BOOL doUpdate = NO; + + @synchronized(strongSelf) + { + if (strongSelf->_compositionNeedsUpdate) + { + doUpdate = YES; + strongSelf->_compositionNeedsUpdate = NO; + } + } + + if (doUpdate) + { + [self _updateComposition]; + } + } + + if (synchronizationGroup != nil) + { + dispatch_group_leave(synchronizationGroup); + } + }); +} + +- (void)_updateComposition +{ + NSMutableArray *composedItemReferences = [NSMutableArray new]; + NSMutableSet *updatedItemReferences = [NSMutableSet new]; + NSArray *sourceRecords; + NSMapTable *compositionRecordByItemReference = nil; + + @synchronized(_sourceRecords) + { + sourceRecords = [_sourceRecords copy]; + } + + if (_sortComparator != nil) + { + compositionRecordByItemReference = [NSMapTable strongToWeakObjectsMapTable]; + } + + for (OCDataSourceCompositionRecord *record in sourceRecords) + { + NSRange itemRange = NSMakeRange(composedItemReferences.count, 0); + + // Skip records that shouldn't be included + if (!record.include) { + record.itemRange = NSMakeRange(NSUIntegerMax, 0); + continue; + } + + // Fetch updates where available + if (record.hasUpdates) + { + @synchronized(record) + { + if (record.hasUpdates) + { + OCDataSourceSnapshot *snapshot = [record.subscription snapshotResettingChangeTracking:YES]; + + record.activeSnapshot = snapshot; + record.hasUpdates = NO; + + [updatedItemReferences unionSet:snapshot.updatedItems]; + } + } + } + + // Add to composed array + NSArray *snapshotItems; + + if ((snapshotItems = record.activeSnapshot.items) != nil) + { + if (record.filter != nil) + { + // Apply source-specific filter + snapshotItems = [snapshotItems filteredArrayUsingBlock:^BOOL(OCDataItemReference _Nonnull itemRef, BOOL * _Nonnull stop) { + if (record.filter(record.source, itemRef)) + { + return (YES); + } + + [updatedItemReferences removeObject:itemRef]; + + return (NO); + }]; + } + + if (record.sortComparator != nil) + { + // Apply source-specific sorting + snapshotItems = [snapshotItems sortedArrayUsingComparator:^NSComparisonResult(OCDataItemReference itemRef1, OCDataItemReference itemRef2) { + return (record.sortComparator(record.source, itemRef1, record.source, itemRef2)); + }]; + } + + if ((_sortComparator != nil) || (_filter != nil)) + { + for (OCDataItemReference itemRef in snapshotItems) + { + BOOL includeItem = YES; + + if (_filter != nil) + { + includeItem = _filter(record.source, itemRef); + + if (includeItem) + { + // Only add items passing the filter + [composedItemReferences addObject:itemRef]; + } + else + { + // Remove updates for items that are not in the composed set of items + [updatedItemReferences removeObject:itemRef]; + } + } + + if ((_sortComparator != nil) && includeItem) + { + [compositionRecordByItemReference setObject:record forKey:itemRef]; + } + } + } + + if (_filter == nil) + { + [composedItemReferences addObjectsFromArray:snapshotItems]; + } + + itemRange.length = composedItemReferences.count - itemRange.location; + } + + record.itemRange = itemRange; + } + + // Propagate updates + @synchronized(_subscriptions) + { + // Make items available for sorting by reference + _compositionRecordByItemReference = compositionRecordByItemReference; + + // Sort items + if (_sortComparator != nil) + { + [composedItemReferences sortUsingComparator:^NSComparisonResult(OCDataItemReference reference1, OCDataItemReference reference2) { + return (_sortComparator(self, reference1, self, reference2)); + }]; + } + + // Update data source + [self setItemReferences:composedItemReferences updated:updatedItemReferences]; + } +} + +#pragma mark - Forwarding to underlying data sources +- (OCDataSource *)dataSourceForItemReference:(OCDataItemReference)itemRef +{ + @synchronized(_subscriptions) + { + if (_compositionRecordByItemReference != nil) + { + return ([_compositionRecordByItemReference objectForKey:itemRef].source); + } + + NSUInteger itemIndex = [_itemReferences indexOfObject:itemRef]; + + if (itemIndex != NSNotFound) + { + for (OCDataSourceCompositionRecord *record in _sourceRecords) + { + NSRange recordRange = record.itemRange; + + if ((itemIndex >= recordRange.location) && + (itemIndex < (recordRange.location + recordRange.length))) + { + return (record.source); + } + } + } + } + + return (nil); +} + +- (nullable OCDataItemRecord *)recordForItemRef:(OCDataItemReference)itemRef error:(NSError * _Nullable * _Nullable)error +{ + OCDataSource *datasource; + + if ((datasource = [self dataSourceForItemReference:itemRef]) != nil) + { + return ([datasource recordForItemRef:itemRef error:error]); + } + + return (nil); +} + +- (void)retrieveItemForRef:(OCDataItemReference)reference reusingRecord:(nullable OCDataItemRecord *)reuseRecord completionHandler:(OCDataSourceItemForReferenceCompletionHandler)completionHandler +{ + OCDataSource *datasource; + + if ((datasource = [self dataSourceForItemReference:reference]) != nil) + { + return ([datasource retrieveItemForRef:reference reusingRecord:reuseRecord completionHandler:completionHandler]); + } +} + +- (nullable OCDataSource *)dataSourceForChildrenOfItemReference:(OCDataItemReference)itemRef +{ + OCDataSource *datasource; + + if ((datasource = [self dataSourceForItemReference:itemRef]) != nil) + { + return ([datasource dataSourceForChildrenOfItemReference:itemRef]); + } + + return (nil); +} + +@end + + +#pragma mark - Record implementation +@implementation OCDataSourceCompositionRecord + +- (instancetype)initWithSource:(OCDataSource *)source composition:(OCDataSourceComposition *)composition +{ + if ((self = [super init]) != nil) + { + __weak OCDataSourceCompositionRecord *weakSelf = self; + + _composition = composition; + _include = YES; + + _source = source; + _subscription = [source associateWithUpdateHandler:^(OCDataSourceSubscription * _Nonnull subscription) { + [weakSelf updateWithSubscription:subscription]; + } onQueue:composition.compositionQueue trackDifferences:YES performInitialUpdate:YES]; + } + + return (self); +} + +- (void)dealloc +{ + [_subscription terminate]; +} + +- (void)updateWithSubscription:(OCDataSourceSubscription *)subscription +{ + @synchronized(self) + { + _hasUpdates = YES; + [_composition setNeedsCompositionUpdate]; + } +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.h b/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.h new file mode 100644 index 00000000..069318f1 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.h @@ -0,0 +1,45 @@ +// +// OCDataItemRecord.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 + +@class OCDataSource; + +@interface OCDataItemRecord : NSObject + +@property(weak,nullable) OCDataSource *source; + +@property(strong) OCDataItemReference reference; +@property(strong) OCDataItemType type; + +@property(assign) BOOL hasChildren; + +@property(strong,nullable) id item; + +- (instancetype)initWithSource:(nullable OCDataSource *)source itemType:(OCDataItemType)type itemReference:(OCDataItemReference)itemRef hasChildren:(BOOL)hasChildren item:(nullable id)item; + +- (instancetype)initWithSource:(nullable OCDataSource *)source item:(nullable id)item hasChildren:(BOOL)hasChildren; + +- (void)retrieveItemWithCompletionHandler:(void(^)(NSError * _Nullable error, OCDataItemRecord * _Nullable record))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.m b/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.m new file mode 100644 index 00000000..c90efa79 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Item Records/OCDataItemRecord.m @@ -0,0 +1,67 @@ +// +// OCDataItemRecord.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 "OCDataItemRecord.h" +#import "OCDataSource.h" + +@implementation OCDataItemRecord + +- (instancetype)initWithSource:(nullable OCDataSource *)source itemType:(OCDataItemType)type itemReference:(OCDataItemReference)itemRef hasChildren:(BOOL)hasChildren item:(nullable id)item +{ + if ((self = [super init]) != nil) + { + _source = source; + + _type = type; + _reference = itemRef; + + _hasChildren = hasChildren; + + _item = item; + } + + return (self); +} + +- (instancetype)initWithSource:(nullable OCDataSource *)source item:(nullable id)item hasChildren:(BOOL)hasChildren +{ + return ([self initWithSource:source itemType:item.dataItemType itemReference:item.dataItemReference hasChildren:hasChildren item:item]); +} + +- (void)retrieveItemWithCompletionHandler:(void (^)(NSError * _Nullable, OCDataItemRecord * _Nullable))completionHandler +{ + OCDataSource *strongSource; + + if (_item != nil) + { + completionHandler(nil, self); + return; + } + + if ((strongSource = _source) != nil) + { + [strongSource retrieveItemForRef:_reference reusingRecord:self completionHandler:^(NSError * _Nullable error, OCDataItemRecord * _Nullable record) { + if (completionHandler != nil) + { + completionHandler(error, record); + } + }]; + } +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.h b/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.h new file mode 100644 index 00000000..ecacfa27 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.h @@ -0,0 +1,34 @@ +// +// OCDataSourceKVO.h +// ownCloudSDK +// +// Created by Felix Schwarz on 16.05.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 "OCDataSourceArray.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSArray> * _Nullable (^OCDataSourceKVOItemUpdateHandler)(NSObject *object, NSString *keyPath, id _Nullable newValue, NSSet> * _Nullable * _Nullable outUpdatedItems); +typedef NSArray> * _Nullable (^OCDataSourceKVOVersionedItemUpdateHandler)(NSObject *object, NSString *keyPath, id _Nullable newValue); + +@interface OCDataSourceKVO : OCDataSourceArray + +- (instancetype)initWithObject:(NSObject *)object keyPath:(NSString *)keyPath itemUpdateHandler:(OCDataSourceKVOItemUpdateHandler)itemUpdateHandler; //!< Create a data source based on the key-value observation of an object. The itemUpdateHandler returns an array of OCDataItems and a set of updated items via outUpdatedItems. + +- (instancetype)initWithObject:(NSObject *)object keyPath:(NSString *)keyPath versionedItemUpdateHandler:(nullable OCDataSourceKVOVersionedItemUpdateHandler)versionedItemUpdateHandler; //!< Create a data source based on the key-value observation of an object. The versionedItemUpdateHandler returns an array of OCDataItems that also conform to OCDataItemVersioning. Passing nil will use the observation value as that array. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.m b/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.m new file mode 100644 index 00000000..5b6a5508 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/KVO Backed/OCDataSourceKVO.m @@ -0,0 +1,136 @@ +// +// OCDataSourceKVO.m +// ownCloudSDK +// +// Created by Felix Schwarz on 16.05.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 "OCDataSourceKVO.h" +#import "OCDeallocAction.h" + +@interface OCDataSourceKVO () +{ + __weak NSObject *_object; + NSString *_keyPath; + + BOOL _kvoRegistered; + + OCDataSourceKVOItemUpdateHandler _itemUpdateHandler; + OCDataSourceKVOVersionedItemUpdateHandler _versionedItemUpdateHandler; +} + +@end + +@implementation OCDataSourceKVO + +- (instancetype)initWithObject:(NSObject *)object keyPath:(NSString *)keyPath itemUpdateHandler:(OCDataSourceKVOItemUpdateHandler)itemUpdateHandler +{ + if ((self = [self initWithObject:object keyPath:keyPath]) != nil) + { + _itemUpdateHandler = [itemUpdateHandler copy]; + + [self registerKVO]; + } + + return (self); +} + +- (instancetype)initWithObject:(NSObject *)object keyPath:(NSString *)keyPath versionedItemUpdateHandler:(OCDataSourceKVOVersionedItemUpdateHandler)versionedItemUpdateHandler +{ + if ((self = [self initWithObject:object keyPath:keyPath]) != nil) + { + if (versionedItemUpdateHandler != nil) + { + _versionedItemUpdateHandler = [versionedItemUpdateHandler copy]; + } + + [self registerKVO]; + } + + return (self); +} + +- (instancetype)initWithObject:(NSObject *)object keyPath:(NSString *)keyPath +{ + if ((self = [super init]) != nil) + { + _keyPath = keyPath; + _object = object; + } + + return (self); +} + +- (void)dealloc +{ + [self unregisterKVO]; +} + +- (void)registerKVO +{ + __weak OCDataSourceKVO *weakSelf = self; + + if ((_object != nil) && (!_kvoRegistered)) + { + [_object addObserver:self forKeyPath:_keyPath options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:(__bridge void *)self]; + _kvoRegistered = YES; + + [OCDeallocAction addAction:^{ + [weakSelf unregisterKVO]; + } forDeallocationOfObject:_object]; + } +} + +- (void)unregisterKVO +{ + if (_kvoRegistered) + { + [_object removeObserver:self forKeyPath:_keyPath context:(__bridge void *)self]; + _kvoRegistered = NO; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + id newValue = change[NSKeyValueChangeNewKey]; + + if ([newValue isKindOfClass:NSNull.class]) + { + newValue = nil; + } + + if (_itemUpdateHandler != nil) + { + NSSet> *updatedItems = nil; + NSArray> *items; + + items = _itemUpdateHandler(object, keyPath, newValue, &updatedItems); + + [self setItems:items updated:updatedItems]; + } + else if (_versionedItemUpdateHandler != nil) + { + NSArray> *versionedItems; + + versionedItems = _versionedItemUpdateHandler(object, keyPath, newValue); + + [self setVersionedItems:versionedItems]; + } + else + { + [self setVersionedItems:(NSArray> *)newValue]; + } +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.h b/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.h new file mode 100644 index 00000000..c51fd679 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.h @@ -0,0 +1,39 @@ +// +// OCDataSourceMapped.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.11.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 + +NS_ASSUME_NONNULL_BEGIN + +@class OCDataSourceMapped; + +typedef _Nullable id(^OCDataSourceMappedItemCreator)(OCDataSourceMapped *mappedSource, id fromItem); +typedef _Nonnull id(^OCDataSourceMappedItemUpdater)(OCDataSourceMapped *mappedSource, id fromItem, id mappedItem); +typedef void(^OCDataSourceMappedItemDestroyer)(OCDataSourceMapped *mappedSource, OCDataItemReference fromItemReference, id mappedItem); + +@interface OCDataSourceMapped : OCDataSourceArray + +@property(nullable,strong,nonatomic) OCDataSource *source; + +- (instancetype)initWithItems:(nullable NSArray> *)items NS_UNAVAILABLE; + +- (instancetype)initWithSource:(OCDataSource *)source creator:(OCDataSourceMappedItemCreator)itemCreator updater:(nullable OCDataSourceMappedItemUpdater)updater destroyer:(nullable OCDataSourceMappedItemDestroyer)destroyer queue:(nullable dispatch_queue_t)queue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.m b/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.m new file mode 100644 index 00000000..be3e0ba3 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Mapped/OCDataSourceMapped.m @@ -0,0 +1,188 @@ +// +// OCDataSourceMapped.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.11.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 "OCDataSourceMapped.h" + +@implementation OCDataSourceMapped +{ + NSMutableArray> *_mappedItems; + NSMapTable> *_mappedItemBySourceItemReference; + + OCDataSourceSubscription *_subscription; + + OCDataSourceMappedItemCreator _itemCreator; + OCDataSourceMappedItemUpdater _itemUpdater; + OCDataSourceMappedItemDestroyer _itemDestroyer; + + dispatch_queue_t _queue; +} + +- (instancetype)initWithSource:(OCDataSource *)source creator:(OCDataSourceMappedItemCreator)itemCreator updater:(nullable OCDataSourceMappedItemUpdater)itemUpdater destroyer:(nullable OCDataSourceMappedItemDestroyer)itemDestroyer queue:(nullable dispatch_queue_t)queue +{ + if ((self = [super initWithItems:nil]) != nil) + { + _itemCreator = itemCreator; + _itemUpdater = itemUpdater; + _itemDestroyer = itemDestroyer; + + _mappedItems = NSMutableArray.new; + _mappedItemBySourceItemReference = NSMapTable.strongToWeakObjectsMapTable; + + _queue = queue; + + self.source = source; + } + + return (self); +} + +- (void)dealloc +{ + self.source = nil; +} + +- (void)setSource:(OCDataSource *)source +{ + if ((_source != nil) && (source != _source)) + { + [_subscription terminate]; + _subscription = nil; + + if (_itemDestroyer != nil) + { + NSDictionary> *mappedItemBySourceItemReference = _mappedItemBySourceItemReference.dictionaryRepresentation; + + [mappedItemBySourceItemReference enumerateKeysAndObjectsUsingBlock:^(OCDataItemReference _Nonnull sourceItemRef, id _Nonnull mappedItem, BOOL * _Nonnull stop) { + _itemDestroyer(self, sourceItemRef, mappedItem); + }]; + + [_mappedItems removeAllObjects]; + [_mappedItemBySourceItemReference removeAllObjects]; + } + } + + _source = source; + + if (source != nil) + { + __weak OCDataSourceMapped *weakSelf = self; + _subscription = [_source associateWithUpdateHandler:^(OCDataSourceSubscription * _Nonnull subscription) { + OCDataSourceSnapshot *snapshot; + + if ((snapshot = [subscription snapshotResettingChangeTracking:YES]) != nil) + { + [weakSelf _handleSourceSnapshot:snapshot]; + } + } onQueue:_queue trackDifferences:YES performInitialUpdate:YES]; + } +} + +- (void)_handleSourceSnapshot:(OCDataSourceSnapshot *)snapshot +{ + // Remove items + for (OCDataItemReference removedItemReference in snapshot.removedItems) + { + id mappedItem = [_mappedItemBySourceItemReference objectForKey:removedItemReference]; + + if (_itemDestroyer != nil) + { + _itemDestroyer(self, removedItemReference, mappedItem); + } + + [_mappedItems removeObject:mappedItem]; + } + + // Added items + NSArray *addedItems; + + if ((_mappedItems.count == 0) && (snapshot.numberOfItems > 0)) + { + addedItems = snapshot.items; + } + else + { + addedItems = snapshot.addedItems.allObjects; + } + + for (OCDataItemReference addedItemReference in addedItems) + { + NSError *error = nil; + OCDataItemRecord *addedItemRecord = [_source recordForItemRef:addedItemReference error:&error]; + + if (addedItemRecord != nil) + { + id mappedItem; + + if ((mappedItem = _itemCreator(self, addedItemRecord.item)) != nil) + { + [_mappedItems addObject:mappedItem]; + [_mappedItemBySourceItemReference setObject:mappedItem forKey:addedItemReference]; + } + } + } + + // Update items + NSMutableSet> *updatedItems = [NSMutableSet new]; + + for (OCDataItemReference updatedItemReference in snapshot.updatedItems) + { + NSError *error = nil; + OCDataItemRecord *updatedItemRecord = [_source recordForItemRef:updatedItemReference error:&error]; + id existingMappedItem = [_mappedItemBySourceItemReference objectForKey:updatedItemReference]; + + if ((updatedItemRecord != nil) && (existingMappedItem != nil)) + { + id updatedMappedItem; + + if (_itemUpdater != nil) + { + updatedMappedItem = _itemUpdater(self, updatedItemRecord.item, existingMappedItem); + } + else + { + updatedMappedItem = existingMappedItem; + } + + if (updatedMappedItem != existingMappedItem) + { + [_mappedItems removeObject:existingMappedItem]; + [_mappedItems addObject:updatedMappedItem]; + [_mappedItemBySourceItemReference setObject:updatedMappedItem forKey:updatedItemReference]; + } + + [updatedItems addObject:updatedMappedItem]; + } + } + + // Compose items array (establish same order and corresponding contents as source) + NSMutableArray> *items = [NSMutableArray new]; + + for (OCDataItemReference sourceItemReference in snapshot.items) + { + id mappedItem; + + if ((mappedItem = [_mappedItemBySourceItemReference objectForKey:sourceItemReference]) != nil) + { + [items addObject:mappedItem]; + } + } + + [self setItems:items updated:updatedItems]; +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/OCDataSource.h b/ownCloudSDK/Data Sources/Sources/OCDataSource.h new file mode 100644 index 00000000..92e77868 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/OCDataSource.h @@ -0,0 +1,99 @@ +// +// OCDataSource.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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" +#import "OCDataSourceSubscription.h" +#import "OCDataItemRecord.h" +#import "OCCache.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef OCDataItemRecord * _Nullable (^OCDataSourceItemRecordForReferenceProvider)(OCDataSource *source, OCDataItemReference itemRef, NSError * _Nullable * _Nullable error); + +typedef void(^OCDataSourceItemForReferenceCompletionHandler)(NSError * _Nullable error, OCDataItemRecord * _Nullable record); +typedef void(^OCDataSourceItemForReferenceProvider)(OCDataSource *source, OCDataItemReference itemRef, OCDataItemRecord * _Nullable reuseRecord, OCDataSourceItemForReferenceCompletionHandler completionHandler); + +typedef OCDataSource * _Nullable (^OCDataSourceChildDataSourceProvider)(OCDataSource *source, OCDataItemReference itemRef); + +typedef void(^OCDataSourceSubscriptionObserver)(OCDataSource *source, id owner, BOOL hasSubscribers); + +typedef NS_ENUM(NSInteger, OCDataSourceState) +{ + OCDataSourceStateLoading, + OCDataSourceStateIdle +}; + +@interface OCDataSource : NSObject +{ + NSMutableArray *_itemReferences; + NSMutableArray *_subscriptions; +} + +@property(strong) OCDataSourceUUID uuid; +@property(strong,nullable) OCDataSourceType type; + +@property(assign) OCDataSourceState state; + +@property(readonly,nonatomic) NSUInteger numberOfItems; +@property(strong,nullable) NSDictionary> *specialItems; //!< Headers, Footers, … - !! please note setting this alone does not currently trigger an update, only changes to the data sources itemReferences !! If this is needed in the future, subscriptions also need to track specialItems and their updates. Therefore, if specialItems change alongside content, make sure to update .specialItems BEFORE updating the content. + +#pragma mark - Item retrieval +@property(copy,nullable) OCDataSourceItemRecordForReferenceProvider itemRecordForReferenceProvider; +@property(copy,nullable) OCDataSourceItemForReferenceProvider itemForReferenceProvider; + +- (nullable OCDataItemRecord *)recordForItemRef:(OCDataItemReference)itemRef error:(NSError * _Nullable * _Nullable)error; +- (void)retrieveItemForRef:(OCDataItemReference)reference reusingRecord:(nullable OCDataItemRecord *)reuseRecord completionHandler:(OCDataSourceItemForReferenceCompletionHandler)completionHandler; + +#pragma mark - Caching +- (void)cacheItem:(id)item forItemRef:(OCDataItemReference)reference; +- (nullable id)cachedItemForItemRef:(OCDataItemReference)reference; + +- (void)invalidateCacheForItemRef:(OCDataItemReference)reference; +- (void)invalidateCache; + +#pragma mark - Children +@property(copy,nullable) OCDataSourceChildDataSourceProvider childDataSourceProvider; +- (nullable OCDataSource *)dataSourceForChildrenOfItemReference:(OCDataItemReference)itemRef; + +#pragma mark - Managing subscriptions +- (OCDataSourceSubscription *)subscribeWithUpdateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(nullable dispatch_queue_t)updateQueue trackDifferences:(BOOL)trackDifferences performInitialUpdate:(BOOL)performInitialUpdate; //!< Subscribes for updates to the datasource. The subscription needs to be explicitely terminated when no longer used. The subscription does NOT auto-terminate by the release of all strong references to it. +- (OCDataSourceSubscription *)associateWithUpdateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(dispatch_queue_t)updateQueue trackDifferences:(BOOL)trackDifferences performInitialUpdate:(BOOL)performInitialUpdate; //!< Like subscribeWithUpdateHandler, but for subscriptions feeding other data sources. Do not use this API unless you implement a new data source type. +- (void)terminateSubscription:(OCDataSourceSubscription *)subscription; //!< Terminates a subscription. Calling OCDataSourceSubscription.terminate() is preferred. + +#pragma mark - Observing subscriptions +- (void)addSubscriptionObserver:(OCDataSourceSubscriptionObserver)subscriptionObserver withOwner:(id)owner performInitial:(BOOL)performInitial; //!< Observes the subscription status of the data source and calls the provided block with the result of (subscriberCount > 0) when ever that result changes +- (void)removeSubscriptionObserverForOwner:(id)owner; //!< Removes the subscription observer for the owner. Performed automatically when owner is deallocated. + +#pragma mark - Managing content +- (void)setItemReferences:(nullable NSArray *)itemRefs updated:(nullable NSSet *)updatedItemRefs; +- (void)signalUpdatesForItemReferences:(nullable NSSet *)updatedItemRefs; + +#pragma mark - Synchronization +@property(strong,nullable) dispatch_group_t synchronizationGroup; //!< If the contents of several data sources should be updated "atomically", notifications need to be withheld until the last data source has completed updating. Providing this dispatch group allows synchronization of change notifications across data sources. The synchronizationGroup is only used for subscriptions with an .updateQueue. A warning is logged otherwise. + +@end + +extern OCDataSourceSpecialItem OCDataSourceSpecialItemHeader; +extern OCDataSourceSpecialItem OCDataSourceSpecialItemFooter; + +extern OCDataSourceSpecialItem OCDataSourceSpecialItemRootItem; +extern OCDataSourceSpecialItem OCDataSourceSpecialItemFolderStatistics; + +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/Data Sources/Sources/OCDataSource.m b/ownCloudSDK/Data Sources/Sources/OCDataSource.m new file mode 100644 index 00000000..3ed3342d --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/OCDataSource.m @@ -0,0 +1,258 @@ +// +// OCDataSource.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.03.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 "OCDataSource.h" +#import "OCDataSourceSubscription.h" +#import "OCDataSourceSubscription+Internal.h" +#import "NSError+OCError.h" + +@interface OCDataSource () +{ + OCCache> *_cachedItems; + + BOOL _hasSubscriptions; + + NSMapTable, OCDataSourceSubscriptionObserver> *_subscriptionObserversByOwner; +} +@end + +@implementation OCDataSource + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _itemReferences = [NSMutableArray new]; + _subscriptions = [NSMutableArray new]; + _subscriptionObserversByOwner = [NSMapTable weakToStrongObjectsMapTable]; + _uuid = NSUUID.UUID.UUIDString; + + _state = OCDataSourceStateIdle; + } + + return (self); +} + +- (void)dealloc +{ + NSArray *subscriptions; + + @synchronized (_subscriptions) + { + subscriptions = [_subscriptions copy]; + } + + for (OCDataSourceSubscription *subscription in subscriptions) + { + [self terminateSubscription:subscription]; + } +} + +#pragma mark - Item retrieval +- (OCDataItemRecord *)recordForItemRef:(OCDataItemReference)itemRef error:(NSError * _Nullable __autoreleasing *)error +{ + if (_itemRecordForReferenceProvider != nil) + { + return (_itemRecordForReferenceProvider(self, itemRef, error)); + } + + if (error != NULL) + { + *error = OCError(OCErrorFeatureNotImplemented); + } + + return (nil); +} + +- (void)retrieveItemForRef:(OCDataItemReference)itemRef reusingRecord:(nullable OCDataItemRecord *)reuseRecord completionHandler:(void(^)(NSError * _Nullable error, OCDataItemRecord * _Nullable record))completionHandler +{ + if (_itemForReferenceProvider != nil) + { + _itemForReferenceProvider(self, itemRef, reuseRecord, completionHandler); + return; + } + + completionHandler(OCError(OCErrorFeatureNotImplemented), nil); +} + +#pragma mark - Children +- (nullable OCDataSource *)dataSourceForChildrenOfItemReference:(OCDataItemReference)itemRef +{ + if (_childDataSourceProvider != nil) + { + return (_childDataSourceProvider(self, itemRef)); + } + + return (nil); +} + +#pragma mark - Caching +- (void)cacheItem:(id)item forItemRef:(OCDataItemReference)reference +{ + if (_cachedItems == nil) + { + _cachedItems = [[OCCache alloc] init]; + _cachedItems.countLimit = 30; + } + + [_cachedItems setObject:item forKey:reference]; +} + +- (nullable id)cachedItemForItemRef:(OCDataItemReference)reference +{ + return ([_cachedItems objectForKey:reference]); +} + +- (void)invalidateCacheForItemRef:(OCDataItemReference)reference +{ + [_cachedItems removeObjectForKey:reference]; +} + +- (void)invalidateCache +{ + [_cachedItems clearCache]; +} + +#pragma mark - Managing subscriptions +- (OCDataSourceSubscription *)subscribeWithUpdateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(dispatch_queue_t)updateQueue trackDifferences:(BOOL)trackDifferences performInitialUpdate:(BOOL)performInitialUpdate +{ + return ([self _subscribeWithUpdateHandler:updateHandler onQueue:updateQueue trackDifferences:trackDifferences performInitialUpdate:performInitialUpdate isInternal:NO]); +} + +- (OCDataSourceSubscription *)associateWithUpdateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(dispatch_queue_t)updateQueue trackDifferences:(BOOL)trackDifferences performInitialUpdate:(BOOL)performInitialUpdate +{ + return ([self _subscribeWithUpdateHandler:updateHandler onQueue:updateQueue trackDifferences:trackDifferences performInitialUpdate:performInitialUpdate isInternal:YES]); +} + +- (OCDataSourceSubscription *)_subscribeWithUpdateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(dispatch_queue_t)updateQueue trackDifferences:(BOOL)trackDifferences performInitialUpdate:(BOOL)performInitialUpdate isInternal:(BOOL)isInternal +{ + OCDataSourceSubscription *subscription; + + @synchronized (_subscriptions) + { + subscription = [[OCDataSourceSubscription alloc] initWithSource:self trackDifferences:trackDifferences itemReferences:_itemReferences updateHandler:updateHandler onQueue:updateQueue]; + subscription.isInterDataSourceSubscription = isInternal; + [_subscriptions addObject:subscription]; + + [self setHasSubscriptions:(_subscriptions.count > 0)]; + } + + if (performInitialUpdate) + { + [subscription setNeedsUpdateHandling]; + } + + return (subscription); +} + +- (void)terminateSubscription:(OCDataSourceSubscription *)subscription +{ + subscription.terminated = YES; + subscription.source = nil; + + @synchronized (_subscriptions) + { + [_subscriptions removeObject:subscription]; + + [self setHasSubscriptions:(_subscriptions.count > 0)]; + } +} + +#pragma mark - Observing subscriptions +- (void)setHasSubscriptions:(BOOL)hasSubscriptions +{ + @synchronized(_subscriptionObserversByOwner) + { + if (_hasSubscriptions != hasSubscriptions) + { + _hasSubscriptions = hasSubscriptions; + + for (id owner in _subscriptionObserversByOwner) + { + OCDataSourceSubscriptionObserver observer = [_subscriptionObserversByOwner objectForKey:owner]; + observer(self, owner, hasSubscriptions); + } + } + } +} + +- (void)addSubscriptionObserver:(OCDataSourceSubscriptionObserver)subscriptionObserver withOwner:(id)owner performInitial:(BOOL)performInitial +{ + subscriptionObserver = [subscriptionObserver copy]; + + @synchronized(_subscriptionObserversByOwner) + { + [_subscriptionObserversByOwner setObject:subscriptionObserver forKey:owner]; + + if (performInitial) + { + subscriptionObserver(self, owner, _hasSubscriptions); + } + } +} + +- (void)removeSubscriptionObserverForOwner:(id)owner +{ + @synchronized(_subscriptionObserversByOwner) + { + [_subscriptionObserversByOwner setObject:nil forKey:owner]; + } +} + +#pragma mark - Managing content +- (void)setItemReferences:(nullable NSArray *)itemRefs updated:(nullable NSSet *)updatedItemRefs +{ + @synchronized (_subscriptions) + { + if (_synchronizationGroup != nil) + { + dispatch_group_enter(_synchronizationGroup); + } + + [_itemReferences setArray:itemRefs]; + + for (OCDataSourceSubscription *subscription in _subscriptions) + { + [subscription _updateWithItemReferences:itemRefs updated:updatedItemRefs]; + } + + if (_synchronizationGroup != nil) + { + dispatch_group_leave(_synchronizationGroup); + } + } +} + +- (void)signalUpdatesForItemReferences:(nullable NSSet *)updatedItemRefs +{ + @synchronized (_subscriptions) + { + for (OCDataSourceSubscription *subscription in _subscriptions) + { + [subscription _updateWithItemReferences:_itemReferences updated:updatedItemRefs]; + } + } +} + +@end + +OCDataSourceSpecialItem OCDataSourceSpecialItemHeader = @"header"; +OCDataSourceSpecialItem OCDataSourceSpecialItemFooter = @"footer"; + +OCDataSourceSpecialItem OCDataSourceSpecialItemRootItem = @"rootItem"; +OCDataSourceSpecialItem OCDataSourceSpecialItemFolderStatistics = @"folderStatistics"; diff --git a/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.h b/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.h new file mode 100644 index 00000000..4dd97ae0 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.h @@ -0,0 +1,38 @@ +// +// OCDataSourceSnapshot.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 OCDataSourceSnapshot : NSObject + +@property(assign) NSUInteger numberOfItems; + +@property(strong) NSArray *items; //!< The current item references at the time of snapshot in their then current order + +@property(strong,nullable) NSSet *addedItems; //!< Added items since last snapshot +@property(strong,nullable) NSSet *updatedItems; //!< Updated items since last snapshot +@property(strong,nullable) NSSet *removedItems; //!< Removed items since last snapshot + +@property(strong,nullable) NSDictionary> *specialItems; //!< The current special items at the time of snapshot + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.m b/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.m new file mode 100644 index 00000000..021adf5c --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Snapshots/OCDataSourceSnapshot.m @@ -0,0 +1,23 @@ +// +// OCDataSourceSnapshot.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 "OCDataSourceSnapshot.h" + +@implementation OCDataSourceSnapshot + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.h b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.h new file mode 100644 index 00000000..98d528ed --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.h @@ -0,0 +1,33 @@ +// +// OCDataSourceSubscription+Internal.h +// ownCloudSDK +// +// Created by Felix Schwarz on 16.03.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 "OCDataSourceSubscription.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCDataSourceSubscription (Internal) + +@property(nonatomic) BOOL isInterDataSourceSubscription; + +- (void)_updateWithItemReferences:(nullable NSArray *)itemRefs updated:(nullable NSSet *)updatedItemRefs; +- (void)setNeedsUpdateHandling; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.m b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.m new file mode 100644 index 00000000..85a0b2bd --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription+Internal.m @@ -0,0 +1,231 @@ +// +// OCDataSourceSubscription+Internal.m +// ownCloudSDK +// +// Created by Felix Schwarz on 16.03.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 "OCDataSourceSubscription+Internal.h" +#import "OCDataSource.h" +#import "OCLogger.h" + +@implementation OCDataSourceSubscription (Internal) + +- (void)setNeedsUpdateHandling +{ + __weak OCDataSourceSubscription *weakSelf = self; + BOOL isInterDataSourceSubscription = _isInterDataSourceSubscription; + dispatch_group_t synchronizationGroup = self.source.synchronizationGroup; + dispatch_queue_t updateQueue = self.updateQueue; + dispatch_block_t updateHandlingBlock = ^{ + OCDataSourceSubscription *strongSelf; + OCDataSourceSubscriptionUpdateHandler updateHandler; + + if (((strongSelf = weakSelf) != nil) && ((updateHandler = strongSelf.updateHandler) != nil)) + { + @synchronized(self) { + if (strongSelf->_needsUpdateHandling) + { + strongSelf->_needsUpdateHandling = NO; + } + }; + + updateHandler(strongSelf); + } + + if (isInterDataSourceSubscription && (synchronizationGroup != nil)) + { + dispatch_group_leave(synchronizationGroup); + } + }; + + @synchronized(self) { + _needsUpdateHandling = YES; + }; + + if (isInterDataSourceSubscription && (synchronizationGroup != nil)) + { + dispatch_group_enter(synchronizationGroup); + } + + if (updateQueue != nil) + { + if ((synchronizationGroup != nil) && !isInterDataSourceSubscription) + { + dispatch_group_notify(synchronizationGroup, updateQueue, updateHandlingBlock); + } + else + { + dispatch_async(updateQueue, updateHandlingBlock); + } + } + else + { + if ((synchronizationGroup != nil) && !isInterDataSourceSubscription) + { + OCLogWarning(@"Subscription to a source using a .synchronizationGroup does not provide an .updateQueue - and may notify about updates before all data sources content is in sync."); + } + + updateHandlingBlock(); + } +} + +- (void)_updateWithItemReferences:(nullable NSArray *)newItemRefs updated:(nullable NSSet *)updatedItemRefs +{ + BOOL wasUpdated = NO; + + if ((_updatedItemRefs==nil) && (newItemRefs != nil) && (newItemRefs.count == _itemRefs.count) && [newItemRefs isEqual:_itemRefs]) + { + // No changes + return; + } + + @synchronized (_itemRefs) + { + if (!self.trackDifferences) + { + // Do not track differences + if (newItemRefs != nil) + { + [_itemRefs setArray:newItemRefs]; + } + else + { + [_itemRefs removeAllObjects]; + } + + wasUpdated = YES; + } + else + { + // Track differences + NSMutableSet *newlyAddedItemRefs = nil; + NSMutableSet *newlyUpdatedItemRefs = nil; + NSMutableSet *newlyRemovedItemRefs = nil; + NSMutableSet *previouslyRemovedAndNowReaddedItemRefs = nil; + NSMutableSet *previouslyAddedAndNowRemovedItemRefs = nil; + NSMutableSet *danglingUpdatedItemRefs = nil; + NSSet *existingItemRefs = nil; + BOOL itemRefsChanged = NO; + + if (_itemRefs != nil) + { + existingItemRefs = [[NSSet alloc] initWithArray:_itemRefs]; + newlyRemovedItemRefs = [existingItemRefs mutableCopy]; + } + + if ((_itemRefs.count == newItemRefs.count) && (_itemRefs != nil) && (newItemRefs != nil)) + { + if (![_itemRefs isEqualToArray:newItemRefs]) + { + itemRefsChanged = YES; + } + } + + if (updatedItemRefs != nil) + { + newlyUpdatedItemRefs = [updatedItemRefs mutableCopy]; + } + else + { + newlyUpdatedItemRefs = [NSMutableSet new]; + } + + if (newItemRefs != nil) + { + newlyAddedItemRefs = [[NSMutableSet alloc] initWithArray:newItemRefs]; + + // Find items that are supposed to be updated, but not contained in newItemRefs + if (updatedItemRefs != nil) + { + danglingUpdatedItemRefs = [newlyUpdatedItemRefs mutableCopy]; + [danglingUpdatedItemRefs minusSet:newlyAddedItemRefs]; + } + + // Find added and removed items + if (existingItemRefs != nil) + { + // Removed Items = (Old Items - New Items) + [newlyRemovedItemRefs minusSet:newlyAddedItemRefs]; + + // Added Items = (New Items - Old Items) + [newlyAddedItemRefs minusSet:existingItemRefs]; + } + + // Find items that were added and then removed + previouslyAddedAndNowRemovedItemRefs = [newlyRemovedItemRefs mutableCopy]; + [previouslyAddedAndNowRemovedItemRefs intersectSet:_addedItemRefs]; + + // Find items that were removed and then added + previouslyRemovedAndNowReaddedItemRefs = [newlyAddedItemRefs mutableCopy]; + [previouslyRemovedAndNowReaddedItemRefs intersectSet:_removedItemRefs]; + + // Find items that are supposed to be updated, but not contained in newItemRefs + if (danglingUpdatedItemRefs.count > 0) + { + OCLogWarning(@"Data Source Subscription told itemRef(s) %@ updated, but not in itemRefs, so can't be updated", danglingUpdatedItemRefs); + [newlyUpdatedItemRefs minusSet:danglingUpdatedItemRefs]; + } + + // Addition + subsequent removal -> drop any reference to item + [_addedItemRefs minusSet:previouslyAddedAndNowRemovedItemRefs]; + [_updatedItemRefs minusSet:previouslyAddedAndNowRemovedItemRefs]; + [newlyRemovedItemRefs minusSet:previouslyAddedAndNowRemovedItemRefs]; + + // Removal + subsequent addition -> drop reference from removal + addition, add to updates + [_removedItemRefs minusSet:previouslyRemovedAndNowReaddedItemRefs]; + [newlyAddedItemRefs minusSet:previouslyRemovedAndNowReaddedItemRefs]; + + [newlyUpdatedItemRefs unionSet:previouslyRemovedAndNowReaddedItemRefs]; + + // Add new additions + [_addedItemRefs unionSet:newlyAddedItemRefs]; + + // Add new updates, remove removed + [_updatedItemRefs unionSet:newlyUpdatedItemRefs]; + [_updatedItemRefs minusSet:newlyRemovedItemRefs]; + + // Add new removals + [_removedItemRefs unionSet:newlyRemovedItemRefs]; + + // Replace itemRefs content with newItemRefs + [_itemRefs setArray:newItemRefs]; + + if ((((_addedItemRefs.count > 0) || (_removedItemRefs.count > 0) || (_updatedItemRefs.count > 0)) && !_needsUpdateHandling) || + ((newlyAddedItemRefs.count > 0) || (newlyRemovedItemRefs.count > 0) || (newlyUpdatedItemRefs.count > 0) || itemRefsChanged) ) + { + wasUpdated = YES; + } + } + } + } + + if (wasUpdated) + { + // Notify of changes + [self setNeedsUpdateHandling]; + } +} + +- (BOOL)isInterDataSourceSubscription +{ + return (_isInterDataSourceSubscription); +} + +- (void)setIsInterDataSourceSubscription:(BOOL)isInterDataSourceSubscription +{ + _isInterDataSourceSubscription = isInterDataSourceSubscription; +} + +@end diff --git a/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.h b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.h new file mode 100644 index 00000000..e3695f97 --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.h @@ -0,0 +1,60 @@ +// +// OCDataSourceSubscription.h +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 "OCDataSourceSnapshot.h" + +@class OCDataSourceSnapshot; +@class OCDataSourceSubscription; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^OCDataSourceSubscriptionUpdateHandler)(OCDataSourceSubscription *subscription); + +@interface OCDataSourceSubscription : NSObject +{ + NSMutableArray *_itemRefs; + NSMutableSet *_addedItemRefs; + NSMutableSet *_updatedItemRefs; + NSMutableSet *_removedItemRefs; + + BOOL _needsUpdateHandling; + + BOOL _isInterDataSourceSubscription; +} + +@property(class,strong,readonly,nonatomic) dispatch_queue_t defaultUpdateQueue; + +@property(weak,nullable) OCDataSource *source; + +@property(strong,nullable) dispatch_queue_t updateQueue; +@property(copy,nullable) OCDataSourceSubscriptionUpdateHandler updateHandler; + +@property(assign) BOOL trackDifferences; + +@property(assign) BOOL terminated; + +- (instancetype)initWithSource:(OCDataSource *)source trackDifferences:(BOOL)trackDifferences itemReferences:(nullable NSArray *)itemRefs updateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(nullable dispatch_queue_t)updateQueue; + +- (BOOL)hasChangesSinceLastTrackingReset; //!< Returns YES if changes have occured since the last snapshot with change tracking reset. Returns NO otherwise. Will always return NO if .trackDifferences = NO or .terminated = YES. +- (OCDataSourceSnapshot *)snapshotResettingChangeTracking:(BOOL)resetChangeTracking; //!< Returns a snapshot. Added/Updated/Removed items are only filled if .trackDifferences = YES. Pass YES for resetChangeTracking to reset the change tracking. If you pass NO, changes will continue to accumulate. +- (void)terminate; //!< Terminates the subscription. When this call returns, .updateHandler will no longer be called. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.m b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.m new file mode 100644 index 00000000..e694f2bc --- /dev/null +++ b/ownCloudSDK/Data Sources/Sources/Subscriptions/OCDataSourceSubscription.m @@ -0,0 +1,110 @@ +// +// OCDataSourceSubscription.m +// ownCloudSDK +// +// Created by Felix Schwarz on 15.03.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 "OCDataSourceSubscription.h" +#import "OCDataSource.h" + +@implementation OCDataSourceSubscription + ++ (dispatch_queue_t)defaultUpdateQueue +{ + return (dispatch_get_main_queue()); +} + +- (instancetype)initWithSource:(OCDataSource *)source trackDifferences:(BOOL)trackDifferences itemReferences:(NSArray *)itemRefs updateHandler:(OCDataSourceSubscriptionUpdateHandler)updateHandler onQueue:(dispatch_queue_t)updateQueue +{ + if ((self = [super init]) != nil) + { + _source = source; + _trackDifferences = trackDifferences; + + self.updateHandler = updateHandler; + + _itemRefs = (itemRefs.count > 0) ? [itemRefs mutableCopy] : [NSMutableArray new]; + _addedItemRefs = [NSMutableSet new]; + _updatedItemRefs = [NSMutableSet new]; + _removedItemRefs = [NSMutableSet new]; + + _updateQueue = updateQueue; + } + + return (self); +} + +- (void)terminate +{ + [_source terminateSubscription:self]; + _terminated = YES; + + @synchronized (_itemRefs) + { + [_itemRefs removeAllObjects]; + [_addedItemRefs removeAllObjects]; + [_updatedItemRefs removeAllObjects]; + [_removedItemRefs removeAllObjects]; + + self.updateHandler = nil; + } +} + +- (BOOL)hasChangesSinceLastTrackingReset +{ + if (_trackDifferences && !_terminated) + { + @synchronized (_itemRefs) + { + return (_addedItemRefs.count > 0) || (_updatedItemRefs.count > 0) || (_removedItemRefs.count > 0); + } + } + + return (NO); +} + +- (OCDataSourceSnapshot *)snapshotResettingChangeTracking:(BOOL)resetChangeTracking +{ + OCDataSourceSnapshot *snapshot = [OCDataSourceSnapshot new]; + + @synchronized (_itemRefs) + { + snapshot.items = [_itemRefs copy]; + snapshot.numberOfItems = _itemRefs.count; + + if (resetChangeTracking) + { + snapshot.addedItems = _addedItemRefs; + snapshot.updatedItems = _updatedItemRefs; + snapshot.removedItems = _removedItemRefs; + + _addedItemRefs = [NSMutableSet new]; + _updatedItemRefs = [NSMutableSet new]; + _removedItemRefs = [NSMutableSet new]; + } + else + { + snapshot.addedItems = [_addedItemRefs copy]; + snapshot.updatedItems = [_updatedItemRefs copy]; + snapshot.removedItems = [_removedItemRefs copy]; + } + + snapshot.specialItems = [_source.specialItems copy]; + } + + return (snapshot); +} + +@end diff --git a/ownCloudSDK/Diagnostics/OCDiagnosticNode.h b/ownCloudSDK/Diagnostics/OCDiagnosticNode.h index 1450e3fa..7f85818b 100644 --- a/ownCloudSDK/Diagnostics/OCDiagnosticNode.h +++ b/ownCloudSDK/Diagnostics/OCDiagnosticNode.h @@ -27,7 +27,7 @@ typedef NS_ENUM(NSUInteger, OCDiagnosticNodeType) OCDiagnosticNodeTypeInfo, OCDiagnosticNodeTypeAction, OCDiagnosticNodeTypeGroup -}; +} __attribute__((enum_extensibility(closed))); typedef void(^OCDiagnosticNodeAction)(OCDiagnosticContext * _Nullable context); diff --git a/ownCloudSDK/Drive/OCDrive.h b/ownCloudSDK/Drive/OCDrive.h new file mode 100644 index 00000000..0c15953b --- /dev/null +++ b/ownCloudSDK/Drive/OCDrive.h @@ -0,0 +1,99 @@ +// +// OCDrive.h +// ownCloudSDK +// +// Created by Felix Schwarz on 31.01.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 "OCTypes.h" +#import "OCQuota.h" +#import "OCDataTypes.h" + +@class GADrive; +@class OCLocation; + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString *OCDriveType NS_TYPED_ENUM; +typedef NSString* OCDriveAlias; + +typedef NSString *OCDriveSpecialType NS_TYPED_ENUM; + +typedef NS_ENUM(NSInteger, OCDriveDetachedState) +{ + OCDriveDetachedStateNone, + + OCDriveDetachedStateNew, //!< Initial state when a drive has been detected as detached, before further examination + OCDriveDetachedStateHasUserChanges, //!< The detached drive has user changes and should be retained (not implemented yet) + OCDriveDetachedStateRetain, //!< The detached drive should be retained (not implemented yet) + OCDriveDetachedStateItemsRemoved, //!< The detached drive's items have been marked as removed in the database and both database and files will be disposed of through the vacuum item policy + OCDriveDetachedStateDisposable //!< The detached drive and its items on disk and in the database can be disposed of (not implemented yet) +}; + +@interface OCDrive : NSObject + +@property(strong) OCDriveID identifier; +@property(strong) OCDriveType type; + +@property(readonly,nonatomic,nullable) OCDriveSpecialType specialType; //!< Convenience accessor to determine if a drive is the personal or shares jail drive. + +@property(readonly,nonatomic) BOOL isDeactivated; + +@property(strong,nullable,nonatomic) NSString *name; +@property(strong,nullable) NSString *desc; + +@property(strong,nullable) NSURL *davRootURL; + +@property(strong,nullable) GAQuota *quota; + +@property(strong,nullable) GADrive *gaDrive; + +@property(assign) OCSeed seed; + +@property(strong,nonatomic,readonly) OCLocation *rootLocation; +@property(strong,nonatomic,readonly) OCFileETag rootETag; + +#pragma mark - Detached management +@property(readonly,nonatomic) BOOL isDetached; +@property(assign) OCDriveDetachedState detachedState; +@property(strong,nullable) NSDate *detachedSinceDate; + +#pragma mark - Instantiation ++ (instancetype)driveFromGADrive:(GADrive *)drive; //!< oCIS drive, initialized from a GADrive instance + +#pragma mark - Comparison +- (BOOL)isSubstantiallyDifferentFrom:(OCDrive *)drive; + +@end + +extern OCDriveType OCDriveTypePersonal; //!< A users personal space +extern OCDriveType OCDriveTypeVirtual; //!< Virtual space containing all items shared with the user +extern OCDriveType OCDriveTypeProject; //!< Regular spaces +extern OCDriveType OCDriveTypeMountpoint; //!< Accepted shared items +extern OCDriveType OCDriveTypeShare; + +extern OCDriveSpecialType OCDriveSpecialTypePersonal; //!< The user's personal space +extern OCDriveSpecialType OCDriveSpecialTypeShares; //!< The Shares Jail space +extern OCDriveSpecialType OCDriveSpecialTypeSpace; //!< Regular project spaces + +extern OCDriveID OCDriveIDSharesJail; //!< The static UUID of the Shares Jail + +#define OCDriveIDNil ((OCDriveID)NSNull.null) +#define OCDriveIDWrap(driveID) ((OCDriveID)((driveID == nil) ? OCDriveIDNil : driveID)) +#define OCDriveIDUnwrap(driveID) ((OCDriveID)(((driveID!=nil) && [driveID isKindOfClass:NSNull.class]) ? nil : driveID)) +#define OCDriveIDIsIdentical(driveID1,driveID2) ((OCDriveIDUnwrap(driveID1)==OCDriveIDUnwrap(driveID2)) || [driveID1 isEqual:driveID2]) + +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/Drive/OCDrive.m b/ownCloudSDK/Drive/OCDrive.m new file mode 100644 index 00000000..1bec23cd --- /dev/null +++ b/ownCloudSDK/Drive/OCDrive.m @@ -0,0 +1,300 @@ +// +// OCDrive.m +// ownCloudSDK +// +// Created by Felix Schwarz on 31.01.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 "OCDrive.h" +#import "GADrive.h" +#import "GADriveItem.h" +#import "GADeleted.h" +#import "OCMacros.h" +#import "OCLocation.h" +#import "OCCore.h" +#import "NSError+OCError.h" +#import "NSProgress+OCExtensions.h" + +#import "OCDataConverter.h" +#import "OCDataRenderer.h" +#import "OCDataItemPresentable.h" +#import "OCResourceManager.h" + +#import "OCResourceRequestDriveItem.h" + +@implementation OCDrive + ++ (instancetype)driveFromGADrive:(GADrive *)gDrive +{ + OCDrive *drive = nil; + + if (gDrive != nil) + { + drive = [OCDrive new]; + + drive.identifier = gDrive.identifier; + drive.type = gDrive.driveType; + + drive.name = gDrive.name; + drive.desc = gDrive.desc; + + drive.davRootURL = gDrive.root.webDavUrl; + + drive.quota = gDrive.quota; + + drive.gaDrive = gDrive; + } + + return (drive); +} + +#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) + { + GADriveItem *imageDriveItem = [inDrive.gaDrive specialDriveItemFor:GASpecialFolderNameImage]; + GADriveItem *readmeDriveItem = [inDrive.gaDrive specialDriveItemFor:GASpecialFolderNameReadme]; + + presentable = [[OCDataItemPresentable alloc] initWithItem:inDrive]; + presentable.title = inDrive.name; + presentable.subtitle = (inDrive.desc.length > 0) ? inDrive.desc : nil; // inDrive.type; + + 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:@[ + driveToPresentableConverter + ]]; +} + +#pragma mark - Comparison +- (BOOL)isSubstantiallyDifferentFrom:(OCDrive *)drive +{ + return (OCNANotEqual(drive.identifier, _identifier) || + OCNANotEqual(drive.type, _type) || + OCNANotEqual(drive.name, _name) || + OCNANotEqual(drive.desc, _desc) || + OCNANotEqual(drive.davRootURL, _davRootURL) || + OCNANotEqual([drive.gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag) || + OCNANotEqual([drive.gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag) || + OCNANotEqual(drive.detachedSinceDate, _detachedSinceDate) || + (drive.detachedState != _detachedState) || + (drive.isDeactivated != self.isDeactivated) || + OCNANotEqual(drive.rootETag, self.rootETag)); +} + +#pragma mark - Special types +- (OCDriveSpecialType)specialType +{ + // Personal space, identified by type + if ([self.type isEqual:OCDriveTypePersonal]) + { + return (OCDriveSpecialTypePersonal); + } + + // Shares space, identified by UUID + if ([self.identifier isEqual:OCDriveIDSharesJail]) + { + return (OCDriveSpecialTypeShares); + } + + // Project space, identified by type + if ([self.type isEqual:OCDriveTypeProject]) + { + return (OCDriveSpecialTypeSpace); + } + + return (nil); +} + +#pragma mark - Overrides +- (NSString *)name +{ + OCDriveSpecialType specialType = self.specialType; + + if (specialType == OCDriveSpecialTypePersonal) + { + return (OCLocalized(@"Personal")); + } + + if (specialType == OCDriveSpecialTypeShares) + { + return (OCLocalized(@"Shared with me")); + } + + return (_name); +} + +#pragma mark - Utility accessors +- (OCLocation *)rootLocation +{ + return ([[OCLocation alloc] initWithDriveID:_identifier path:@"/"]); +} + +- (OCFileETag)rootETag +{ + OCFileETag rootETag = _gaDrive.root.eTag; + + if (rootETag == nil) + { + rootETag = _gaDrive.eTag; + } + + return (rootETag); +} + +- (BOOL)isDeactivated +{ + return ([_gaDrive.root.deleted.state isEqual:GADeletedStateTrashed]); +} + +#pragma mark - Secure coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [self init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _type = [decoder decodeObjectOfClass:NSString.class forKey:@"type"]; + + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + + _davRootURL = [decoder decodeObjectOfClass:NSURL.class forKey:@"davURL"]; + + _quota = [decoder decodeObjectOfClass:GAQuota.class forKey:@"quota"]; + + _gaDrive = [decoder decodeObjectOfClass:GADrive.class forKey:@"gaDrive"]; + + _detachedState = [decoder decodeIntegerForKey:@"detachedState"]; + _detachedSinceDate = [decoder decodeObjectOfClass:NSDate.class forKey:@"detachedSinceDate"]; + } + + return (self); +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_type forKey:@"type"]; + + [coder encodeObject:_name forKey:@"name"]; + [coder encodeObject:_desc forKey:@"desc"]; + + [coder encodeObject:_davRootURL forKey:@"davURL"]; + + [coder encodeObject:_quota forKey:@"quota"]; + + [coder encodeObject:_gaDrive forKey:@"gaDrive"]; + + [coder encodeInteger:_detachedState forKey:@"detachedState"]; + [coder encodeObject:_detachedSinceDate forKey:@"detachedSinceDate"]; +} + +#pragma mark - OCDataItem / OCDataItemVersion compliance +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeDrive); +} + +- (OCDataItemReference)dataItemReference +{ + return (_identifier); +} + +- (OCDataItemVersion)dataItemVersion +{ + return ([NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@:%@:%@:%ld:%@:%ld", _identifier, _type, _name, _desc, _davRootURL, _gaDrive.eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag, [_gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag, (long)_detachedState, _detachedSinceDate, (long)self.isDeactivated]); +} + +#pragma mark - Comparison +- (NSUInteger)hash +{ + return (_identifier.hash ^ _gaDrive.eTag.hash ^ _name.hash ^ _desc.hash ^ _davRootURL.hash); +} + +- (BOOL)isEqual:(id)object +{ + if ([object isKindOfClass:OCDrive.class]) + { + return ([self isSubstantiallyDifferentFrom:object]); + } + + return (NO); +} + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@, isDetached: %d%@%@>", NSStringFromClass(self.class), self, + OCExpandVar(identifier), + OCExpandVar(type), + OCExpandVar(name), + [NSString stringWithFormat:@", rootETag: %@", self.rootETag], + self.isDetached, + OCExpandVar(quota), + OCExpandVar(davRootURL) + ]); +} + +@end + +OCDriveType OCDriveTypePersonal = @"personal"; +OCDriveType OCDriveTypeVirtual = @"virtual"; +OCDriveType OCDriveTypeProject = @"project"; +OCDriveType OCDriveTypeMountpoint = @"mountpoint"; +OCDriveType OCDriveTypeShare = @"share"; + +OCDriveSpecialType OCDriveSpecialTypePersonal = @"personal"; +OCDriveSpecialType OCDriveSpecialTypeShares = @"shares"; +OCDriveSpecialType OCDriveSpecialTypeSpace = @"space"; + +OCDriveID OCDriveIDSharesJail = @"a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668"; diff --git a/ownCloudSDK/Drive/OCQuota.h b/ownCloudSDK/Drive/OCQuota.h new file mode 100644 index 00000000..d7b429fb --- /dev/null +++ b/ownCloudSDK/Drive/OCQuota.h @@ -0,0 +1,28 @@ +// +// OCQuota.h +// ownCloudSDK +// +// Created by Felix Schwarz on 31.01.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 "GAQuota.h" + +NS_ASSUME_NONNULL_BEGIN + +extern OCQuotaState OCQuotaStateNormal; +extern OCQuotaState OCQuotaStateNearing; +extern OCQuotaState OCQuotaStateCritical; +extern OCQuotaState OCQuotaStateExceeded; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Drive/OCQuota.m b/ownCloudSDK/Drive/OCQuota.m new file mode 100644 index 00000000..5069d79a --- /dev/null +++ b/ownCloudSDK/Drive/OCQuota.m @@ -0,0 +1,24 @@ +// +// OCQuota.m +// ownCloudSDK +// +// Created by Felix Schwarz on 31.01.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 "OCQuota.h" + +OCQuotaState OCQuotaStateNormal = @"normal"; +OCQuotaState OCQuotaStateNearing = @"nearing"; +OCQuotaState OCQuotaStateCritical = @"critical"; +OCQuotaState OCQuotaStateExceeded = @"exceeded"; diff --git a/ownCloudSDK/Errors/NSError+OCError.h b/ownCloudSDK/Errors/NSError+OCError.h index 3794d0e6..d646de14 100644 --- a/ownCloudSDK/Errors/NSError+OCError.h +++ b/ownCloudSDK/Errors/NSError+OCError.h @@ -118,7 +118,24 @@ typedef NS_ENUM(NSUInteger, OCError) OCErrorWebFingerLacksServerInstanceRelation, //!< Web finger response lacks server instance relation. OCErrorUnknownUser, //!< Unknown user - OCErrorRequestTimeout //!< Request timed out + OCErrorRequestTimeout, //!< Request timed out + + OCErrorResourceDoesNotExist, //!< Resource does not exist + + OCErrorInvalidType, //!< Invalid type + OCErrorRequiredValueMissing, //!< Required value missing + + OCErrorGraphError, //!< Generic graph error + + OCErrorDataItemTypeUnavailable, //!< Object does not return DataItemType. + OCErrorDataConverterUnavailable,//!< No data converter available for conversion. + + OCErrorMissingDriveID, //!< Missing Drive ID. + + OCErrorResourceNotFound, //!< Resource not found. + OCErrorInvalidParameter, //!< Invalid parameter. + + OCErrorItemProcessing //!< Item is currently processing. }; @class OCIssue; diff --git a/ownCloudSDK/Errors/NSError+OCError.m b/ownCloudSDK/Errors/NSError+OCError.m index 5a1b37f8..7638e02d 100644 --- a/ownCloudSDK/Errors/NSError+OCError.m +++ b/ownCloudSDK/Errors/NSError+OCError.m @@ -345,6 +345,14 @@ + (id)provideUserInfoValueForOCError:(NSError *)error userInfoKey:(NSErrorUserIn unlocalizedString = @"Lock invalidated."; break; + case OCErrorRequestTimeout: + unlocalizedString = @"Request timed out"; + break; + + case OCErrorResourceDoesNotExist: + unlocalizedString = @"Resource does not exist."; + break; + case OCErrorWebFingerLacksServerInstanceRelation: unlocalizedString = @"Web finger response lacks server instance relation."; break; @@ -353,8 +361,40 @@ + (id)provideUserInfoValueForOCError:(NSError *)error userInfoKey:(NSErrorUserIn unlocalizedString = @"Unknown user"; break; - case OCErrorRequestTimeout: - unlocalizedString = @"Request timed out"; + case OCErrorInvalidType: + unlocalizedString = @"Invalid type."; + break; + + case OCErrorRequiredValueMissing: + unlocalizedString = @"Required value missing."; + break; + + case OCErrorGraphError: + unlocalizedString = @"Generic graph error."; + break; + + case OCErrorDataItemTypeUnavailable: + unlocalizedString = @"Object does not return DataItemType."; + break; + + case OCErrorDataConverterUnavailable: + unlocalizedString = @"No data converter available for conversion."; + break; + + case OCErrorMissingDriveID: + unlocalizedString = @"Missing Drive ID."; + break; + + case OCErrorResourceNotFound: + unlocalizedString = @"Resource not found."; + break; + + case OCErrorInvalidParameter: + unlocalizedString = @"Invalid parameter."; + break; + + case OCErrorItemProcessing: + unlocalizedString = @"Item is currently processing."; break; } } diff --git a/ownCloudSDK/Events/OCEvent.h b/ownCloudSDK/Events/OCEvent.h index bad310b8..5c3cb645 100644 --- a/ownCloudSDK/Events/OCEvent.h +++ b/ownCloudSDK/Events/OCEvent.h @@ -59,7 +59,7 @@ typedef NS_ENUM(NSUInteger, OCEventType) // Wakeup OCEventTypeWakeupSyncRecord -}; +} __attribute__((enum_extensibility(closed))); @class OCEvent; @class OCEventTarget; @@ -84,6 +84,7 @@ typedef NSString* OCEventUUID; NSDictionary> *_userInfo; NSDictionary *_ephermalUserInfo; + OCDriveID _driveID; OCPath _path; NSInteger _depth; @@ -102,8 +103,9 @@ typedef NSString* OCEventUUID; @property(nullable,readonly) NSDictionary> *userInfo; //!< The userInfo value of the OCEventTarget used to create this event. @property(nullable,readonly) NSDictionary *ephermalUserInfo; //!< The ephermalUserInfo value of the OCEventTarget used to create this event. -@property(nullable,strong) OCPath path; //!< Used by OCEventTypeRetrieveItemList. -@property(assign) NSInteger depth; //!< Used by OCEventTypeRetrieveItemList. +@property(nullable,strong) OCDriveID driveID; //!< Used by OCEventTypeRetrieveItemList. +@property(nullable,strong) OCPath path; //!< Used by OCEventTypeRetrieveItemList. +@property(assign) NSInteger depth; //!< Used by OCEventTypeRetrieveItemList. @property(nullable,strong) NSString *mimeType; @property(nullable,strong) OCFile *file; @@ -132,6 +134,7 @@ typedef NSString* OCEventUUID; extern OCEventUserInfoKey OCEventUserInfoKeyItem; extern OCEventUserInfoKey OCEventUserInfoKeyItemVersionIdentifier; extern OCEventUserInfoKey OCEventUserInfoKeySelector; +extern OCEventUserInfoKey OCEventUserInfoDriveID; NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Events/OCEvent.m b/ownCloudSDK/Events/OCEvent.m index 5df2f77a..1d75abd0 100644 --- a/ownCloudSDK/Events/OCEvent.m +++ b/ownCloudSDK/Events/OCEvent.m @@ -22,6 +22,8 @@ #import "OCBookmark.h" #import "OCCertificate.h" +#import "OCCertificateStore.h" +#import "OCCertificateStoreRecord.h" #import "OCChecksum.h" #import "OCClaim.h" #import "OCEventRecord.h" @@ -38,7 +40,7 @@ #import "OCItemThumbnail.h" #import "OCItemVersionIdentifier.h" #import "OCProcessSession.h" -#import "OCRecipient.h" +#import "OCIdentity.h" #import "OCQueryCondition.h" #import "OCShare.h" #import "OCSyncIssue.h" @@ -51,6 +53,7 @@ #import "OCDAVRawResponse.h" #import "OCMessage.h" #import "OCCoreUpdateScheduleRecord.h" +#import "OCDrive.h" @implementation OCEvent @@ -81,6 +84,8 @@ @implementation OCEvent // OC classes OCBookmark.class, OCCertificate.class, + OCCertificateStore.class, + OCCertificateStoreRecord.class, OCChecksum.class, OCClaim.class, OCEvent.class, @@ -101,6 +106,7 @@ @implementation OCEvent OCItemVersionIdentifier.class, OCProcessSession.class, OCProgress.class, + OCIdentity.class, OCRecipient.class, OCQueryCondition.class, OCShare.class, @@ -114,6 +120,8 @@ @implementation OCEvent OCMessage.class, OCMessageChoice.class, OCCoreUpdateScheduleRecord.class, + OCLocation.class, + OCDrive.class, // Foundation classes NSArray.class, @@ -259,7 +267,7 @@ - (NSData *)serializedData - (NSString *)description { - return ([NSString stringWithFormat:@"<%@: %p, eventType: %lu, path: %@, uuid: %@, userInfo: %@, result: %@, file: %@%@%@>", NSStringFromClass(self.class), self, (unsigned long)_eventType, _path, _uuid, _userInfo, _result, _file, ((_ephermalUserInfo[@"_processSession"]!=nil)?[NSString stringWithFormat:@", processSession=%@",_ephermalUserInfo[@"_processSession"]]:@""), ((_ephermalUserInfo[@"_doProcess"]!=nil)?[NSString stringWithFormat:@", doProcess=%@",_ephermalUserInfo[@"_doProcess"]]:@"")]); + return ([NSString stringWithFormat:@"<%@: %p, eventType: %lu, driveID: %@, path: %@, uuid: %@, userInfo: %@, result: %@, file: %@%@%@>", NSStringFromClass(self.class), self, (unsigned long)_eventType, _driveID, _path, _uuid, _userInfo, _result, _file, ((_ephermalUserInfo[@"_processSession"]!=nil)?[NSString stringWithFormat:@", processSession=%@",_ephermalUserInfo[@"_processSession"]]:@""), ((_ephermalUserInfo[@"_doProcess"]!=nil)?[NSString stringWithFormat:@", doProcess=%@",_ephermalUserInfo[@"_doProcess"]]:@"")]); } #pragma mark - Secure coding @@ -277,6 +285,7 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _eventType = [decoder decodeIntegerForKey:@"eventType"]; _userInfo = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"userInfo"]; + _driveID = [decoder decodeObjectOfClass:NSString.class forKey:@"driveID"]; _path = [decoder decodeObjectOfClass:NSString.class forKey:@"path"]; _depth = [decoder decodeIntegerForKey:@"depth"]; @@ -298,6 +307,7 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_userInfo forKey:@"userInfo"]; + [coder encodeObject:_driveID forKey:@"driveID"]; [coder encodeObject:_path forKey:@"path"]; [coder encodeInteger:_depth forKey:@"depth"]; @@ -313,3 +323,4 @@ - (void)encodeWithCoder:(NSCoder *)coder OCEventUserInfoKey OCEventUserInfoKeyItem = @"item"; OCEventUserInfoKey OCEventUserInfoKeyItemVersionIdentifier = @"itemVersionIdentifier"; OCEventUserInfoKey OCEventUserInfoKeySelector = @"selector"; +OCEventUserInfoKey OCEventUserInfoDriveID = @"drive-id"; diff --git a/ownCloudSDK/Extensions/OCExtensionContext.m b/ownCloudSDK/Extensions/OCExtensionContext.m index 1e6f3178..12b90a3f 100644 --- a/ownCloudSDK/Extensions/OCExtensionContext.m +++ b/ownCloudSDK/Extensions/OCExtensionContext.m @@ -22,7 +22,7 @@ @implementation OCExtensionContext + (instancetype)contextWithLocation:(OCExtensionLocation *)location requirements:(OCExtensionRequirements)requirements preferences:(OCExtensionRequirements)preferences { - OCExtensionContext *context = [OCExtensionContext new]; + OCExtensionContext *context = [self new]; context.location = location; context.requirements = requirements; diff --git a/ownCloudSDK/Extensions/OCExtensionManager.h b/ownCloudSDK/Extensions/OCExtensionManager.h index c1ba597a..73c85e62 100644 --- a/ownCloudSDK/Extensions/OCExtensionManager.h +++ b/ownCloudSDK/Extensions/OCExtensionManager.h @@ -21,10 +21,11 @@ #import "OCExtension.h" #import "OCExtensionContext.h" #import "OCExtensionMatch.h" +#import "OCClassSettings.h" NS_ASSUME_NONNULL_BEGIN -@interface OCExtensionManager : NSObject +@interface OCExtensionManager : NSObject { NSMutableArray *_extensions; NSArray *_cachedExtensions; @@ -42,4 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @end +extern OCClassSettingsIdentifier OCClassSettingsIdentifierExtensions; +extern OCClassSettingsKey OCClassSettingsKeyExtensionsDisallowed; + NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Extensions/OCExtensionManager.m b/ownCloudSDK/Extensions/OCExtensionManager.m index cdbf70e3..296ed7ca 100644 --- a/ownCloudSDK/Extensions/OCExtensionManager.m +++ b/ownCloudSDK/Extensions/OCExtensionManager.m @@ -59,8 +59,29 @@ - (instancetype)init } } +- (BOOL)isExtensionAllowed:(OCExtension *)extension +{ + NSArray *disallowedExtensionIdentifiers = [self classSettingForOCClassSettingsKey:OCClassSettingsKeyExtensionsDisallowed]; + + if ([disallowedExtensionIdentifiers isKindOfClass:NSArray.class] && (extension.identifier != nil)) + { + if ([disallowedExtensionIdentifiers containsObject:extension.identifier]) + { + return (NO); + } + } + + return (YES); +} + - (void)addExtension:(OCExtension *)extension { + if (![self isExtensionAllowed:extension]) + { + // Block disallowed extensions + return; + } + @synchronized(self) { _cachedExtensions = nil; @@ -90,6 +111,9 @@ - (void)removeExtension:(OCExtension *)extension { OCExtensionPriority priority; + // Block disallowed extensions + if (![self isExtensionAllowed:extension]) { continue; } + if ((priority = [extension matchesContext:context]) != OCExtensionPriorityNoMatch) { OCExtensionMatch *match; @@ -121,4 +145,59 @@ - (void)provideExtensionsForContext:(OCExtensionContext *)context completionHand }); } +#pragma mark - Class settings ++ (OCClassSettingsIdentifier)classSettingsIdentifier +{ + return (OCClassSettingsIdentifierExtensions); +} + ++ (nullable NSDictionary *)defaultSettingsForIdentifier:(nonnull OCClassSettingsIdentifier)identifier +{ + return (@{ + OCClassSettingsKeyExtensionsDisallowed : @[], + }); +} + ++ (BOOL)includeInLogSnapshot +{ + return (YES); +} + ++ (OCClassSettingsMetadataCollection)classSettingsMetadata +{ + NSMutableDictionary *possibleValues = [NSMutableDictionary new]; + + for (OCExtension *extension in OCExtensionManager.sharedExtensionManager.extensions) + { + if (extension.identifier != nil) + { + possibleValues[extension.identifier] = [NSString stringWithFormat:@"Extension with the identifier %@.", extension.identifier]; + } + } + + if (possibleValues.count == 0) + { + // Do not provide an empty possibleValues dictionary, because that results in a catch-22: + // - on launch, OCClassSettings would use OCClassSettingsMetadataKeyPossibleValues to validate the set MDM/branding parameters + // - the extensions, however, are only added later, so that the initial possible values are empty, which will effectively block the parameters usage/intention + + possibleValues = nil; + } + + OCClassSettingsMetadataCollection metadata = @{ + OCClassSettingsKeyExtensionsDisallowed : [NSDictionary dictionaryWithObjectsAndKeys: + OCClassSettingsMetadataTypeStringArray, OCClassSettingsMetadataKeyType, + @"List of all disallowed extensions. If provided, extensions not listed here are allowed.", OCClassSettingsMetadataKeyDescription, + @"Extensions", OCClassSettingsMetadataKeyCategory, + OCClassSettingsKeyStatusAdvanced, OCClassSettingsMetadataKeyStatus, + possibleValues, OCClassSettingsMetadataKeyPossibleValues, + nil] + }; + + return (metadata); +} + @end + +OCClassSettingsIdentifier OCClassSettingsIdentifierExtensions = @"extensions"; +OCClassSettingsKey OCClassSettingsKeyExtensionsDisallowed = @"disallowed"; diff --git a/ownCloudSDK/External/ISRunLoopThread/OCRunLoopThread.m b/ownCloudSDK/External/ISRunLoopThread/OCRunLoopThread.m index 1d14d803..8bec9d30 100755 --- a/ownCloudSDK/External/ISRunLoopThread/OCRunLoopThread.m +++ b/ownCloudSDK/External/ISRunLoopThread/OCRunLoopThread.m @@ -147,6 +147,7 @@ - (instancetype)initWithName:(NSString *)aName runLoop = nil; thread = [[NSThread alloc] initWithTarget:self selector:@selector(_threadMain:) object:nil]; + thread.qualityOfService = NSQualityOfServiceUserInitiated; [thread start]; diff --git a/ownCloudSDK/File Handling/Checksums/OCChecksumAlgorithmSHA1.m b/ownCloudSDK/File Handling/Checksums/OCChecksumAlgorithmSHA1.m index f207161a..1367472e 100644 --- a/ownCloudSDK/File Handling/Checksums/OCChecksumAlgorithmSHA1.m +++ b/ownCloudSDK/File Handling/Checksums/OCChecksumAlgorithmSHA1.m @@ -37,7 +37,7 @@ - (OCChecksum *)computeChecksumForInputStream:(NSInputStream *)inputStream error size_t maxLength = 128 * 1024; // 128 KB void *readBuffer = NULL; - if ((readBuffer = malloc(maxLength)) != NULL) + if ((maxLength > 0) && ((readBuffer = calloc(1, maxLength)) != NULL)) { UInt8 digest[CC_SHA1_DIGEST_LENGTH]; CC_SHA1_CTX digestContext; diff --git a/ownCloudSDK/GraphAPI/GAGraph.h b/ownCloudSDK/GraphAPI/GAGraph.h new file mode 100644 index 00000000..1117c519 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GAGraph.h @@ -0,0 +1,25 @@ +// +// GAGraph.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 + +typedef NSDictionary* GAGraphData; +typedef NSString* GAGraphType; +typedef NSString* GAGraphIdentifier; + +#import "GAGraphData+Decoder.h" diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.h new file mode 100644 index 00000000..e6d92ac5 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.h @@ -0,0 +1,37 @@ +// +// GAAppRole.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAAppRole : NSObject + +// occgen: type properties +@property(strong, nullable) NSArray *allowedMemberTypes; //!< Specifies whether this app role can be assigned to users and groups (by setting to [''User'']), to other application''s (by setting to [''Application''], or both (by setting to [''User'', ''Application'']). App roles supporting assignment to other applications'' service principals are also known as application permissions. The ''Application'' value is only supported for app roles defined on application entities. +@property(strong, nullable) NSString *desc; //!< The description for the app role. This is displayed when the app role is being assigned and, if the app role functions as an application permission, during consent experiences. +@property(strong, nullable) NSString *displayName; //!< Display name for the permission that appears in the app role assignment and consent experiences. +@property(strong) NSString *identifier; //!< [string:uuid] Unique role identifier inside the appRoles collection. When creating a new app role, a new GUID identifier must be provided. | pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.m new file mode 100644 index 00000000..3a687c8e --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRole.m @@ -0,0 +1,75 @@ +// +// GAAppRole.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAAppRole.h" + +// occgen: type start +@implementation GAAppRole + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAAppRole *instance = [self new]; + + GA_SET(allowedMemberTypes, NSString, NSArray.class); + GA_MAP(desc, "description", NSString, Nil); + GA_SET(displayName, NSString, Nil); + GA_MAP_REQ(identifier, "id", NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _allowedMemberTypes = [decoder decodeObjectOfClasses:[NSSet setWithObjects: NSString.class, NSArray.class, nil] forKey:@"allowedMemberTypes"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_allowedMemberTypes forKey:@"allowedMemberTypes"]; + [coder encodeObject:_desc forKey:@"desc"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_identifier forKey:@"identifier"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@>", NSStringFromClass(self.class), self, ((_allowedMemberTypes!=nil) ? [NSString stringWithFormat:@", allowedMemberTypes: %@", _allowedMemberTypes] : @""), ((_desc!=nil) ? [NSString stringWithFormat:@", desc: %@", _desc] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.h new file mode 100644 index 00000000..2754e121 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.h @@ -0,0 +1,42 @@ +// +// GAAppRoleAssignment.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAAppRoleAssignment : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< The unique identifier for the object. 12345678-9abc-def0-1234-56789abcde. The value of the ID property is often, but not exclusively, in the form of a GUID. The value should be treated as an opaque identifier and not based in being a GUID. Null values are not allowed. Read-only. +@property(strong, nullable) NSDate *deletedDateTime; //!< [string:date-time] | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong) NSString *appRoleId; //!< [string:uuid] The identifier (id) for the app role which is assigned to the user. Required on create. | pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ +@property(strong, nullable) NSDate *createdDateTime; //!< [string:date-time] The time when the app role assignment was created. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *principalDisplayName; //!< The display name of the user, group, or service principal that was granted the app role assignment. Read-only. +@property(strong) NSString *principalId; //!< [string:uuid] The unique identifier (id) for the user, security group, or service principal being granted the app role. Security groups with dynamic memberships are supported. Required on create. | pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ +@property(strong, nullable) NSString *principalType; //!< The type of the assigned principal. This can either be User, Group, or ServicePrincipal. Read-only. +@property(strong, nullable) NSString *resourceDisplayName; //!< The display name of the resource app's service principal to which the assignment is made. +@property(strong) NSString *resourceId; //!< [string:uuid] The unique identifier (id) for the resource service principal for which the assignment is made. Required on create. | pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.m new file mode 100644 index 00000000..0ce55711 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAAppRoleAssignment.m @@ -0,0 +1,90 @@ +// +// GAAppRoleAssignment.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAAppRoleAssignment.h" + +// occgen: type start +@implementation GAAppRoleAssignment + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAAppRoleAssignment *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(deletedDateTime, NSDate, Nil); + GA_SET_REQ(appRoleId, NSString, Nil); + GA_SET(createdDateTime, NSDate, Nil); + GA_SET(principalDisplayName, NSString, Nil); + GA_SET_REQ(principalId, NSString, Nil); + GA_SET(principalType, NSString, Nil); + GA_SET(resourceDisplayName, NSString, Nil); + GA_SET_REQ(resourceId, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _deletedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"deletedDateTime"]; + _appRoleId = [decoder decodeObjectOfClass:NSString.class forKey:@"appRoleId"]; + _createdDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"createdDateTime"]; + _principalDisplayName = [decoder decodeObjectOfClass:NSString.class forKey:@"principalDisplayName"]; + _principalId = [decoder decodeObjectOfClass:NSString.class forKey:@"principalId"]; + _principalType = [decoder decodeObjectOfClass:NSString.class forKey:@"principalType"]; + _resourceDisplayName = [decoder decodeObjectOfClass:NSString.class forKey:@"resourceDisplayName"]; + _resourceId = [decoder decodeObjectOfClass:NSString.class forKey:@"resourceId"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_deletedDateTime forKey:@"deletedDateTime"]; + [coder encodeObject:_appRoleId forKey:@"appRoleId"]; + [coder encodeObject:_createdDateTime forKey:@"createdDateTime"]; + [coder encodeObject:_principalDisplayName forKey:@"principalDisplayName"]; + [coder encodeObject:_principalId forKey:@"principalId"]; + [coder encodeObject:_principalType forKey:@"principalType"]; + [coder encodeObject:_resourceDisplayName forKey:@"resourceDisplayName"]; + [coder encodeObject:_resourceId forKey:@"resourceId"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_deletedDateTime!=nil) ? [NSString stringWithFormat:@", deletedDateTime: %@", _deletedDateTime] : @""), ((_appRoleId!=nil) ? [NSString stringWithFormat:@", appRoleId: %@", _appRoleId] : @""), ((_createdDateTime!=nil) ? [NSString stringWithFormat:@", createdDateTime: %@", _createdDateTime] : @""), ((_principalDisplayName!=nil) ? [NSString stringWithFormat:@", principalDisplayName: %@", _principalDisplayName] : @""), ((_principalId!=nil) ? [NSString stringWithFormat:@", principalId: %@", _principalId] : @""), ((_principalType!=nil) ? [NSString stringWithFormat:@", principalType: %@", _principalType] : @""), ((_resourceDisplayName!=nil) ? [NSString stringWithFormat:@", resourceDisplayName: %@", _resourceDisplayName] : @""), ((_resourceId!=nil) ? [NSString stringWithFormat:@", resourceId: %@", _resourceId] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.h new file mode 100644 index 00000000..520bfb78 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.h @@ -0,0 +1,39 @@ +// +// GAApplication.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAAppRole; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAApplication : NSObject + +// occgen: type properties +@property(strong) NSString *identifier; //!< The unique identifier for the object. 12345678-9abc-def0-1234-56789abcde. The value of the ID property is often, but not exclusively, in the form of a GUID. The value should be treated as an opaque identifier and not based in being a GUID. Null values are not allowed. Read-only. +@property(strong, nullable) NSArray *appRoles; //!< The collection of roles defined for the application. With app role assignments, these roles can be assigned to users, groups, or service principals associated with other applications. Not nullable. +@property(strong, nullable) NSString *displayName; //!< The display name for the application. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.m new file mode 100644 index 00000000..49fda736 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAApplication.m @@ -0,0 +1,73 @@ +// +// GAApplication.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAApplication.h" +#import "GAAppRole.h" + +// occgen: type start +@implementation GAApplication + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAApplication *instance = [self new]; + + GA_MAP_REQ(identifier, "id", NSString, Nil); + GA_SET(appRoles, GAAppRole, NSArray.class); + GA_SET(displayName, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _appRoles = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAAppRole.class, NSArray.class, nil] forKey:@"appRoles"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_appRoles forKey:@"appRoles"]; + [coder encodeObject:_displayName forKey:@"displayName"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_appRoles!=nil) ? [NSString stringWithFormat:@", appRoles: %@", _appRoles] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.h new file mode 100644 index 00000000..095f7b15 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.h @@ -0,0 +1,46 @@ +// +// GADeleted.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +typedef NSString * GADeletedState; + +// occgen: type start {"locked":true} +NS_ASSUME_NONNULL_BEGIN +typedef NSString* GADeletedState NS_TYPED_ENUM; + +@interface GADeleted : NSObject + +// occgen: type properties { "customPropertyTypes" : { "state" : "GADeletedState" }} +@property(strong, nullable) GADeletedState state; //!< Represents the state of the deleted item. + +// occgen: type protected {"locked":true} + + +// occgen: type end {"locked":true} +@end + +extern GADeletedState GADeletedStateTrashed; + +NS_ASSUME_NONNULL_END + + + + + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.m new file mode 100644 index 00000000..5079ea7b --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADeleted.m @@ -0,0 +1,72 @@ +// +// GADeleted.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GADeleted.h" + +// occgen: type start +@implementation GADeleted + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GADeleted *instance = [self new]; + + GA_SET(state, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _state = [decoder decodeObjectOfClass:NSString.class forKey:@"state"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_state forKey:@"state"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@>", NSStringFromClass(self.class), self, ((_state!=nil) ? [NSString stringWithFormat:@", state: %@", _state] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + +// occgen: static {"locked":true} +GADeletedState GADeletedStateTrashed = @"trashed"; + + + + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.h new file mode 100644 index 00000000..ca68a896 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.h @@ -0,0 +1,35 @@ +// +// GADirectoryObject.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GADirectoryObject : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< The unique identifier for the object. 12345678-9abc-def0-1234-56789abcde. The value of the ID property is often, but not exclusively, in the form of a GUID. The value should be treated as an opaque identifier and not based in being a GUID. Null values are not allowed. Read-only. +@property(strong, nullable) NSDate *deletedDateTime; //!< [string:date-time] | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.m new file mode 100644 index 00000000..19649c90 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADirectoryObject.m @@ -0,0 +1,69 @@ +// +// GADirectoryObject.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GADirectoryObject.h" + +// occgen: type start +@implementation GADirectoryObject + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GADirectoryObject *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(deletedDateTime, NSDate, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _deletedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"deletedDateTime"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_deletedDateTime forKey:@"deletedDateTime"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_deletedDateTime!=nil) ? [NSString stringWithFormat:@", deletedDateTime: %@", _deletedDateTime] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.h new file mode 100644 index 00000000..60caebf5 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.h @@ -0,0 +1,61 @@ +// +// GADrive.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes { "locked" : true } +#import +#import "GAGraphObject.h" +#import "OCDrive.h" +#import "GASpecialFolder.h" + +// occgen: forward declarations { "locked" : true } +@class GAQuota; +@class GAUser; +@class GADriveItem; +@class GAIdentitySet; +@class GAItemReference; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GADrive : NSObject + +// occgen: type properties { "customPropertyTypes" : { "driveType" : "OCDriveType", "eTag" : "OCFileETag", "driveAlias" : "OCDriveAlias" }} +@property(strong, nullable) NSString *identifier; //!< The unique idenfier for this drive. +@property(strong, nullable) GAIdentitySet *createdBy; //!< Identity of the user, device, or application which created the item. Read-only. +@property(strong, nullable) NSDate *createdDateTime; //!< [string:date-time] Date and time of item creation. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *desc; //!< Provides a user-visible description of the item. Optional. +@property(strong, nullable) OCFileETag eTag; //!< ETag for the item. Read-only. +@property(strong, nullable) GAIdentitySet *lastModifiedBy; //!< Identity of the user, device, and application which last modified the item. Read-only. +@property(strong, nullable) NSDate *lastModifiedDateTime; //!< [string:date-time] Date and time the item was last modified. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *name; //!< The name of the item. Read-write. +@property(strong, nullable) GAItemReference *parentReference; //!< Parent information, if the item has a parent. Read-write. +@property(strong, nullable) NSURL *webUrl; //!< URL that displays the resource in the browser. Read-only. +@property(strong, nullable) GAUser *createdByUser; //!< Identity of the user who created the item. Read-only. +@property(strong, nullable) GAUser *lastModifiedByUser; //!< Identity of the user who last modified the item. Read-only. +@property(strong, nullable) OCDriveType driveType; //!< Describes the type of drive represented by this resource. Values are "personal" for users home spaces, "project", "virtual" or "share". Read-only. +@property(strong, nullable) OCDriveAlias driveAlias; //!< "The drive alias can be used in clients to make the urls user friendly. Example: 'personal/einstein'. This will be used to resolve to the correct driveID." +@property(strong, nullable) GAIdentitySet *owner; +@property(strong, nullable) GAQuota *quota; +@property(strong, nullable) NSArray *items; //!< All items contained in the drive. Read-only. Nullable. +@property(strong, nullable) GADriveItem *root; //!< Drive item describing the drive's root. Read-only. +@property(strong, nullable) NSArray *special; //!< A collection of special drive resources. + +// occgen: type protected {"locked":true} +- (nullable GADriveItem *)specialDriveItemFor:(GASpecialFolderName)name; + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.m new file mode 100644 index 00000000..ec607070 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADrive.m @@ -0,0 +1,159 @@ +// +// GADrive.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GADrive.h" +#import "GADriveItem.h" +#import "GAIdentitySet.h" +#import "GAItemReference.h" +#import "GAQuota.h" +#import "GAUser.h" + +// occgen: more includes +#import "GASpecialFolder.h" +#import "NSArray+OCFiltering.h" + +// occgen: type start +@implementation GADrive + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GADrive *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(createdBy, GAIdentitySet, Nil); + GA_SET(createdDateTime, NSDate, Nil); + GA_MAP(desc, "description", NSString, Nil); + GA_SET(eTag, NSString, Nil); + GA_SET(lastModifiedBy, GAIdentitySet, Nil); + GA_SET(lastModifiedDateTime, NSDate, Nil); + GA_SET(name, NSString, Nil); + GA_SET(parentReference, GAItemReference, Nil); + GA_SET(webUrl, NSURL, Nil); + GA_SET(createdByUser, GAUser, Nil); + GA_SET(lastModifiedByUser, GAUser, Nil); + GA_SET(driveType, NSString, Nil); + GA_SET(driveAlias, NSString, Nil); + GA_SET(owner, GAIdentitySet, Nil); + GA_SET(quota, GAQuota, Nil); + GA_SET(items, GADriveItem, NSArray.class); + GA_SET(root, GADriveItem, Nil); + GA_SET(special, GADriveItem, NSArray.class); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _createdBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"createdBy"]; + _createdDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"createdDateTime"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + _eTag = [decoder decodeObjectOfClass:NSString.class forKey:@"eTag"]; + _lastModifiedBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"lastModifiedBy"]; + _lastModifiedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastModifiedDateTime"]; + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + _parentReference = [decoder decodeObjectOfClass:GAItemReference.class forKey:@"parentReference"]; + _webUrl = [decoder decodeObjectOfClass:NSURL.class forKey:@"webUrl"]; + _createdByUser = [decoder decodeObjectOfClass:GAUser.class forKey:@"createdByUser"]; + _lastModifiedByUser = [decoder decodeObjectOfClass:GAUser.class forKey:@"lastModifiedByUser"]; + _driveType = [decoder decodeObjectOfClass:NSString.class forKey:@"driveType"]; + _driveAlias = [decoder decodeObjectOfClass:NSString.class forKey:@"driveAlias"]; + _owner = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"owner"]; + _quota = [decoder decodeObjectOfClass:GAQuota.class forKey:@"quota"]; + _items = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GADriveItem.class, NSArray.class, nil] forKey:@"items"]; + _root = [decoder decodeObjectOfClass:GADriveItem.class forKey:@"root"]; + _special = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GADriveItem.class, NSArray.class, nil] forKey:@"special"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_createdBy forKey:@"createdBy"]; + [coder encodeObject:_createdDateTime forKey:@"createdDateTime"]; + [coder encodeObject:_desc forKey:@"desc"]; + [coder encodeObject:_eTag forKey:@"eTag"]; + [coder encodeObject:_lastModifiedBy forKey:@"lastModifiedBy"]; + [coder encodeObject:_lastModifiedDateTime forKey:@"lastModifiedDateTime"]; + [coder encodeObject:_name forKey:@"name"]; + [coder encodeObject:_parentReference forKey:@"parentReference"]; + [coder encodeObject:_webUrl forKey:@"webUrl"]; + [coder encodeObject:_createdByUser forKey:@"createdByUser"]; + [coder encodeObject:_lastModifiedByUser forKey:@"lastModifiedByUser"]; + [coder encodeObject:_driveType forKey:@"driveType"]; + [coder encodeObject:_driveAlias forKey:@"driveAlias"]; + [coder encodeObject:_owner forKey:@"owner"]; + [coder encodeObject:_quota forKey:@"quota"]; + [coder encodeObject:_items forKey:@"items"]; + [coder encodeObject:_root forKey:@"root"]; + [coder encodeObject:_special forKey:@"special"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_createdBy!=nil) ? [NSString stringWithFormat:@", createdBy: %@", _createdBy] : @""), ((_createdDateTime!=nil) ? [NSString stringWithFormat:@", createdDateTime: %@", _createdDateTime] : @""), ((_desc!=nil) ? [NSString stringWithFormat:@", desc: %@", _desc] : @""), ((_eTag!=nil) ? [NSString stringWithFormat:@", eTag: %@", _eTag] : @""), ((_lastModifiedBy!=nil) ? [NSString stringWithFormat:@", lastModifiedBy: %@", _lastModifiedBy] : @""), ((_lastModifiedDateTime!=nil) ? [NSString stringWithFormat:@", lastModifiedDateTime: %@", _lastModifiedDateTime] : @""), ((_name!=nil) ? [NSString stringWithFormat:@", name: %@", _name] : @""), ((_parentReference!=nil) ? [NSString stringWithFormat:@", parentReference: %@", _parentReference] : @""), ((_webUrl!=nil) ? [NSString stringWithFormat:@", webUrl: %@", _webUrl] : @""), ((_createdByUser!=nil) ? [NSString stringWithFormat:@", createdByUser: %@", _createdByUser] : @""), ((_lastModifiedByUser!=nil) ? [NSString stringWithFormat:@", lastModifiedByUser: %@", _lastModifiedByUser] : @""), ((_driveType!=nil) ? [NSString stringWithFormat:@", driveType: %@", _driveType] : @""), ((_driveAlias!=nil) ? [NSString stringWithFormat:@", driveAlias: %@", _driveAlias] : @""), ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), ((_quota!=nil) ? [NSString stringWithFormat:@", quota: %@", _quota] : @""), ((_items!=nil) ? [NSString stringWithFormat:@", items: %@", _items] : @""), ((_root!=nil) ? [NSString stringWithFormat:@", root: %@", _root] : @""), ((_special!=nil) ? [NSString stringWithFormat:@", special: %@", _special] : @"")]); +} + +// occgen: type protected {"locked":true} +- (GADriveItem *)specialDriveItemFor:(GASpecialFolderName)name +{ + return ([_special firstObjectMatching:^BOOL(GADriveItem * _Nonnull driveItem) { + return ([driveItem.specialFolder.name isEqual:name]); + }]); +} + +// occgen: type end {"locked":true} +@end + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.h new file mode 100644 index 00000000..3f83c7fe --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.h @@ -0,0 +1,77 @@ +// +// GADriveItem.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes { "locked" : true } +#import +#import "GAGraphObject.h" +#import "OCDrive.h" + +// occgen: forward declarations { "locked" : true } +@class GADeleted; +@class GADriveItem; +@class GAFileSystemInfo; +@class GAFolder; +@class GAIdentitySet; +@class GAImage; +@class GAItemReference; +@class GAOpenGraphFile; +@class GAPermission; +@class GARoot; +@class GASpecialFolder; +@class GATrash; +@class GAUser; +@class GARemoteItem; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GADriveItem : NSObject + +// occgen: type properties { "customPropertyTypes" : { "eTag" : "OCFileETag" }} +@property(strong, nullable) NSString *identifier; //!< Read-only. +@property(strong, nullable) GAIdentitySet *createdBy; //!< Identity of the user, device, or application which created the item. Read-only. +@property(strong, nullable) NSDate *createdDateTime; //!< [string:date-time] Date and time of item creation. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *desc; //!< Provides a user-visible description of the item. Optional. +@property(strong, nullable) OCFileETag eTag; //!< ETag for the item. Read-only. +@property(strong, nullable) GAIdentitySet *lastModifiedBy; //!< Identity of the user, device, and application which last modified the item. Read-only. +@property(strong, nullable) NSDate *lastModifiedDateTime; //!< [string:date-time] Date and time the item was last modified. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *name; //!< The name of the item. Read-write. +@property(strong, nullable) GAItemReference *parentReference; //!< Parent information, if the item has a parent. Read-write. +@property(strong, nullable) NSURL *webUrl; //!< URL that displays the resource in the browser. Read-only. +@property(strong, nullable) GAUser *createdByUser; //!< Identity of the user who created the item. Read-only. +@property(strong, nullable) GAUser *lastModifiedByUser; //!< Identity of the user who last modified the item. Read-only. +@property(strong, nullable) NSString *content; //!< [string:base64url] The content stream, if the item represents a file. +@property(strong, nullable) NSString *cTag; //!< An eTag for the content of the item. This eTag is not changed if only the metadata is changed. Note This property is not returned if the item is a folder. Read-only. +@property(strong, nullable) GADeleted *deleted; +@property(strong, nullable) GAOpenGraphFile *file; +@property(strong, nullable) GAFileSystemInfo *fileSystemInfo; +@property(strong, nullable) GAFolder *folder; +@property(strong, nullable) GAImage *image; +@property(strong, nullable) GARoot *root; +@property(strong, nullable) GATrash *trash; +@property(strong, nullable) GASpecialFolder *specialFolder; +@property(strong, nullable) GARemoteItem *remoteItem; +@property(strong, nullable) NSNumber *size; //!< [integer:int64] Size of the item in bytes. Read-only. +@property(strong, nullable) NSURL *webDavUrl; //!< WebDAV compatible URL for the item. Read-only. +@property(strong, nullable) NSArray *children; //!< Collection containing Item objects for the immediate children of Item. Only items representing folders have children. Read-only. Nullable. +@property(strong, nullable) NSArray *permissions; //!< The set of permissions for the item. Read-only. Nullable. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.m new file mode 100644 index 00000000..02f797bf --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GADriveItem.m @@ -0,0 +1,158 @@ +// +// GADriveItem.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GADriveItem.h" +#import "GADeleted.h" +#import "GADriveItem.h" +#import "GAFileSystemInfo.h" +#import "GAFolder.h" +#import "GAIdentitySet.h" +#import "GAImage.h" +#import "GAItemReference.h" +#import "GAOpenGraphFile.h" +#import "GAPermission.h" +#import "GARemoteItem.h" +#import "GARoot.h" +#import "GASpecialFolder.h" +#import "GATrash.h" +#import "GAUser.h" + +// occgen: type start +@implementation GADriveItem + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GADriveItem *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(createdBy, GAIdentitySet, Nil); + GA_SET(createdDateTime, NSDate, Nil); + GA_MAP(desc, "description", NSString, Nil); + GA_SET(eTag, NSString, Nil); + GA_SET(lastModifiedBy, GAIdentitySet, Nil); + GA_SET(lastModifiedDateTime, NSDate, Nil); + GA_SET(name, NSString, Nil); + GA_SET(parentReference, GAItemReference, Nil); + GA_SET(webUrl, NSURL, Nil); + GA_SET(createdByUser, GAUser, Nil); + GA_SET(lastModifiedByUser, GAUser, Nil); + GA_SET(content, NSString, Nil); + GA_SET(cTag, NSString, Nil); + GA_SET(deleted, GADeleted, Nil); + GA_SET(file, GAOpenGraphFile, Nil); + GA_SET(fileSystemInfo, GAFileSystemInfo, Nil); + GA_SET(folder, GAFolder, Nil); + GA_SET(image, GAImage, Nil); + GA_SET(root, GARoot, Nil); + GA_SET(trash, GATrash, Nil); + GA_SET(specialFolder, GASpecialFolder, Nil); + GA_SET(remoteItem, GARemoteItem, Nil); + GA_SET(size, NSNumber, Nil); + GA_SET(webDavUrl, NSURL, Nil); + GA_SET(children, GADriveItem, NSArray.class); + GA_SET(permissions, GAPermission, NSArray.class); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _createdBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"createdBy"]; + _createdDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"createdDateTime"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + _eTag = [decoder decodeObjectOfClass:NSString.class forKey:@"eTag"]; + _lastModifiedBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"lastModifiedBy"]; + _lastModifiedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastModifiedDateTime"]; + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + _parentReference = [decoder decodeObjectOfClass:GAItemReference.class forKey:@"parentReference"]; + _webUrl = [decoder decodeObjectOfClass:NSURL.class forKey:@"webUrl"]; + _createdByUser = [decoder decodeObjectOfClass:GAUser.class forKey:@"createdByUser"]; + _lastModifiedByUser = [decoder decodeObjectOfClass:GAUser.class forKey:@"lastModifiedByUser"]; + _content = [decoder decodeObjectOfClass:NSString.class forKey:@"content"]; + _cTag = [decoder decodeObjectOfClass:NSString.class forKey:@"cTag"]; + _deleted = [decoder decodeObjectOfClass:GADeleted.class forKey:@"deleted"]; + _file = [decoder decodeObjectOfClass:GAOpenGraphFile.class forKey:@"file"]; + _fileSystemInfo = [decoder decodeObjectOfClass:GAFileSystemInfo.class forKey:@"fileSystemInfo"]; + _folder = [decoder decodeObjectOfClass:GAFolder.class forKey:@"folder"]; + _image = [decoder decodeObjectOfClass:GAImage.class forKey:@"image"]; + _root = [decoder decodeObjectOfClass:GARoot.class forKey:@"root"]; + _trash = [decoder decodeObjectOfClass:GATrash.class forKey:@"trash"]; + _specialFolder = [decoder decodeObjectOfClass:GASpecialFolder.class forKey:@"specialFolder"]; + _remoteItem = [decoder decodeObjectOfClass:GARemoteItem.class forKey:@"remoteItem"]; + _size = [decoder decodeObjectOfClass:NSNumber.class forKey:@"size"]; + _webDavUrl = [decoder decodeObjectOfClass:NSURL.class forKey:@"webDavUrl"]; + _children = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GADriveItem.class, NSArray.class, nil] forKey:@"children"]; + _permissions = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAPermission.class, NSArray.class, nil] forKey:@"permissions"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_createdBy forKey:@"createdBy"]; + [coder encodeObject:_createdDateTime forKey:@"createdDateTime"]; + [coder encodeObject:_desc forKey:@"desc"]; + [coder encodeObject:_eTag forKey:@"eTag"]; + [coder encodeObject:_lastModifiedBy forKey:@"lastModifiedBy"]; + [coder encodeObject:_lastModifiedDateTime forKey:@"lastModifiedDateTime"]; + [coder encodeObject:_name forKey:@"name"]; + [coder encodeObject:_parentReference forKey:@"parentReference"]; + [coder encodeObject:_webUrl forKey:@"webUrl"]; + [coder encodeObject:_createdByUser forKey:@"createdByUser"]; + [coder encodeObject:_lastModifiedByUser forKey:@"lastModifiedByUser"]; + [coder encodeObject:_content forKey:@"content"]; + [coder encodeObject:_cTag forKey:@"cTag"]; + [coder encodeObject:_deleted forKey:@"deleted"]; + [coder encodeObject:_file forKey:@"file"]; + [coder encodeObject:_fileSystemInfo forKey:@"fileSystemInfo"]; + [coder encodeObject:_folder forKey:@"folder"]; + [coder encodeObject:_image forKey:@"image"]; + [coder encodeObject:_root forKey:@"root"]; + [coder encodeObject:_trash forKey:@"trash"]; + [coder encodeObject:_specialFolder forKey:@"specialFolder"]; + [coder encodeObject:_remoteItem forKey:@"remoteItem"]; + [coder encodeObject:_size forKey:@"size"]; + [coder encodeObject:_webDavUrl forKey:@"webDavUrl"]; + [coder encodeObject:_children forKey:@"children"]; + [coder encodeObject:_permissions forKey:@"permissions"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_createdBy!=nil) ? [NSString stringWithFormat:@", createdBy: %@", _createdBy] : @""), ((_createdDateTime!=nil) ? [NSString stringWithFormat:@", createdDateTime: %@", _createdDateTime] : @""), ((_desc!=nil) ? [NSString stringWithFormat:@", desc: %@", _desc] : @""), ((_eTag!=nil) ? [NSString stringWithFormat:@", eTag: %@", _eTag] : @""), ((_lastModifiedBy!=nil) ? [NSString stringWithFormat:@", lastModifiedBy: %@", _lastModifiedBy] : @""), ((_lastModifiedDateTime!=nil) ? [NSString stringWithFormat:@", lastModifiedDateTime: %@", _lastModifiedDateTime] : @""), ((_name!=nil) ? [NSString stringWithFormat:@", name: %@", _name] : @""), ((_parentReference!=nil) ? [NSString stringWithFormat:@", parentReference: %@", _parentReference] : @""), ((_webUrl!=nil) ? [NSString stringWithFormat:@", webUrl: %@", _webUrl] : @""), ((_createdByUser!=nil) ? [NSString stringWithFormat:@", createdByUser: %@", _createdByUser] : @""), ((_lastModifiedByUser!=nil) ? [NSString stringWithFormat:@", lastModifiedByUser: %@", _lastModifiedByUser] : @""), ((_content!=nil) ? [NSString stringWithFormat:@", content: %@", _content] : @""), ((_cTag!=nil) ? [NSString stringWithFormat:@", cTag: %@", _cTag] : @""), ((_deleted!=nil) ? [NSString stringWithFormat:@", deleted: %@", _deleted] : @""), ((_file!=nil) ? [NSString stringWithFormat:@", file: %@", _file] : @""), ((_fileSystemInfo!=nil) ? [NSString stringWithFormat:@", fileSystemInfo: %@", _fileSystemInfo] : @""), ((_folder!=nil) ? [NSString stringWithFormat:@", folder: %@", _folder] : @""), ((_image!=nil) ? [NSString stringWithFormat:@", image: %@", _image] : @""), ((_root!=nil) ? [NSString stringWithFormat:@", root: %@", _root] : @""), ((_trash!=nil) ? [NSString stringWithFormat:@", trash: %@", _trash] : @""), ((_specialFolder!=nil) ? [NSString stringWithFormat:@", specialFolder: %@", _specialFolder] : @""), ((_remoteItem!=nil) ? [NSString stringWithFormat:@", remoteItem: %@", _remoteItem] : @""), ((_size!=nil) ? [NSString stringWithFormat:@", size: %@", _size] : @""), ((_webDavUrl!=nil) ? [NSString stringWithFormat:@", webDavUrl: %@", _webDavUrl] : @""), ((_children!=nil) ? [NSString stringWithFormat:@", children: %@", _children] : @""), ((_permissions!=nil) ? [NSString stringWithFormat:@", permissions: %@", _permissions] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.h new file mode 100644 index 00000000..cafd354a --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.h @@ -0,0 +1,45 @@ +// +// GAEducationClass.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAUser; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAEducationClass : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< Read-only. +@property(strong, nullable) NSString *desc; //!< An optional description for the group. Returned by default. Supports $filter (eq, ne, not, ge, le, startsWith) and $search. +@property(strong) NSString *displayName; //!< The display name for the group. This property is required when a group is created and cannot be cleared during updates. Returned by default. Supports $filter (eq, ne, not, ge, le, in, startsWith, and eq on null values), $search, and $orderBy. +@property(strong, nullable) NSArray *members; //!< Users and groups that are members of this group. HTTP Methods: GET (supported for all groups), Nullable. Supports $expand. +@property(strong, nullable) NSString *onPremisesDomainName; //!< Contains the on-premises domainFQDN, also called dnsDomainName synchronized from the on-premises directory. The property is only populated for customers who are synchronizing their on-premises directory to Azure Active Directory via Azure AD Connect. Read-only. Returned only on $select. +@property(strong, nullable) NSString *onPremisesSamAccountName; //!< Contains the on-premises SAM account name synchronized from the on-premises directory. Read-only. +@property(strong, nullable) NSArray *'members@odata.bind'; //!< A list of member references to the members to be added. Up to 20 members can be added with a single request +@property(strong) NSString *classification; //!< Classification of the group, i.e. "class" or "course" +@property(strong, nullable) NSString *externalId; //!< An external unique ID for the class + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.m new file mode 100644 index 00000000..fbaaf45a --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationClass.m @@ -0,0 +1,91 @@ +// +// GAEducationClass.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAEducationClass.h" +#import "GAUser.h" + +// occgen: type start +@implementation GAEducationClass + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAEducationClass *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_MAP(desc, "description", NSString, Nil); + GA_SET_REQ(displayName, NSString, Nil); + GA_SET(members, GAUser, NSArray.class); + GA_SET(onPremisesDomainName, NSString, Nil); + GA_SET(onPremisesSamAccountName, NSString, Nil); + GA_SET('members@odata.bind', NSString, NSArray.class); + GA_SET_REQ(classification, NSString, Nil); + GA_SET(externalId, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _members = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAUser.class, NSArray.class, nil] forKey:@"members"]; + _onPremisesDomainName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesDomainName"]; + _onPremisesSamAccountName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesSamAccountName"]; + _'members@odata.bind' = [decoder decodeObjectOfClasses:[NSSet setWithObjects: NSString.class, NSArray.class, nil] forKey:@"'members@odata.bind'"]; + _classification = [decoder decodeObjectOfClass:NSString.class forKey:@"classification"]; + _externalId = [decoder decodeObjectOfClass:NSString.class forKey:@"externalId"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_desc forKey:@"desc"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_members forKey:@"members"]; + [coder encodeObject:_onPremisesDomainName forKey:@"onPremisesDomainName"]; + [coder encodeObject:_onPremisesSamAccountName forKey:@"onPremisesSamAccountName"]; + [coder encodeObject:_'members@odata.bind' forKey:@"'members@odata.bind'"]; + [coder encodeObject:_classification forKey:@"classification"]; + [coder encodeObject:_externalId forKey:@"externalId"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_desc!=nil) ? [NSString stringWithFormat:@", desc: %@", _desc] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_members!=nil) ? [NSString stringWithFormat:@", members: %@", _members] : @""), ((_onPremisesDomainName!=nil) ? [NSString stringWithFormat:@", onPremisesDomainName: %@", _onPremisesDomainName] : @""), ((_onPremisesSamAccountName!=nil) ? [NSString stringWithFormat:@", onPremisesSamAccountName: %@", _onPremisesSamAccountName] : @""), ((_'members@odata.bind'!=nil) ? [NSString stringWithFormat:@", 'members@odata.bind': %@", _'members@odata.bind'] : @""), ((_classification!=nil) ? [NSString stringWithFormat:@", classification: %@", _classification] : @""), ((_externalId!=nil) ? [NSString stringWithFormat:@", externalId: %@", _externalId] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.h new file mode 100644 index 00000000..ac27c111 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.h @@ -0,0 +1,35 @@ +// +// GAEducationOrganization.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAEducationOrganization : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< The unique idenfier for an entity. Read-only. +@property(strong, nullable) NSString *displayName; //!< The organization name + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.m new file mode 100644 index 00000000..1c46a5d4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationOrganization.m @@ -0,0 +1,69 @@ +// +// GAEducationOrganization.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAEducationOrganization.h" + +// occgen: type start +@implementation GAEducationOrganization + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAEducationOrganization *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(displayName, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_displayName forKey:@"displayName"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.h new file mode 100644 index 00000000..0ac67e90 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.h @@ -0,0 +1,36 @@ +// +// GAEducationSchool.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAEducationSchool : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< The unique idenfier for an entity. Read-only. +@property(strong, nullable) NSString *displayName; //!< The organization name +@property(strong, nullable) NSString *schoolNumber; //!< School number + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.m new file mode 100644 index 00000000..bfc4a610 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationSchool.m @@ -0,0 +1,72 @@ +// +// GAEducationSchool.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAEducationSchool.h" + +// occgen: type start +@implementation GAEducationSchool + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAEducationSchool *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(displayName, NSString, Nil); + GA_SET(schoolNumber, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _schoolNumber = [decoder decodeObjectOfClass:NSString.class forKey:@"schoolNumber"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_schoolNumber forKey:@"schoolNumber"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_schoolNumber!=nil) ? [NSString stringWithFormat:@", schoolNumber: %@", _schoolNumber] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.h new file mode 100644 index 00000000..6438bbc8 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.h @@ -0,0 +1,52 @@ +// +// GAEducationUser.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GADrive; +@class GAGroup; +@class GAObjectIdentity; +@class GAPasswordProfile; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAEducationUser : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< Read-only. +@property(strong, nullable) NSNumber *accountEnabled; //!< [boolean] Set to "true" when the account is enabled. +@property(strong, nullable) NSString *displayName; //!< The name displayed in the address book for the user. This value is usually the combination of the user''s first name, middle initial, and last name. This property is required when a user is created and it cannot be cleared during updates. Returned by default. Supports $filter and $orderby. +@property(strong, nullable) NSArray *drives; //!< A collection of drives available for this user. Read-only. +@property(strong, nullable) GADrive *drive; //!< The personal drive of this user. Read-only. +@property(strong, nullable) NSArray *identities; //!< Identities associated with this account. +@property(strong, nullable) NSString *mail; //!< The SMTP address for the user, for example, ''jeff@contoso.onowncloud.com''. Returned by default. Supports $filter and endsWith. +@property(strong, nullable) NSArray *memberOf; //!< Groups that this user is a member of. HTTP Methods: GET (supported for all groups). Read-only. Nullable. Supports $expand. +@property(strong, nullable) NSString *onPremisesSamAccountName; //!< Contains the on-premises SAM account name synchronized from the on-premises directory. Read-only. +@property(strong, nullable) GAPasswordProfile *passwordProfile; +@property(strong, nullable) NSString *surname; //!< The user's surname (family name or last name). Returned by default. Supports $filter. +@property(strong, nullable) NSString *givenName; //!< The user's givenName. Returned by default. Supports $filter. +@property(strong, nullable) NSString *primaryRole; //!< The user`s default role. Such as "student" or "teacher" + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.m new file mode 100644 index 00000000..f209060e --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEducationUser.m @@ -0,0 +1,106 @@ +// +// GAEducationUser.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAEducationUser.h" +#import "GADrive.h" +#import "GAGroup.h" +#import "GAObjectIdentity.h" +#import "GAPasswordProfile.h" + +// occgen: type start +@implementation GAEducationUser + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAEducationUser *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(accountEnabled, NSNumber, Nil); + GA_SET(displayName, NSString, Nil); + GA_SET(drives, GADrive, NSArray.class); + GA_SET(drive, GADrive, Nil); + GA_SET(identities, GAObjectIdentity, NSArray.class); + GA_SET(mail, NSString, Nil); + GA_SET(memberOf, GAGroup, NSArray.class); + GA_SET(onPremisesSamAccountName, NSString, Nil); + GA_SET(passwordProfile, GAPasswordProfile, Nil); + GA_SET(surname, NSString, Nil); + GA_SET(givenName, NSString, Nil); + GA_SET(primaryRole, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _accountEnabled = [decoder decodeObjectOfClass:NSNumber.class forKey:@"accountEnabled"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _drives = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GADrive.class, NSArray.class, nil] forKey:@"drives"]; + _drive = [decoder decodeObjectOfClass:GADrive.class forKey:@"drive"]; + _identities = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAObjectIdentity.class, NSArray.class, nil] forKey:@"identities"]; + _mail = [decoder decodeObjectOfClass:NSString.class forKey:@"mail"]; + _memberOf = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAGroup.class, NSArray.class, nil] forKey:@"memberOf"]; + _onPremisesSamAccountName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesSamAccountName"]; + _passwordProfile = [decoder decodeObjectOfClass:GAPasswordProfile.class forKey:@"passwordProfile"]; + _surname = [decoder decodeObjectOfClass:NSString.class forKey:@"surname"]; + _givenName = [decoder decodeObjectOfClass:NSString.class forKey:@"givenName"]; + _primaryRole = [decoder decodeObjectOfClass:NSString.class forKey:@"primaryRole"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_accountEnabled forKey:@"accountEnabled"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_drives forKey:@"drives"]; + [coder encodeObject:_drive forKey:@"drive"]; + [coder encodeObject:_identities forKey:@"identities"]; + [coder encodeObject:_mail forKey:@"mail"]; + [coder encodeObject:_memberOf forKey:@"memberOf"]; + [coder encodeObject:_onPremisesSamAccountName forKey:@"onPremisesSamAccountName"]; + [coder encodeObject:_passwordProfile forKey:@"passwordProfile"]; + [coder encodeObject:_surname forKey:@"surname"]; + [coder encodeObject:_givenName forKey:@"givenName"]; + [coder encodeObject:_primaryRole forKey:@"primaryRole"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_accountEnabled!=nil) ? [NSString stringWithFormat:@", accountEnabled: %@", _accountEnabled] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_drives!=nil) ? [NSString stringWithFormat:@", drives: %@", _drives] : @""), ((_drive!=nil) ? [NSString stringWithFormat:@", drive: %@", _drive] : @""), ((_identities!=nil) ? [NSString stringWithFormat:@", identities: %@", _identities] : @""), ((_mail!=nil) ? [NSString stringWithFormat:@", mail: %@", _mail] : @""), ((_memberOf!=nil) ? [NSString stringWithFormat:@", memberOf: %@", _memberOf] : @""), ((_onPremisesSamAccountName!=nil) ? [NSString stringWithFormat:@", onPremisesSamAccountName: %@", _onPremisesSamAccountName] : @""), ((_passwordProfile!=nil) ? [NSString stringWithFormat:@", passwordProfile: %@", _passwordProfile] : @""), ((_surname!=nil) ? [NSString stringWithFormat:@", surname: %@", _surname] : @""), ((_givenName!=nil) ? [NSString stringWithFormat:@", givenName: %@", _givenName] : @""), ((_primaryRole!=nil) ? [NSString stringWithFormat:@", primaryRole: %@", _primaryRole] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.h new file mode 100644 index 00000000..8ed8e3b1 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.h @@ -0,0 +1,34 @@ +// +// GAEntity.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAEntity : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< The unique idenfier for an entity. Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.m new file mode 100644 index 00000000..2ff2edba --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAEntity.m @@ -0,0 +1,66 @@ +// +// GAEntity.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAEntity.h" + +// occgen: type start +@implementation GAEntity + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAEntity *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.h new file mode 100644 index 00000000..4ed5f3f4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.h @@ -0,0 +1,36 @@ +// +// GAFileSystemInfo.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAFileSystemInfo : NSObject + +// occgen: type properties +@property(strong, nullable) NSDate *createdDateTime; //!< [string:date-time] The UTC date and time the file was created on a client. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSDate *lastAccessedDateTime; //!< [string:date-time] The UTC date and time the file was last accessed. Available for the recent file list only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSDate *lastModifiedDateTime; //!< [string:date-time] The UTC date and time the file was last modified on a client. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.m new file mode 100644 index 00000000..5285a57f --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFileSystemInfo.m @@ -0,0 +1,72 @@ +// +// GAFileSystemInfo.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAFileSystemInfo.h" + +// occgen: type start +@implementation GAFileSystemInfo + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAFileSystemInfo *instance = [self new]; + + GA_SET(createdDateTime, NSDate, Nil); + GA_SET(lastAccessedDateTime, NSDate, Nil); + GA_SET(lastModifiedDateTime, NSDate, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _createdDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"createdDateTime"]; + _lastAccessedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastAccessedDateTime"]; + _lastModifiedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastModifiedDateTime"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_createdDateTime forKey:@"createdDateTime"]; + [coder encodeObject:_lastAccessedDateTime forKey:@"lastAccessedDateTime"]; + [coder encodeObject:_lastModifiedDateTime forKey:@"lastModifiedDateTime"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_createdDateTime!=nil) ? [NSString stringWithFormat:@", createdDateTime: %@", _createdDateTime] : @""), ((_lastAccessedDateTime!=nil) ? [NSString stringWithFormat:@", lastAccessedDateTime: %@", _lastAccessedDateTime] : @""), ((_lastModifiedDateTime!=nil) ? [NSString stringWithFormat:@", lastModifiedDateTime: %@", _lastModifiedDateTime] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.h new file mode 100644 index 00000000..a4bafdcd --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.h @@ -0,0 +1,38 @@ +// +// GAFolder.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAFolderView; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAFolder : NSObject + +// occgen: type properties +@property(strong, nullable) NSNumber *childCount; //!< [integer:int32] Number of children contained immediately within this container. +@property(strong, nullable) GAFolderView *view; + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.m new file mode 100644 index 00000000..e5565571 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolder.m @@ -0,0 +1,70 @@ +// +// GAFolder.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAFolder.h" +#import "GAFolderView.h" + +// occgen: type start +@implementation GAFolder + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAFolder *instance = [self new]; + + GA_SET(childCount, NSNumber, Nil); + GA_SET(view, GAFolderView, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _childCount = [decoder decodeObjectOfClass:NSNumber.class forKey:@"childCount"]; + _view = [decoder decodeObjectOfClass:GAFolderView.class forKey:@"view"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_childCount forKey:@"childCount"]; + [coder encodeObject:_view forKey:@"view"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_childCount!=nil) ? [NSString stringWithFormat:@", childCount: %@", _childCount] : @""), ((_view!=nil) ? [NSString stringWithFormat:@", view: %@", _view] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.h new file mode 100644 index 00000000..dfca463a --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.h @@ -0,0 +1,36 @@ +// +// GAFolderView.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAFolderView : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *sortBy; //!< The method by which the folder should be sorted. +@property(strong, nullable) NSString *sortOrder; //!< If true, indicates that items should be sorted in descending order. Otherwise, items should be sorted ascending. +@property(strong, nullable) NSString *viewType; //!< The type of view that should be used to represent the folder. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.m new file mode 100644 index 00000000..00291d4b --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAFolderView.m @@ -0,0 +1,72 @@ +// +// GAFolderView.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAFolderView.h" + +// occgen: type start +@implementation GAFolderView + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAFolderView *instance = [self new]; + + GA_SET(sortBy, NSString, Nil); + GA_SET(sortOrder, NSString, Nil); + GA_SET(viewType, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _sortBy = [decoder decodeObjectOfClass:NSString.class forKey:@"sortBy"]; + _sortOrder = [decoder decodeObjectOfClass:NSString.class forKey:@"sortOrder"]; + _viewType = [decoder decodeObjectOfClass:NSString.class forKey:@"viewType"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_sortBy forKey:@"sortBy"]; + [coder encodeObject:_sortOrder forKey:@"sortOrder"]; + [coder encodeObject:_viewType forKey:@"viewType"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_sortBy!=nil) ? [NSString stringWithFormat:@", sortBy: %@", _sortBy] : @""), ((_sortOrder!=nil) ? [NSString stringWithFormat:@", sortOrder: %@", _sortOrder] : @""), ((_viewType!=nil) ? [NSString stringWithFormat:@", viewType: %@", _viewType] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.h new file mode 100644 index 00000000..af462af4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.h @@ -0,0 +1,42 @@ +// +// GAGroup.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAUser; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAGroup : NSObject + +// occgen: type properties {"locked":true} +@property(strong, nullable) NSString *identifier; //!< Read-only. +@property(strong, nullable) NSString *desc; //!< An optional description for the group. Returned by default. Supports $filter (eq, ne, not, ge, le, startsWith) and $search. +@property(strong, nullable) NSString *displayName; //!< The display name for the group. This property is required when a group is created and cannot be cleared during updates. Returned by default. Supports $filter (eq, ne, not, ge, le, in, startsWith, and eq on null values), $search, and $orderBy. +@property(strong, nullable) NSArray *members; //!< Users and groups that are members of this group. HTTP Methods: GET (supported for all groups), Nullable. Supports $expand. +@property(strong, nullable) NSString *onPremisesDomainName; //!< Contains the on-premises domainFQDN, also called dnsDomainName synchronized from the on-premises directory. The property is only populated for customers who are synchronizing their on-premises directory to Azure Active Directory via Azure AD Connect. Read-only. Returned only on $select. +@property(strong, nullable) NSString *onPremisesSamAccountName; //!< Contains the on-premises SAM account name synchronized from the on-premises directory. Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.m new file mode 100644 index 00000000..377a516b --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAGroup.m @@ -0,0 +1,82 @@ +// +// GAGroup.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAGroup.h" +#import "GAUser.h" + +// occgen: type start +@implementation GAGroup + +// occgen: type serialization {"locked":true} ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAGroup *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_MAP(desc, "description", NSString, Nil); + GA_SET(displayName, NSString, Nil); + GA_SET(members, GAUser, NSArray.class); + GA_SET(onPremisesDomainName, NSString, Nil); + GA_SET(onPremisesSamAccountName, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization {"locked":true} ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _desc = [decoder decodeObjectOfClass:NSString.class forKey:@"desc"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _members = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAUser.class, NSArray.class, nil] forKey:@"members"]; + _onPremisesDomainName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesDomainName"]; + _onPremisesSamAccountName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesSamAccountName"]; + } + + return (self); +} + +// occgen: type native serialization {"locked":true} +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_desc forKey:@"desc"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_members forKey:@"members"]; + [coder encodeObject:_onPremisesDomainName forKey:@"onPremisesDomainName"]; + [coder encodeObject:_onPremisesSamAccountName forKey:@"onPremisesSamAccountName"]; +} + +// occgen: type debug description {"locked":true} +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_desc!=nil) ? [NSString stringWithFormat:@", desc: %@", _desc] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_members!=nil) ? [NSString stringWithFormat:@", members: %@", _members] : @""), ((_onPremisesDomainName!=nil) ? [NSString stringWithFormat:@", onPremisesDomainName: %@", _onPremisesDomainName] : @""), ((_onPremisesSamAccountName!=nil) ? [NSString stringWithFormat:@", onPremisesSamAccountName: %@", _onPremisesSamAccountName] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.h new file mode 100644 index 00000000..b1fd23ed --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.h @@ -0,0 +1,37 @@ +// +// GAHashes.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAHashes : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *crc32Hash; //!< The CRC32 value of the file (if available). Read-only. +@property(strong, nullable) NSString *quickXorHash; //!< A proprietary hash of the file that can be used to determine if the contents of the file have changed (if available). Read-only. +@property(strong, nullable) NSString *sha1Hash; //!< SHA1 hash for the contents of the file (if available). Read-only. +@property(strong, nullable) NSString *sha256Hash; //!< SHA256 hash for the contents of the file (if available). Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.m new file mode 100644 index 00000000..e676fe34 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAHashes.m @@ -0,0 +1,75 @@ +// +// GAHashes.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAHashes.h" + +// occgen: type start +@implementation GAHashes + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAHashes *instance = [self new]; + + GA_SET(crc32Hash, NSString, Nil); + GA_SET(quickXorHash, NSString, Nil); + GA_SET(sha1Hash, NSString, Nil); + GA_SET(sha256Hash, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _crc32Hash = [decoder decodeObjectOfClass:NSString.class forKey:@"crc32Hash"]; + _quickXorHash = [decoder decodeObjectOfClass:NSString.class forKey:@"quickXorHash"]; + _sha1Hash = [decoder decodeObjectOfClass:NSString.class forKey:@"sha1Hash"]; + _sha256Hash = [decoder decodeObjectOfClass:NSString.class forKey:@"sha256Hash"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_crc32Hash forKey:@"crc32Hash"]; + [coder encodeObject:_quickXorHash forKey:@"quickXorHash"]; + [coder encodeObject:_sha1Hash forKey:@"sha1Hash"]; + [coder encodeObject:_sha256Hash forKey:@"sha256Hash"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@>", NSStringFromClass(self.class), self, ((_crc32Hash!=nil) ? [NSString stringWithFormat:@", crc32Hash: %@", _crc32Hash] : @""), ((_quickXorHash!=nil) ? [NSString stringWithFormat:@", quickXorHash: %@", _quickXorHash] : @""), ((_sha1Hash!=nil) ? [NSString stringWithFormat:@", sha1Hash: %@", _sha1Hash] : @""), ((_sha256Hash!=nil) ? [NSString stringWithFormat:@", sha256Hash: %@", _sha256Hash] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.h new file mode 100644 index 00000000..38911289 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.h @@ -0,0 +1,35 @@ +// +// GAIdentity.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAIdentity : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *displayName; //!< The identity''s display name. Note that this may not always be available or up to date. For example, if a user changes their display name, the API may show the new value in a future response, but the items associated with the user won''t show up as having changed when using delta. +@property(strong, nullable) NSString *identifier; //!< Unique identifier for the identity. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.m new file mode 100644 index 00000000..71b7fec8 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentity.m @@ -0,0 +1,69 @@ +// +// GAIdentity.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAIdentity.h" + +// occgen: type start +@implementation GAIdentity + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAIdentity *instance = [self new]; + + GA_SET(displayName, NSString, Nil); + GA_MAP(identifier, "id", NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_identifier forKey:@"identifier"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.h new file mode 100644 index 00000000..801fd957 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.h @@ -0,0 +1,40 @@ +// +// GAIdentitySet.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAIdentity; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAIdentitySet : NSObject + +// occgen: type properties +@property(strong, nullable) GAIdentity *application; //!< Optional. The application associated with this action. +@property(strong, nullable) GAIdentity *device; //!< Optional. The device associated with this action. +@property(strong, nullable) GAIdentity *user; //!< Optional. The user associated with this action. +@property(strong, nullable) GAIdentity *group; //!< Optional. The group associated with this action. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.m new file mode 100644 index 00000000..faa48151 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAIdentitySet.m @@ -0,0 +1,76 @@ +// +// GAIdentitySet.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAIdentitySet.h" +#import "GAIdentity.h" + +// occgen: type start +@implementation GAIdentitySet + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAIdentitySet *instance = [self new]; + + GA_SET(application, GAIdentity, Nil); + GA_SET(device, GAIdentity, Nil); + GA_SET(user, GAIdentity, Nil); + GA_SET(group, GAIdentity, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _application = [decoder decodeObjectOfClass:GAIdentity.class forKey:@"application"]; + _device = [decoder decodeObjectOfClass:GAIdentity.class forKey:@"device"]; + _user = [decoder decodeObjectOfClass:GAIdentity.class forKey:@"user"]; + _group = [decoder decodeObjectOfClass:GAIdentity.class forKey:@"group"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_application forKey:@"application"]; + [coder encodeObject:_device forKey:@"device"]; + [coder encodeObject:_user forKey:@"user"]; + [coder encodeObject:_group forKey:@"group"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@>", NSStringFromClass(self.class), self, ((_application!=nil) ? [NSString stringWithFormat:@", application: %@", _application] : @""), ((_device!=nil) ? [NSString stringWithFormat:@", device: %@", _device] : @""), ((_user!=nil) ? [NSString stringWithFormat:@", user: %@", _user] : @""), ((_group!=nil) ? [NSString stringWithFormat:@", group: %@", _group] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.h new file mode 100644 index 00000000..06fb6aff --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.h @@ -0,0 +1,35 @@ +// +// GAImage.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAImage : NSObject + +// occgen: type properties +@property(strong, nullable) NSNumber *height; //!< [integer:int32] Optional. Height of the image, in pixels. Read-only. +@property(strong, nullable) NSNumber *width; //!< [integer:int32] Optional. Width of the image, in pixels. Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.m new file mode 100644 index 00000000..dbcb88d1 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAImage.m @@ -0,0 +1,69 @@ +// +// GAImage.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAImage.h" + +// occgen: type start +@implementation GAImage + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAImage *instance = [self new]; + + GA_SET(height, NSNumber, Nil); + GA_SET(width, NSNumber, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _height = [decoder decodeObjectOfClass:NSNumber.class forKey:@"height"]; + _width = [decoder decodeObjectOfClass:NSNumber.class forKey:@"width"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_height forKey:@"height"]; + [coder encodeObject:_width forKey:@"width"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_height!=nil) ? [NSString stringWithFormat:@", height: %@", _height] : @""), ((_width!=nil) ? [NSString stringWithFormat:@", width: %@", _width] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.h new file mode 100644 index 00000000..85a00dae --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.h @@ -0,0 +1,39 @@ +// +// GAItemReference.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAItemReference : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *driveId; //!< Unique identifier of the drive instance that contains the item. Read-only. +@property(strong, nullable) NSString *driveType; //!< Identifies the type of drive. See [drive][] resource for values. Read-only. +@property(strong, nullable) NSString *identifier; //!< Unique identifier of the item in the drive. Read-only. +@property(strong, nullable) NSString *name; //!< The name of the item being referenced. Read-only. +@property(strong, nullable) NSString *path; //!< Path that can be used to navigate to the item. Read-only. +@property(strong, nullable) NSString *shareId; //!< A unique identifier for a shared resource that can be accessed via the [Shares][] API. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.m new file mode 100644 index 00000000..2f0bf5b5 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAItemReference.m @@ -0,0 +1,81 @@ +// +// GAItemReference.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAItemReference.h" + +// occgen: type start +@implementation GAItemReference + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAItemReference *instance = [self new]; + + GA_SET(driveId, NSString, Nil); + GA_SET(driveType, NSString, Nil); + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(name, NSString, Nil); + GA_SET(path, NSString, Nil); + GA_SET(shareId, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _driveId = [decoder decodeObjectOfClass:NSString.class forKey:@"driveId"]; + _driveType = [decoder decodeObjectOfClass:NSString.class forKey:@"driveType"]; + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + _path = [decoder decodeObjectOfClass:NSString.class forKey:@"path"]; + _shareId = [decoder decodeObjectOfClass:NSString.class forKey:@"shareId"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_driveId forKey:@"driveId"]; + [coder encodeObject:_driveType forKey:@"driveType"]; + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_name forKey:@"name"]; + [coder encodeObject:_path forKey:@"path"]; + [coder encodeObject:_shareId forKey:@"shareId"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_driveId!=nil) ? [NSString stringWithFormat:@", driveId: %@", _driveId] : @""), ((_driveType!=nil) ? [NSString stringWithFormat:@", driveType: %@", _driveType] : @""), ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_name!=nil) ? [NSString stringWithFormat:@", name: %@", _name] : @""), ((_path!=nil) ? [NSString stringWithFormat:@", path: %@", _path] : @""), ((_shareId!=nil) ? [NSString stringWithFormat:@", shareId: %@", _shareId] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.h new file mode 100644 index 00000000..e5575f5c --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.h @@ -0,0 +1,37 @@ +// +// GAODataError.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAODataErrorMain; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAODataError : NSObject + +// occgen: type properties +@property(strong) GAODataErrorMain *error; + +// occgen: type protected {"locked":true} +- (NSError *)nativeError; + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.m new file mode 100644 index 00000000..52d538cc --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataError.m @@ -0,0 +1,74 @@ +// +// GAODataError.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAODataError.h" +#import "GAODataErrorMain.h" + +// occgen: error include { "locked" : true } +#import "NSError+OCError.h" + +// occgen: type start +@implementation GAODataError + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAODataError *instance = [self new]; + + GA_SET_REQ(error, GAODataErrorMain, Nil); + + return (instance); +} + +// occgen: type protected {"locked":true} +- (NSError *)nativeError +{ + NSError *error = _error.nativeError; + return ((error != nil) ? error : OCError(OCErrorGraphError)); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _error = [decoder decodeObjectOfClass:GAODataErrorMain.class forKey:@"error"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_error forKey:@"error"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@>", NSStringFromClass(self.class), self, ((_error!=nil) ? [NSString stringWithFormat:@", error: %@", _error] : @"")]); +} + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.h new file mode 100644 index 00000000..ba48633f --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.h @@ -0,0 +1,36 @@ +// +// GAODataErrorDetail.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAODataErrorDetail : NSObject + +// occgen: type properties +@property(strong) NSString *code; +@property(strong) NSString *message; +@property(strong, nullable) NSString *target; + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.m new file mode 100644 index 00000000..0b2bdc4b --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorDetail.m @@ -0,0 +1,72 @@ +// +// GAODataErrorDetail.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAODataErrorDetail.h" + +// occgen: type start +@implementation GAODataErrorDetail + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAODataErrorDetail *instance = [self new]; + + GA_SET_REQ(code, NSString, Nil); + GA_SET_REQ(message, NSString, Nil); + GA_SET(target, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _code = [decoder decodeObjectOfClass:NSString.class forKey:@"code"]; + _message = [decoder decodeObjectOfClass:NSString.class forKey:@"message"]; + _target = [decoder decodeObjectOfClass:NSString.class forKey:@"target"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_code forKey:@"code"]; + [coder encodeObject:_message forKey:@"message"]; + [coder encodeObject:_target forKey:@"target"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_code!=nil) ? [NSString stringWithFormat:@", code: %@", _code] : @""), ((_message!=nil) ? [NSString stringWithFormat:@", message: %@", _message] : @""), ((_target!=nil) ? [NSString stringWithFormat:@", target: %@", _target] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.h new file mode 100644 index 00000000..d9e180fb --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.h @@ -0,0 +1,41 @@ +// +// GAODataErrorMain.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAODataErrorDetail; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAODataErrorMain : NSObject + +// occgen: type properties +@property(strong) NSString *code; +@property(strong) NSString *message; +@property(strong, nullable) NSString *target; +@property(strong, nullable) NSArray *details; +@property(strong, nullable) NSDictionary *innererror; //!< The structure of this object is service-specific + +// occgen: type protected {"locked":true} +- (NSError *)nativeError; + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.m new file mode 100644 index 00000000..7e114185 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAODataErrorMain.m @@ -0,0 +1,89 @@ +// +// GAODataErrorMain.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAODataErrorMain.h" +#import "GAODataErrorDetail.h" + +// occgen: error include {"locked":true} +#import "NSError+OCError.h" + +// occgen: type start +@implementation GAODataErrorMain + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAODataErrorMain *instance = [self new]; + + GA_SET_REQ(code, NSString, Nil); + GA_SET_REQ(message, NSString, Nil); + GA_SET(target, NSString, Nil); + GA_SET(details, GAODataErrorDetail, NSArray.class); + GA_SET(innererror, NSDictionary, Nil); + + return (instance); +} + +// occgen: type protected {"locked":true} +- (NSError *)nativeError +{ + NSError *error = [NSError errorWithDomain:OCErrorDomain code:OCErrorRequiredValueMissing userInfo:@{ + @"gaError" : self + }]; + + return (error); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _code = [decoder decodeObjectOfClass:NSString.class forKey:@"code"]; + _message = [decoder decodeObjectOfClass:NSString.class forKey:@"message"]; + _target = [decoder decodeObjectOfClass:NSString.class forKey:@"target"]; + _details = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAODataErrorDetail.class, NSArray.class, nil] forKey:@"details"]; + _innererror = [decoder decodeObjectOfClass:NSDictionary.class forKey:@"innererror"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_code forKey:@"code"]; + [coder encodeObject:_message forKey:@"message"]; + [coder encodeObject:_target forKey:@"target"]; + [coder encodeObject:_details forKey:@"details"]; + [coder encodeObject:_innererror forKey:@"innererror"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_code!=nil) ? [NSString stringWithFormat:@", code: %@", _code] : @""), ((_message!=nil) ? [NSString stringWithFormat:@", message: %@", _message] : @""), ((_target!=nil) ? [NSString stringWithFormat:@", target: %@", _target] : @""), ((_details!=nil) ? [NSString stringWithFormat:@", details: %@", _details] : @""), ((_innererror!=nil) ? [NSString stringWithFormat:@", innererror: %@", _innererror] : @"")]); +} + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.h new file mode 100644 index 00000000..245f68b2 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.h @@ -0,0 +1,35 @@ +// +// GAObjectIdentity.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAObjectIdentity : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *issuer; //!< domain of the Provider issuing the identity +@property(strong, nullable) NSString *issuerAssignedId; //!< The unique id assigned by the issuer to the account + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.m new file mode 100644 index 00000000..f6ba09de --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAObjectIdentity.m @@ -0,0 +1,69 @@ +// +// GAObjectIdentity.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAObjectIdentity.h" + +// occgen: type start +@implementation GAObjectIdentity + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAObjectIdentity *instance = [self new]; + + GA_SET(issuer, NSString, Nil); + GA_SET(issuerAssignedId, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _issuer = [decoder decodeObjectOfClass:NSString.class forKey:@"issuer"]; + _issuerAssignedId = [decoder decodeObjectOfClass:NSString.class forKey:@"issuerAssignedId"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_issuer forKey:@"issuer"]; + [coder encodeObject:_issuerAssignedId forKey:@"issuerAssignedId"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_issuer!=nil) ? [NSString stringWithFormat:@", issuer: %@", _issuer] : @""), ((_issuerAssignedId!=nil) ? [NSString stringWithFormat:@", issuerAssignedId: %@", _issuerAssignedId] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.h new file mode 100644 index 00000000..4e8d26f0 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.h @@ -0,0 +1,39 @@ +// +// GAOpenGraphFile.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAHashes; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAOpenGraphFile : NSObject + +// occgen: type properties +@property(strong, nullable) GAHashes *hashes; +@property(strong, nullable) NSString *mimeType; //!< The MIME type for the file. This is determined by logic on the server and might not be the value provided when the file was uploaded. Read-only. +@property(strong, nullable) NSNumber *processingMetadata; //!< [boolean] + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.m new file mode 100644 index 00000000..91597776 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAOpenGraphFile.m @@ -0,0 +1,73 @@ +// +// GAOpenGraphFile.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAOpenGraphFile.h" +#import "GAHashes.h" + +// occgen: type start +@implementation GAOpenGraphFile + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAOpenGraphFile *instance = [self new]; + + GA_SET(hashes, GAHashes, Nil); + GA_SET(mimeType, NSString, Nil); + GA_SET(processingMetadata, NSNumber, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _hashes = [decoder decodeObjectOfClass:GAHashes.class forKey:@"hashes"]; + _mimeType = [decoder decodeObjectOfClass:NSString.class forKey:@"mimeType"]; + _processingMetadata = [decoder decodeObjectOfClass:NSNumber.class forKey:@"processingMetadata"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_hashes forKey:@"hashes"]; + [coder encodeObject:_mimeType forKey:@"mimeType"]; + [coder encodeObject:_processingMetadata forKey:@"processingMetadata"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, ((_hashes!=nil) ? [NSString stringWithFormat:@", hashes: %@", _hashes] : @""), ((_mimeType!=nil) ? [NSString stringWithFormat:@", mimeType: %@", _mimeType] : @""), ((_processingMetadata!=nil) ? [NSString stringWithFormat:@", processingMetadata: %@", _processingMetadata] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.h new file mode 100644 index 00000000..6edd4266 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.h @@ -0,0 +1,35 @@ +// +// GAPasswordProfile.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAPasswordProfile : NSObject + +// occgen: type properties +@property(strong, nullable) NSNumber *forceChangePasswordNextSignIn; //!< [boolean] If true the user is required to change their password upon the next login +@property(strong, nullable) NSString *password; //!< The user's password + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.m new file mode 100644 index 00000000..6a49730e --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPasswordProfile.m @@ -0,0 +1,69 @@ +// +// GAPasswordProfile.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAPasswordProfile.h" + +// occgen: type start +@implementation GAPasswordProfile + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAPasswordProfile *instance = [self new]; + + GA_SET(forceChangePasswordNextSignIn, NSNumber, Nil); + GA_SET(password, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _forceChangePasswordNextSignIn = [decoder decodeObjectOfClass:NSNumber.class forKey:@"forceChangePasswordNextSignIn"]; + _password = [decoder decodeObjectOfClass:NSString.class forKey:@"password"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_forceChangePasswordNextSignIn forKey:@"forceChangePasswordNextSignIn"]; + [coder encodeObject:_password forKey:@"password"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_forceChangePasswordNextSignIn!=nil) ? [NSString stringWithFormat:@", forceChangePasswordNextSignIn: %@", _forceChangePasswordNextSignIn] : @""), ((_password!=nil) ? [NSString stringWithFormat:@", password: %@", _password] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.h new file mode 100644 index 00000000..946e04d7 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.h @@ -0,0 +1,38 @@ +// +// GAPermission.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAIdentitySet; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAPermission : NSObject + +// occgen: type properties +@property(strong, nullable) NSArray *grantedToIdentities; +@property(strong, nullable) NSArray *roles; + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.m new file mode 100644 index 00000000..541657ff --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAPermission.m @@ -0,0 +1,70 @@ +// +// GAPermission.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAPermission.h" +#import "GAIdentitySet.h" + +// occgen: type start +@implementation GAPermission + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAPermission *instance = [self new]; + + GA_SET(grantedToIdentities, GAIdentitySet, NSArray.class); + GA_SET(roles, NSString, NSArray.class); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _grantedToIdentities = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAIdentitySet.class, NSArray.class, nil] forKey:@"grantedToIdentities"]; + _roles = [decoder decodeObjectOfClasses:[NSSet setWithObjects: NSString.class, NSArray.class, nil] forKey:@"roles"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_grantedToIdentities forKey:@"grantedToIdentities"]; + [coder encodeObject:_roles forKey:@"roles"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_grantedToIdentities!=nil) ? [NSString stringWithFormat:@", grantedToIdentities: %@", _grantedToIdentities] : @""), ((_roles!=nil) ? [NSString stringWithFormat:@", roles: %@", _roles] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.h new file mode 100644 index 00000000..56cf96e4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.h @@ -0,0 +1,41 @@ +// +// GAQuota.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations { "locked" : true } +typedef NSString * OCQuotaState; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAQuota : NSObject + +// occgen: type properties { "customPropertyTypes" : { "state" : "OCQuotaState" } } +@property(strong, nullable) NSNumber *deleted; //!< [integer:int64] Total space consumed by files in the recycle bin, in bytes. Read-only. +@property(strong, nullable) NSNumber *remaining; //!< [integer:int64] Total space remaining before reaching the quota limit, in bytes. Read-only. +@property(strong, nullable) OCQuotaState state; //!< Enumeration value that indicates the state of the storage space. Either "normal", "nearing", "critical" or "exceeded". Read-only. +@property(strong, nullable) NSNumber *total; //!< [integer:int64] Total allowed storage space, in bytes. Read-only. +@property(strong, nullable) NSNumber *used; //!< [integer:int64] Total space used, in bytes. Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.m new file mode 100644 index 00000000..68ad6d82 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAQuota.m @@ -0,0 +1,78 @@ +// +// GAQuota.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAQuota.h" + +// occgen: type start +@implementation GAQuota + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAQuota *instance = [self new]; + + GA_SET(deleted, NSNumber, Nil); + GA_SET(remaining, NSNumber, Nil); + GA_SET(state, NSString, Nil); + GA_SET(total, NSNumber, Nil); + GA_SET(used, NSNumber, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _deleted = [decoder decodeObjectOfClass:NSNumber.class forKey:@"deleted"]; + _remaining = [decoder decodeObjectOfClass:NSNumber.class forKey:@"remaining"]; + _state = [decoder decodeObjectOfClass:NSString.class forKey:@"state"]; + _total = [decoder decodeObjectOfClass:NSNumber.class forKey:@"total"]; + _used = [decoder decodeObjectOfClass:NSNumber.class forKey:@"used"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_deleted forKey:@"deleted"]; + [coder encodeObject:_remaining forKey:@"remaining"]; + [coder encodeObject:_state forKey:@"state"]; + [coder encodeObject:_total forKey:@"total"]; + [coder encodeObject:_used forKey:@"used"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_deleted!=nil) ? [NSString stringWithFormat:@", deleted: %@", _deleted] : @""), ((_remaining!=nil) ? [NSString stringWithFormat:@", remaining: %@", _remaining] : @""), ((_state!=nil) ? [NSString stringWithFormat:@", state: %@", _state] : @""), ((_total!=nil) ? [NSString stringWithFormat:@", total: %@", _total] : @""), ((_used!=nil) ? [NSString stringWithFormat:@", used: %@", _used] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.h new file mode 100644 index 00000000..4d890fb4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.h @@ -0,0 +1,62 @@ +// +// GARemoteItem.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +typedef NSString * OCFileETag; +@class GAFileSystemInfo; +@class GAFolder; +@class GAIdentitySet; +@class GAImage; +@class GAItemReference; +@class GAOpenGraphFile; +@class GAShared; +@class GASpecialFolder; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GARemoteItem : NSObject + +// occgen: type properties { "customPropertyTypes" : { "eTag" : "OCFileETag" }} +@property(strong, nullable) GAIdentitySet *createdBy; +@property(strong, nullable) NSDate *createdDateTime; //!< [string:date-time] Date and time of item creation. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) GAOpenGraphFile *file; +@property(strong, nullable) GAFileSystemInfo *fileSystemInfo; +@property(strong, nullable) GAFolder *folder; +@property(strong, nullable) NSString *identifier; //!< Unique identifier for the remote item in its drive. Read-only. +@property(strong, nullable) GAImage *image; +@property(strong, nullable) GAIdentitySet *lastModifiedBy; +@property(strong, nullable) NSDate *lastModifiedDateTime; //!< [string:date-time] Date and time the item was last modified. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$ +@property(strong, nullable) NSString *name; //!< Optional. Filename of the remote item. Read-only. +@property(strong, nullable) OCFileETag eTag; //!< ETag for the item. Read-only. +@property(strong, nullable) NSString *cTag; //!< An eTag for the content of the item. This eTag is not changed if only the metadata is changed. Note This property is not returned if the item is a folder. Read-only. +@property(strong, nullable) GAItemReference *parentReference; +@property(strong, nullable) GAShared *shared; +@property(strong, nullable) NSNumber *size; //!< [integer:int64] Size of the remote item. Read-only. +@property(strong, nullable) GASpecialFolder *specialFolder; +@property(strong, nullable) NSURL *webDavUrl; //!< DAV compatible URL for the item. +@property(strong, nullable) NSURL *webUrl; //!< URL that displays the resource in the browser. Read-only. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.m new file mode 100644 index 00000000..628e1115 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GARemoteItem.m @@ -0,0 +1,125 @@ +// +// GARemoteItem.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GARemoteItem.h" +#import "GAFileSystemInfo.h" +#import "GAFolder.h" +#import "GAIdentitySet.h" +#import "GAImage.h" +#import "GAItemReference.h" +#import "GAOpenGraphFile.h" +#import "GAShared.h" +#import "GASpecialFolder.h" + +// occgen: type start +@implementation GARemoteItem + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GARemoteItem *instance = [self new]; + + GA_SET(createdBy, GAIdentitySet, Nil); + GA_SET(createdDateTime, NSDate, Nil); + GA_SET(file, GAOpenGraphFile, Nil); + GA_SET(fileSystemInfo, GAFileSystemInfo, Nil); + GA_SET(folder, GAFolder, Nil); + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(image, GAImage, Nil); + GA_SET(lastModifiedBy, GAIdentitySet, Nil); + GA_SET(lastModifiedDateTime, NSDate, Nil); + GA_SET(name, NSString, Nil); + GA_SET(eTag, NSString, Nil); + GA_SET(cTag, NSString, Nil); + GA_SET(parentReference, GAItemReference, Nil); + GA_SET(shared, GAShared, Nil); + GA_SET(size, NSNumber, Nil); + GA_SET(specialFolder, GASpecialFolder, Nil); + GA_SET(webDavUrl, NSURL, Nil); + GA_SET(webUrl, NSURL, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _createdBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"createdBy"]; + _createdDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"createdDateTime"]; + _file = [decoder decodeObjectOfClass:GAOpenGraphFile.class forKey:@"file"]; + _fileSystemInfo = [decoder decodeObjectOfClass:GAFileSystemInfo.class forKey:@"fileSystemInfo"]; + _folder = [decoder decodeObjectOfClass:GAFolder.class forKey:@"folder"]; + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _image = [decoder decodeObjectOfClass:GAImage.class forKey:@"image"]; + _lastModifiedBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"lastModifiedBy"]; + _lastModifiedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastModifiedDateTime"]; + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + _eTag = [decoder decodeObjectOfClass:NSString.class forKey:@"eTag"]; + _cTag = [decoder decodeObjectOfClass:NSString.class forKey:@"cTag"]; + _parentReference = [decoder decodeObjectOfClass:GAItemReference.class forKey:@"parentReference"]; + _shared = [decoder decodeObjectOfClass:GAShared.class forKey:@"shared"]; + _size = [decoder decodeObjectOfClass:NSNumber.class forKey:@"size"]; + _specialFolder = [decoder decodeObjectOfClass:GASpecialFolder.class forKey:@"specialFolder"]; + _webDavUrl = [decoder decodeObjectOfClass:NSURL.class forKey:@"webDavUrl"]; + _webUrl = [decoder decodeObjectOfClass:NSURL.class forKey:@"webUrl"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_createdBy forKey:@"createdBy"]; + [coder encodeObject:_createdDateTime forKey:@"createdDateTime"]; + [coder encodeObject:_file forKey:@"file"]; + [coder encodeObject:_fileSystemInfo forKey:@"fileSystemInfo"]; + [coder encodeObject:_folder forKey:@"folder"]; + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_image forKey:@"image"]; + [coder encodeObject:_lastModifiedBy forKey:@"lastModifiedBy"]; + [coder encodeObject:_lastModifiedDateTime forKey:@"lastModifiedDateTime"]; + [coder encodeObject:_name forKey:@"name"]; + [coder encodeObject:_eTag forKey:@"eTag"]; + [coder encodeObject:_cTag forKey:@"cTag"]; + [coder encodeObject:_parentReference forKey:@"parentReference"]; + [coder encodeObject:_shared forKey:@"shared"]; + [coder encodeObject:_size forKey:@"size"]; + [coder encodeObject:_specialFolder forKey:@"specialFolder"]; + [coder encodeObject:_webDavUrl forKey:@"webDavUrl"]; + [coder encodeObject:_webUrl forKey:@"webUrl"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_createdBy!=nil) ? [NSString stringWithFormat:@", createdBy: %@", _createdBy] : @""), ((_createdDateTime!=nil) ? [NSString stringWithFormat:@", createdDateTime: %@", _createdDateTime] : @""), ((_file!=nil) ? [NSString stringWithFormat:@", file: %@", _file] : @""), ((_fileSystemInfo!=nil) ? [NSString stringWithFormat:@", fileSystemInfo: %@", _fileSystemInfo] : @""), ((_folder!=nil) ? [NSString stringWithFormat:@", folder: %@", _folder] : @""), ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_image!=nil) ? [NSString stringWithFormat:@", image: %@", _image] : @""), ((_lastModifiedBy!=nil) ? [NSString stringWithFormat:@", lastModifiedBy: %@", _lastModifiedBy] : @""), ((_lastModifiedDateTime!=nil) ? [NSString stringWithFormat:@", lastModifiedDateTime: %@", _lastModifiedDateTime] : @""), ((_name!=nil) ? [NSString stringWithFormat:@", name: %@", _name] : @""), ((_eTag!=nil) ? [NSString stringWithFormat:@", eTag: %@", _eTag] : @""), ((_cTag!=nil) ? [NSString stringWithFormat:@", cTag: %@", _cTag] : @""), ((_parentReference!=nil) ? [NSString stringWithFormat:@", parentReference: %@", _parentReference] : @""), ((_shared!=nil) ? [NSString stringWithFormat:@", shared: %@", _shared] : @""), ((_size!=nil) ? [NSString stringWithFormat:@", size: %@", _size] : @""), ((_specialFolder!=nil) ? [NSString stringWithFormat:@", specialFolder: %@", _specialFolder] : @""), ((_webDavUrl!=nil) ? [NSString stringWithFormat:@", webDavUrl: %@", _webDavUrl] : @""), ((_webUrl!=nil) ? [NSString stringWithFormat:@", webUrl: %@", _webUrl] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.h new file mode 100644 index 00000000..f561ef15 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.h @@ -0,0 +1,31 @@ +// +// GARoot.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GARoot : NSObject + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.m new file mode 100644 index 00000000..47a06d2c --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GARoot.m @@ -0,0 +1,63 @@ +// +// GARoot.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GARoot.h" + +// occgen: type start +@implementation GARoot + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GARoot *instance = [self new]; + + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p>", NSStringFromClass(self.class), self]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.h new file mode 100644 index 00000000..f2be0f4f --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.h @@ -0,0 +1,40 @@ +// +// GAShared.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAIdentitySet; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAShared : NSObject + +// occgen: type properties +@property(strong, nullable) GAIdentitySet *owner; +@property(strong, nullable) NSString *scope; //!< Indicates the scope of how the item is shared: anonymous, organization, or users. Read-only. +@property(strong, nullable) GAIdentitySet *sharedBy; +@property(strong, nullable) NSDate *sharedDateTime; //!< [string:date-time] The UTC date and time when the item was shared. Read-only. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.m new file mode 100644 index 00000000..a8467033 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAShared.m @@ -0,0 +1,76 @@ +// +// GAShared.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAShared.h" +#import "GAIdentitySet.h" + +// occgen: type start +@implementation GAShared + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAShared *instance = [self new]; + + GA_SET(owner, GAIdentitySet, Nil); + GA_SET(scope, NSString, Nil); + GA_SET(sharedBy, GAIdentitySet, Nil); + GA_SET(sharedDateTime, NSDate, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _owner = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"owner"]; + _scope = [decoder decodeObjectOfClass:NSString.class forKey:@"scope"]; + _sharedBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"sharedBy"]; + _sharedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"sharedDateTime"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_owner forKey:@"owner"]; + [coder encodeObject:_scope forKey:@"scope"]; + [coder encodeObject:_sharedBy forKey:@"sharedBy"]; + [coder encodeObject:_sharedDateTime forKey:@"sharedDateTime"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@>", NSStringFromClass(self.class), self, ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), ((_scope!=nil) ? [NSString stringWithFormat:@", scope: %@", _scope] : @""), ((_sharedBy!=nil) ? [NSString stringWithFormat:@", sharedBy: %@", _sharedBy] : @""), ((_sharedDateTime!=nil) ? [NSString stringWithFormat:@", sharedDateTime: %@", _sharedDateTime] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.h new file mode 100644 index 00000000..804d4548 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.h @@ -0,0 +1,66 @@ +// +// GASpecialFolder.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +typedef NSString * GASpecialFolderName; + +// occgen: typedefs {"locked":true} +typedef NSString* GASpecialFolderName NS_TYPED_ENUM; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GASpecialFolder : NSObject + +// occgen: type properties { "customPropertyTypes" : { "name" : "GASpecialFolderName" }} +@property(strong, nullable) GASpecialFolderName name; //!< The unique identifier for this item in the /drive/special collection + +// occgen: type protected {"locked":true} + + +// occgen: type end {"locked":true} +@end + +extern GASpecialFolderName GASpecialFolderNameReadme; +extern GASpecialFolderName GASpecialFolderNameImage; + +NS_ASSUME_NONNULL_END + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.m new file mode 100644 index 00000000..d078656c --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GASpecialFolder.m @@ -0,0 +1,90 @@ +// +// GASpecialFolder.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GASpecialFolder.h" + +// occgen: type start +@implementation GASpecialFolder + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GASpecialFolder *instance = [self new]; + + GA_SET(name, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _name = [decoder decodeObjectOfClass:NSString.class forKey:@"name"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_name forKey:@"name"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@>", NSStringFromClass(self.class), self, ((_name!=nil) ? [NSString stringWithFormat:@", name: %@", _name] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end {"locked":true} +@end + +GASpecialFolderName GASpecialFolderNameReadme = @"readme"; +GASpecialFolderName GASpecialFolderNameImage = @"image"; + + + + + + + + + + + + + + + + + + + + + + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.h new file mode 100644 index 00000000..3ef136d1 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.h @@ -0,0 +1,35 @@ +// +// GATagAssignment.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GATagAssignment : NSObject + +// occgen: type properties +@property(strong) NSString *resourceId; +@property(strong) NSArray *tags; + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.m new file mode 100644 index 00000000..407446b4 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagAssignment.m @@ -0,0 +1,69 @@ +// +// GATagAssignment.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GATagAssignment.h" + +// occgen: type start +@implementation GATagAssignment + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GATagAssignment *instance = [self new]; + + GA_SET_REQ(resourceId, NSString, Nil); + GA_SET_REQ(tags, NSString, NSArray.class); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _resourceId = [decoder decodeObjectOfClass:NSString.class forKey:@"resourceId"]; + _tags = [decoder decodeObjectOfClasses:[NSSet setWithObjects: NSString.class, NSArray.class, nil] forKey:@"tags"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_resourceId forKey:@"resourceId"]; + [coder encodeObject:_tags forKey:@"tags"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_resourceId!=nil) ? [NSString stringWithFormat:@", resourceId: %@", _resourceId] : @""), ((_tags!=nil) ? [NSString stringWithFormat:@", tags: %@", _tags] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.h new file mode 100644 index 00000000..888f59fb --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.h @@ -0,0 +1,35 @@ +// +// GATagUnassignment.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GATagUnassignment : NSObject + +// occgen: type properties +@property(strong) NSString *resourceId; +@property(strong) NSArray *tags; + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.m new file mode 100644 index 00000000..9fe71cac --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATagUnassignment.m @@ -0,0 +1,69 @@ +// +// GATagUnassignment.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GATagUnassignment.h" + +// occgen: type start +@implementation GATagUnassignment + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GATagUnassignment *instance = [self new]; + + GA_SET_REQ(resourceId, NSString, Nil); + GA_SET_REQ(tags, NSString, NSArray.class); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _resourceId = [decoder decodeObjectOfClass:NSString.class forKey:@"resourceId"]; + _tags = [decoder decodeObjectOfClasses:[NSSet setWithObjects: NSString.class, NSArray.class, nil] forKey:@"tags"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_resourceId forKey:@"resourceId"]; + [coder encodeObject:_tags forKey:@"tags"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_resourceId!=nil) ? [NSString stringWithFormat:@", resourceId: %@", _resourceId] : @""), ((_tags!=nil) ? [NSString stringWithFormat:@", tags: %@", _tags] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.h new file mode 100644 index 00000000..127d2d2c --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.h @@ -0,0 +1,38 @@ +// +// GATrash.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GAIdentitySet; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GATrash : NSObject + +// occgen: type properties +@property(strong, nullable) GAIdentitySet *trashedBy; //!< Identity of the user, device, or application which trashed the item. Read-only. +@property(strong, nullable) NSDate *trashedDateTime; //!< [string:date-time] The UTC date and time the folder was marked as trashed. | pattern: ^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?([Zz]|[+-][0-9][0-9]:[0-9][0-9])$ + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.m new file mode 100644 index 00000000..5261d15c --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GATrash.m @@ -0,0 +1,70 @@ +// +// GATrash.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GATrash.h" +#import "GAIdentitySet.h" + +// occgen: type start +@implementation GATrash + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GATrash *instance = [self new]; + + GA_SET(trashedBy, GAIdentitySet, Nil); + GA_SET(trashedDateTime, NSDate, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _trashedBy = [decoder decodeObjectOfClass:GAIdentitySet.class forKey:@"trashedBy"]; + _trashedDateTime = [decoder decodeObjectOfClass:NSDate.class forKey:@"trashedDateTime"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_trashedBy forKey:@"trashedBy"]; + [coder encodeObject:_trashedDateTime forKey:@"trashedDateTime"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, ((_trashedBy!=nil) ? [NSString stringWithFormat:@", trashedBy: %@", _trashedBy] : @""), ((_trashedDateTime!=nil) ? [NSString stringWithFormat:@", trashedDateTime: %@", _trashedDateTime] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.h b/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.h new file mode 100644 index 00000000..e34630dd --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.h @@ -0,0 +1,51 @@ +// +// GAUser.h +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import +#import "GAGraphObject.h" + +// occgen: forward declarations +@class GADrive; +@class GAGroup; +@class GAObjectIdentity; +@class GAPasswordProfile; + +// occgen: type start +NS_ASSUME_NONNULL_BEGIN +@interface GAUser : NSObject + +// occgen: type properties +@property(strong, nullable) NSString *identifier; //!< Read-only. +@property(strong, nullable) NSNumber *accountEnabled; //!< [boolean] Set to "true" when the account is enabled. +@property(strong, nullable) NSString *displayName; //!< The name displayed in the address book for the user. This value is usually the combination of the user''s first name, middle initial, and last name. This property is required when a user is created and it cannot be cleared during updates. Returned by default. Supports $filter and $orderby. +@property(strong, nullable) NSArray *drives; //!< A collection of drives available for this user. Read-only. +@property(strong, nullable) GADrive *drive; //!< The personal drive of this user. Read-only. +@property(strong, nullable) NSArray *identities; //!< Identities associated with this account. +@property(strong, nullable) NSString *mail; //!< The SMTP address for the user, for example, ''jeff@contoso.onowncloud.com''. Returned by default. Supports $filter and endsWith. +@property(strong, nullable) NSArray *memberOf; //!< Groups that this user is a member of. HTTP Methods: GET (supported for all groups). Read-only. Nullable. Supports $expand. +@property(strong, nullable) NSString *onPremisesSamAccountName; //!< Contains the on-premises SAM account name synchronized from the on-premises directory. Read-only. +@property(strong, nullable) GAPasswordProfile *passwordProfile; +@property(strong, nullable) NSString *surname; //!< The user's surname (family name or last name). Returned by default. Supports $filter. +@property(strong, nullable) NSString *givenName; //!< The user's givenName. Returned by default. Supports $filter. + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end +NS_ASSUME_NONNULL_END + diff --git a/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.m b/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.m new file mode 100644 index 00000000..36278eb6 --- /dev/null +++ b/ownCloudSDK/GraphAPI/GeneratedTypes/GAUser.m @@ -0,0 +1,103 @@ +// +// GAUser.m +// Autogenerated / Managed by ocapigen +// Copyright (C) 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 . + * + */ + +// occgen: includes +#import "GAUser.h" +#import "GADrive.h" +#import "GAGroup.h" +#import "GAObjectIdentity.h" +#import "GAPasswordProfile.h" + +// occgen: type start +@implementation GAUser + +// occgen: type serialization ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + GAUser *instance = [self new]; + + GA_MAP(identifier, "id", NSString, Nil); + GA_SET(accountEnabled, NSNumber, Nil); + GA_SET(displayName, NSString, Nil); + GA_SET(drives, GADrive, NSArray.class); + GA_SET(drive, GADrive, Nil); + GA_SET(identities, GAObjectIdentity, NSArray.class); + GA_SET(mail, NSString, Nil); + GA_SET(memberOf, GAGroup, NSArray.class); + GA_SET(onPremisesSamAccountName, NSString, Nil); + GA_SET(passwordProfile, GAPasswordProfile, Nil); + GA_SET(surname, NSString, Nil); + GA_SET(givenName, NSString, Nil); + + return (instance); +} + +// occgen: type native deserialization ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _identifier = [decoder decodeObjectOfClass:NSString.class forKey:@"identifier"]; + _accountEnabled = [decoder decodeObjectOfClass:NSNumber.class forKey:@"accountEnabled"]; + _displayName = [decoder decodeObjectOfClass:NSString.class forKey:@"displayName"]; + _drives = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GADrive.class, NSArray.class, nil] forKey:@"drives"]; + _drive = [decoder decodeObjectOfClass:GADrive.class forKey:@"drive"]; + _identities = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAObjectIdentity.class, NSArray.class, nil] forKey:@"identities"]; + _mail = [decoder decodeObjectOfClass:NSString.class forKey:@"mail"]; + _memberOf = [decoder decodeObjectOfClasses:[NSSet setWithObjects: GAGroup.class, NSArray.class, nil] forKey:@"memberOf"]; + _onPremisesSamAccountName = [decoder decodeObjectOfClass:NSString.class forKey:@"onPremisesSamAccountName"]; + _passwordProfile = [decoder decodeObjectOfClass:GAPasswordProfile.class forKey:@"passwordProfile"]; + _surname = [decoder decodeObjectOfClass:NSString.class forKey:@"surname"]; + _givenName = [decoder decodeObjectOfClass:NSString.class forKey:@"givenName"]; + } + + return (self); +} + +// occgen: type native serialization +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_identifier forKey:@"identifier"]; + [coder encodeObject:_accountEnabled forKey:@"accountEnabled"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + [coder encodeObject:_drives forKey:@"drives"]; + [coder encodeObject:_drive forKey:@"drive"]; + [coder encodeObject:_identities forKey:@"identities"]; + [coder encodeObject:_mail forKey:@"mail"]; + [coder encodeObject:_memberOf forKey:@"memberOf"]; + [coder encodeObject:_onPremisesSamAccountName forKey:@"onPremisesSamAccountName"]; + [coder encodeObject:_passwordProfile forKey:@"passwordProfile"]; + [coder encodeObject:_surname forKey:@"surname"]; + [coder encodeObject:_givenName forKey:@"givenName"]; +} + +// occgen: type debug description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, ((_identifier!=nil) ? [NSString stringWithFormat:@", identifier: %@", _identifier] : @""), ((_accountEnabled!=nil) ? [NSString stringWithFormat:@", accountEnabled: %@", _accountEnabled] : @""), ((_displayName!=nil) ? [NSString stringWithFormat:@", displayName: %@", _displayName] : @""), ((_drives!=nil) ? [NSString stringWithFormat:@", drives: %@", _drives] : @""), ((_drive!=nil) ? [NSString stringWithFormat:@", drive: %@", _drive] : @""), ((_identities!=nil) ? [NSString stringWithFormat:@", identities: %@", _identities] : @""), ((_mail!=nil) ? [NSString stringWithFormat:@", mail: %@", _mail] : @""), ((_memberOf!=nil) ? [NSString stringWithFormat:@", memberOf: %@", _memberOf] : @""), ((_onPremisesSamAccountName!=nil) ? [NSString stringWithFormat:@", onPremisesSamAccountName: %@", _onPremisesSamAccountName] : @""), ((_passwordProfile!=nil) ? [NSString stringWithFormat:@", passwordProfile: %@", _passwordProfile] : @""), ((_surname!=nil) ? [NSString stringWithFormat:@", surname: %@", _surname] : @""), ((_givenName!=nil) ? [NSString stringWithFormat:@", givenName: %@", _givenName] : @"")]); +} + +// occgen: type protected {"locked":true} + + +// occgen: type end +@end + diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.h b/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.h new file mode 100644 index 00000000..5a0f4e20 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.h @@ -0,0 +1,48 @@ +// +// OCGDrive.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import +#import "OCGObject.h" +#import "OCGIdentitySet.h" +#import "OCTypes.h" + +@class OCGItemReference; + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCDriveID; +typedef NSString* OCDriveType NS_TYPED_ENUM; + +@interface OCGDrive : OCGObject + +@property(strong,nullable) OCDriveID id; +@property(strong,nullable) OCGIdentitySet *createdBy; +@property(strong,nullable) NSDate *createdDateTime; +@property(strong,nullable) NSString *driveDescription; +@property(strong,nullable) OCFileETag eTag; +@property(strong,nullable) OCGIdentitySet *lastModifiedBy; +@property(strong,nullable) NSDate *lastModifiedDateTime; +@property(strong,nullable) NSString *name; +@property(strong,nullable) OCGItemReference *parentReference; +@property(strong,nullable) NSURL *webUrl; + +@property(strong,nullable) OCGIdentitySet *createdByUser; +@property(strong,nullable) OCGIdentitySet *lastModifiedByUser; + +@property(strong,nullable) OCDriveType driveType; + +@property(strong,nullable) OCGIdentitySet *owner; + +// quota +// items +// root +// special + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.m b/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.m new file mode 100644 index 00000000..90abe95d --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGDrive.m @@ -0,0 +1,13 @@ +// +// OCGDrive.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCGDrive.h" + +@implementation OCGDrive + +@end diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.h b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.h new file mode 100644 index 00000000..d322e796 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.h @@ -0,0 +1,23 @@ +// +// OCGIdentity.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import +#import "OCGraphObject.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCGIdentityID; + +@interface OCGIdentity : NSObject + +@property(strong,nullable) OCGIdentityID id; +@property(strong,nullable) NSString *displayName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.m b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.m new file mode 100644 index 00000000..fef54242 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentity.m @@ -0,0 +1,23 @@ +// +// OCGIdentity.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCGIdentity.h" + +@implementation OCGIdentity + ++ (nullable instancetype)decodeGraphData:(OCGraphData)structure context:(nullable OCGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + OCGIdentity *instance = [self new]; + + OCG_SET(id, NSString); + OCG_SET(displayName, NSString); + + return (instance); +} + +@end diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.h b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.h new file mode 100644 index 00000000..18a5638f --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.h @@ -0,0 +1,23 @@ +// +// OCGIdentitySet.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import +#import "OCGraphObject.h" +#import "OCGIdentity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCGIdentitySet : NSObject + +@property(nullable,strong) OCGIdentity *application; +@property(nullable,strong) OCGIdentity *device; +@property(nullable,strong) OCGIdentity *user; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.m b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.m new file mode 100644 index 00000000..1bc6123b --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGIdentitySet.m @@ -0,0 +1,24 @@ +// +// OCGIdentitySet.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCGIdentitySet.h" + +@implementation OCGIdentitySet + ++ (nullable instancetype)decodeGraphData:(OCGraphData)structure context:(nullable OCGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + OCGIdentitySet *instance = [self new]; + + OCG_SET(application, OCGIdentity); + OCG_SET(device, OCGIdentity); + OCG_SET(user, OCGIdentity); + + return (instance); +} + +@end diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.h b/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.h new file mode 100644 index 00000000..32f109d8 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.h @@ -0,0 +1,17 @@ +// +// OCGItemReference.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCGObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCGItemReference : OCGObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.m b/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.m new file mode 100644 index 00000000..5c1c68ec --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGItemReference.m @@ -0,0 +1,13 @@ +// +// OCGItemReference.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import "OCGItemReference.h" + +@implementation OCGItemReference + +@end diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.h b/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.h new file mode 100644 index 00000000..c9ce2282 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.h @@ -0,0 +1,33 @@ +// +// OCGObject.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "OCGraphObject.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCGPropertyName; +typedef NSString* OCGJSONName; + +@interface OCGObject : NSObject + +//+ (NSDictionary *)jsonNameByPropertyName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.m b/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.m new file mode 100644 index 00000000..5f51fea1 --- /dev/null +++ b/ownCloudSDK/GraphAPI/ManualTypes/OCGObject.m @@ -0,0 +1,23 @@ +// +// OCGObject.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "OCGObject.h" + +@implementation OCGObject + +@end diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.h b/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.h new file mode 100644 index 00000000..be98581d --- /dev/null +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.h @@ -0,0 +1,36 @@ +// +// GAGraphContext.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "GAGraph.h" +#import "GAGraphObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GAGraphContext : NSObject + +// To implement (and use): + +//- (nullable id)provideObjectForData:(GAGraphData)structure type:(GAGraphType)type; +// +//- (void)cacheGraphObject:(id)graphObject; +//- (nullable id)cachedObjectForType:(GAGraphType)type identifier:(GAGraphIdentifier)identifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.m b/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.m new file mode 100644 index 00000000..6d3b7e74 --- /dev/null +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphContext.m @@ -0,0 +1,23 @@ +// +// GAGraphContext.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "GAGraphContext.h" + +@implementation GAGraphContext + +@end diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.h b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.h new file mode 100644 index 00000000..4bc5fdd3 --- /dev/null +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.h @@ -0,0 +1,41 @@ +// +// GAGraphData+Decoder.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 + +// Class property and JSON property names are identical +#define GA_SET(prpName, clName, collectionCl) instance.prpName = [structure objectForKey:@#prpName ofClass:clName.class inCollection:collectionCl required:NO context:context error:outError] +#define GA_SET_REQ(prpName, clName, collectionCl) instance.prpName = [structure objectForKey:@#prpName ofClass:clName.class inCollection:collectionCl required:YES context:context error:outError] + +// Class property and JSON property names differ +#define GA_MAP(prpName, jsonName, clName, collectionCl) instance.prpName = [structure objectForKey:@jsonName ofClass:clName.class inCollection:collectionCl required:NO context:context error:outError] +#define GA_MAP_REQ(prpName, jsonName, clName, collectionCl) instance.prpName = [structure objectForKey:@jsonName ofClass:clName.class inCollection:collectionCl required:YES context:context error:outError] + +NS_ASSUME_NONNULL_BEGIN + +@class GAGraphContext; + +@interface NSDictionary (GAGraphDataDecoder) + +- (nullable id)objectForKey:(NSString *)key ofClass:(Class)class inCollection:(nullable Class)collectionClass required:(BOOL)required context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError; + ++ (nullable id)object:(id)inObject key:(NSString *)key ofClass:(Class)class inCollection:(nullable Class)collectionClass required:(BOOL)required context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m new file mode 100644 index 00000000..c05c6ad3 --- /dev/null +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphData+Decoder.m @@ -0,0 +1,157 @@ +// +// GAGraphData+Decoder.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "GAGraphData+Decoder.h" +#import "NSError+OCError.h" +#import "GAGraphObject.h" +#import "NSDate+OCDateParser.h" +#import "OCLogger.h" + +@implementation NSDictionary (GAGraphDataDecoder) + +- (nullable id)objectForKey:(NSString *)key ofClass:(Class)class inCollection:(nullable Class)collectionClass required:(BOOL)required context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + return ([NSDictionary object:self[key] key:key ofClass:class inCollection:collectionClass required:required context:context error:outError]); +} + ++ (nullable id)object:(id)inObject key:(NSString *)key ofClass:(Class)class inCollection:(nullable Class)collectionClass required:(BOOL)required context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError +{ + id object = inObject; + + if (object != nil) + { + if (collectionClass != Nil) + { + if ([object isKindOfClass:collectionClass] && (class != collectionClass)) + { + NSArray *collectionObject = (NSArray *)object; + NSMutableArray *decodedCollection = [NSMutableArray new]; + + for (id subObject in collectionObject) + { + id decodedObject; + + if ((decodedObject = [self object:subObject key:key ofClass:class inCollection:nil required:required context:context error:outError]) != nil) + { + [decodedCollection addObject:decodedObject]; + } + } + + return (decodedCollection); + } + } + + if ([object isKindOfClass:class]) + { + // Correct type + return (object); + } + else if ([object isKindOfClass:NSString.class] && [class isSubclassOfClass:NSDate.class]) + { + // Parse date + NSDate *decodedDate; + static dispatch_once_t onceToken; + static NSISO8601DateFormatter *dateFormatter; + static NSISO8601DateFormatter *dateFormatter2; + + dispatch_once(&onceToken, ^{ + dateFormatter = [NSISO8601DateFormatter new]; + dateFormatter.formatOptions = NSISO8601DateFormatWithInternetDateTime | + NSISO8601DateFormatWithDashSeparatorInDate | + NSISO8601DateFormatWithColonSeparatorInTime | + NSISO8601DateFormatWithColonSeparatorInTimeZone | + NSISO8601DateFormatWithFractionalSeconds; + + dateFormatter2 = [NSISO8601DateFormatter new]; + dateFormatter2.formatOptions = NSISO8601DateFormatWithInternetDateTime | + NSISO8601DateFormatWithDashSeparatorInDate | + NSISO8601DateFormatWithColonSeparatorInTime | + NSISO8601DateFormatWithColonSeparatorInTimeZone; + }); + + if ((decodedDate = [dateFormatter dateFromString:(NSString *)object]) != nil) + { + // with fractional seconds + return (decodedDate); + } + else if ((decodedDate = [dateFormatter2 dateFromString:(NSString *)object]) != nil) + { + // without fractional seconds + return (decodedDate); + } + else + { + OCLogError(@"GAGraphData+Decoder: error decoding date string %@", object); + } + } + else if ([object isKindOfClass:NSString.class] && [class isSubclassOfClass:NSURL.class]) + { + // Convert string to URL + NSURL *url; + 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) + { + OCLogError(@"GAGraphData+Decoder: converted %@ to URL, but it was a fileURL. Dropped conversion for security considerations.", url); + return (nil); + } + return (url); + } + } + else if ([object isKindOfClass:NSDictionary.class] && [class conformsToProtocol:@protocol(GAGraphObject)]) + { + // Try parsing as GAGraphObject + id decodedObject; + + if ((decodedObject = [((Class)class) decodeGraphData:object context:nil error:outError]) != nil) + { + return (decodedObject); + } + } + + if (outError != NULL) + { + if (*outError == nil) + { + *outError = OCErrorWithDescription(OCErrorInvalidType, ([NSString stringWithFormat:@"Expected type %@ for key %@, got %@.", NSStringFromClass(class), key, NSStringFromClass([object class])])); + } + } + } + + if (required) + { + if (outError != NULL) + { + if (*outError == nil) + { + *outError = OCErrorWithDescription(OCErrorRequiredValueMissing, ([NSString stringWithFormat:@"Required value missing for key %@ (type %@).", key, NSStringFromClass(class)])); + } + } + } + + return (nil); +} + +@end diff --git a/ownCloudSDK/GraphAPI/Parser Support/GAGraphObject.h b/ownCloudSDK/GraphAPI/Parser Support/GAGraphObject.h new file mode 100644 index 00000000..321cbf5f --- /dev/null +++ b/ownCloudSDK/GraphAPI/Parser Support/GAGraphObject.h @@ -0,0 +1,38 @@ +// +// GAGraphObject.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.01.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 "GAGraph.h" +#import "GAGraphData+Decoder.h" + +NS_ASSUME_NONNULL_BEGIN + +@class GAGraphContext; + +@protocol GAGraphObject + +@required ++ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError; + +@optional +@property(readonly,strong,nonatomic) GAGraphType graphType; +@property(readonly,strong,nonatomic) GAGraphIdentifier graphIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/HTTP/OCHTTPTypes.h b/ownCloudSDK/HTTP/OCHTTPTypes.h index b676d65a..d0570ba9 100644 --- a/ownCloudSDK/HTTP/OCHTTPTypes.h +++ b/ownCloudSDK/HTTP/OCHTTPTypes.h @@ -28,7 +28,7 @@ typedef NSDictionary* OCHTTPStaticHeaderFields; typedef NSMutableDictionary* OCHTTPHeaderFields; typedef NSMutableDictionary* OCHTTPRequestParameters; -typedef float OCHTTPRequestPriority; +typedef float OCHTTPRequestPriority; // equivalent to NSURLSessionTaskPriority(Low|Default|High) with values of (0.25|0.5|0.75) respectively typedef NSString* OCHTTPRequestID; typedef NSString* OCHTTPRequestGroupID; diff --git a/ownCloudSDK/HTTP/Pipeline/OCHTTPPipeline.m b/ownCloudSDK/HTTP/Pipeline/OCHTTPPipeline.m index 8edb21f7..9b2a1451 100644 --- a/ownCloudSDK/HTTP/Pipeline/OCHTTPPipeline.m +++ b/ownCloudSDK/HTTP/Pipeline/OCHTTPPipeline.m @@ -829,7 +829,7 @@ - (void)_schedule return (NSOrderedSame); } - return ((task1Priority < task2Priority) ? NSOrderedAscending : NSOrderedDescending); + return ((task1Priority > task2Priority) ? NSOrderedAscending : NSOrderedDescending); // In reverse order, so highest value comes first }; // Sort defaultGroup requests by request.priority @@ -2207,7 +2207,14 @@ - (void)URLSession:(NSURLSession *)session if (task != nil) { - [self evaluateCertificate:certificate forTask:task proceedHandler:proceedHandler]; + if (certificate == nil) + { + proceedHandler(NO, OCError(OCErrorCertificateMissing)); + } + else + { + [self evaluateCertificate:certificate forTask:task proceedHandler:proceedHandler]; + } } else { diff --git a/ownCloudSDK/HTTP/Policy/OCHTTPPolicyBookmark.m b/ownCloudSDK/HTTP/Policy/OCHTTPPolicyBookmark.m index ee606383..d6d21893 100644 --- a/ownCloudSDK/HTTP/Policy/OCHTTPPolicyBookmark.m +++ b/ownCloudSDK/HTTP/Policy/OCHTTPPolicyBookmark.m @@ -91,13 +91,17 @@ - (void)validateCertificate:(nonnull OCCertificate *)certificate forRequest:(non } } -+ (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertificate *)certificate forRequest:(nonnull OCHTTPRequest *)request validationResult:(OCCertificateValidationResult)validationResult validationError:(nonnull NSError *)validationError proceedHandler:(nonnull OCConnectionCertificateProceedHandler)proceedHandler ++ (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertificate *)certificateToValidate forRequest:(nonnull OCHTTPRequest *)request validationResult:(OCCertificateValidationResult)validationResult validationError:(nonnull NSError *)validationError proceedHandler:(nonnull OCConnectionCertificateProceedHandler)proceedHandler { BOOL defaultWouldProceed = ((validationResult == OCCertificateValidationResultPassed) || (validationResult == OCCertificateValidationResultUserAccepted)); BOOL fulfillsBookmarkRequirements = defaultWouldProceed; + BOOL trackNewCertificatesInBookmark = NO; + + NSString *requestHostname = request.hostname; + OCCertificate *storedCertificateForHostname = [bookmark.certificateStore certificateForHostname:requestHostname lastModified:NULL]; // Enforce bookmark certificate - if (bookmark.certificate != nil) + if (storedCertificateForHostname != nil) { BOOL extendedValidationPassed = NO; NSString *extendedValidationRule = nil; @@ -107,7 +111,7 @@ + (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertifica // Check extended validation rule OCCertificateRuleChecker *ruleChecker = nil; - if ((ruleChecker = [OCCertificateRuleChecker ruleWithCertificate:bookmark.certificate newCertificate:certificate rule:extendedValidationRule]) != nil) + if ((ruleChecker = [OCCertificateRuleChecker ruleWithCertificate:storedCertificateForHostname newCertificate:certificateToValidate rule:extendedValidationRule]) != nil) { extendedValidationPassed = [ruleChecker evaluateRule]; } @@ -115,7 +119,7 @@ + (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertifica else { // Check if certificate SHA-256 fingerprints are identical - extendedValidationPassed = [bookmark.certificate isEqual:certificate]; + extendedValidationPassed = [storedCertificateForHostname isEqual:certificateToValidate]; } if (extendedValidationPassed) @@ -129,41 +133,59 @@ + (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertifica fulfillsBookmarkRequirements = NO; - OCLogWarning(@"Certificate %@ does not match bookmark certificate %@. Checking with rule: %@", OCLogPrivate(certificate), OCLogPrivate(bookmark.certificate), OCLogPrivate(renewalAcceptanceRule)); + OCLogWarning(@"Certificate %@ does not match bookmark stored certificate %@. Checking with rule: %@", OCLogPrivate(certificateToValidate), OCLogPrivate(storedCertificateForHostname), OCLogPrivate(renewalAcceptanceRule)); if ((renewalAcceptanceRule = [OCConnection classSettingForOCClassSettingsKey:OCConnectionRenewedCertificateAcceptanceRule]) != nil) { OCCertificateRuleChecker *ruleChecker; - if ((ruleChecker = [OCCertificateRuleChecker ruleWithCertificate:bookmark.certificate newCertificate:certificate rule:renewalAcceptanceRule]) != nil) + if ((ruleChecker = [OCCertificateRuleChecker ruleWithCertificate:storedCertificateForHostname newCertificate:certificateToValidate rule:renewalAcceptanceRule]) != nil) { fulfillsBookmarkRequirements = [ruleChecker evaluateRule]; if (fulfillsBookmarkRequirements) // New certificate fulfills the requirements of the renewed certificate acceptance rule { // Auto-accept successor to user-accepted certificate that also would prompt for confirmation - if ((bookmark.certificate.userAccepted) && (validationResult == OCCertificateValidationResultPromptUser)) + if ((storedCertificateForHostname.userAccepted) && (validationResult == OCCertificateValidationResultPromptUser)) { - [certificate userAccepted:YES withReason:OCCertificateAcceptanceReasonAutoAccepted description:[NSString stringWithFormat:@"Certificate fulfills renewal acceptance rule: %@", ruleChecker.rule]]; + [certificateToValidate userAccepted:YES withReason:OCCertificateAcceptanceReasonAutoAccepted description:[NSString stringWithFormat:@"Certificate fulfills renewal acceptance rule: %@", ruleChecker.rule]]; validationResult = OCCertificateValidationResultUserAccepted; } // Update bookmark certificate - bookmark.certificate = certificate; - bookmark.certificateModificationDate = [NSDate date]; + [bookmark.certificateStore storeCertificate:certificateToValidate forHostname:requestHostname]; [[NSNotificationCenter defaultCenter] postNotificationName:OCBookmarkUpdatedNotification object:bookmark]; [bookmark postCertificateUserApprovalUpdateNotification]; - OCLogWarning(@"Updated stored certificate for bookmark %@ with certificate %@", OCLogPrivate(bookmark), certificate); + OCLogWarning(@"Updated stored certificate for bookmark %@ with certificate %@", OCLogPrivate(bookmark), certificateToValidate); } defaultWouldProceed = fulfillsBookmarkRequirements; } } - OCLogWarning(@"Certificate %@ renewal rule check result: %d", OCLogPrivate(certificate), fulfillsBookmarkRequirements); + OCLogWarning(@"Certificate %@ renewal rule check result: %d", OCLogPrivate(certificateToValidate), fulfillsBookmarkRequirements); + } + } + else if (requestHostname != nil) + { + // No certificate is stored yet in the bookmark for this domain + NSString *trackingRule; + + if ((trackingRule = [OCConnection classSettingForOCClassSettingsKey:OCConnectionAssociatedCertificatesTrackingRule]) != nil) + { + @try { + NSPredicate *predicate = [NSPredicate predicateWithFormat:trackingRule, nil]; + + trackNewCertificatesInBookmark = [predicate evaluateWithObject:nil substitutionVariables:@{ + @"hostname" : requestHostname, + @"certificate" : certificateToValidate + }]; + } @catch (NSException *exception) { + OCLogError(@"evaluation of associated certificate tracking rule %@ threw an exception: %@", trackingRule, exception); + } } } @@ -191,16 +213,15 @@ + (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertifica OCErrorAddDateFromResponse(errorIssue, request.httpResponse); // Embed issue - OCIssue *issue = [OCIssue issueForCertificate:certificate validationResult:validationResult url:request.url level:OCIssueLevelWarning issueHandler:^(OCIssue *issue, OCIssueDecision decision) { + OCIssue *issue = [OCIssue issueForCertificate:certificateToValidate validationResult:validationResult url:request.url level:OCIssueLevelWarning issueHandler:^(OCIssue *issue, OCIssueDecision decision) { if (decision == OCIssueDecisionApprove) { if (changeUserAccepted) { - [certificate userAccepted:YES withReason:OCCertificateAcceptanceReasonUserAccepted description:nil]; + [certificateToValidate userAccepted:YES withReason:OCCertificateAcceptanceReasonUserAccepted description:nil]; } - bookmark.certificate = certificate; - bookmark.certificateModificationDate = [NSDate date]; + [bookmark.certificateStore storeCertificate:certificateToValidate forHostname:requestHostname]; [[NSNotificationCenter defaultCenter] postNotificationName:OCBookmarkUpdatedNotification object:bookmark]; [bookmark postCertificateUserApprovalUpdateNotification]; @@ -213,13 +234,22 @@ + (void)validateBookmark:(OCBookmark *)bookmark certificate:(nonnull OCCertifica if (validationResult == OCCertificateValidationResultPassed) { - issue.localizedDescription = [NSString stringWithFormat:OCLocalizedString(@"The certificate for %@ passes TLS validation but doesn't pass the acceptance rule to replace the certificate for %@.", nil), certificate.hostName, bookmark.certificate.hostName]; + issue.localizedDescription = [NSString stringWithFormat:OCLocalizedString(@"The certificate for %@ passes TLS validation but doesn't pass the acceptance rule to replace the certificate for %@.", nil), certificateToValidate.hostName, storedCertificateForHostname.hostName]; } } errorIssue = [errorIssue errorByEmbeddingIssue:issue]; } + if (doProceed && trackNewCertificatesInBookmark) + { + // Add certificate to bookmark to track changes to it + [bookmark.certificateStore storeCertificate:certificateToValidate forHostname:requestHostname]; + + [[NSNotificationCenter defaultCenter] postNotificationName:OCBookmarkUpdatedNotification object:bookmark]; + [bookmark postCertificateUserApprovalUpdateNotification]; + } + proceedHandler(doProceed, errorIssue); } } diff --git a/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.h b/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.h new file mode 100644 index 00000000..b543abb8 --- /dev/null +++ b/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.h @@ -0,0 +1,29 @@ +// +// NSDictionary+OCFormEncoding.h +// ownCloudSDK +// +// Created by Felix Schwarz on 19.09.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface NSDictionary (OCFormEncoding) + +- (nullable NSData *)urlFormEncodedData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.m b/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.m new file mode 100644 index 00000000..a99b1e4b --- /dev/null +++ b/ownCloudSDK/HTTP/Request/NSDictionary+OCFormEncoding.m @@ -0,0 +1,40 @@ +// +// NSDictionary+OCFormEncoding.m +// ownCloudSDK +// +// Created by Felix Schwarz on 19.09.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 "NSDictionary+OCFormEncoding.h" + +@implementation NSDictionary (OCFormEncoding) + +- (nullable NSData *)urlFormEncodedData +{ + // Encode dictionary as parameters for POST / PUT HTTP request body data + NSMutableArray *queryItems = [NSMutableArray array]; + NSURLComponents *urlComponents = [[NSURLComponents alloc] init]; + + [self enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSString *value, BOOL * _Nonnull stop) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:name value:value]]; + }]; + + urlComponents.queryItems = queryItems; + + // NSURLComponents.percentEncodedQuery will NOT escape "+" as "%2B" because Apple argues that's not what's in the standard and causes issues with normalization + // (source: http://www.openradar.me/24076063) + return ([[[urlComponents percentEncodedQuery] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"] dataUsingEncoding:NSUTF8StringEncoding]); +} + +@end diff --git a/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.h b/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.h index c1e3551b..0e73cebf 100644 --- a/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.h +++ b/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.h @@ -22,6 +22,7 @@ #import "OCTypes.h" #import "OCItem.h" #import "OCUser.h" +#import "OCDrive.h" typedef NS_ENUM(NSInteger, OCPropfindDepth) { OCPropfindDepthInfinity = -1, @@ -50,7 +51,7 @@ typedef NS_ENUM(NSInteger, OCPropfindDepth) { - (OCXMLNode *)xmlRequestPropAttribute; -- (NSArray *)responseItemsForBasePath:(NSString *)basePath reuseUsersByID:(NSMutableDictionary *)usersByUserID withErrors:(NSArray **)errors; +- (NSArray *)responseItemsForBasePath:(NSString *)basePath reuseUsersByID:(NSMutableDictionary *)usersByUserID driveID:(OCDriveID)driveID withErrors:(NSArray **)errors; - (NSDictionary *)multistatusResponsesForBasePath:(NSString *)basePath; @end diff --git a/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.m b/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.m index cd4b377f..c4c25e81 100644 --- a/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.m +++ b/ownCloudSDK/HTTP/Request/OCHTTPDAVRequest.m @@ -38,6 +38,7 @@ + (instancetype)propfindRequestWithURL:(NSURL *)url depth:(OCPropfindDepth)depth ]] ]; [request setValue:@"application/xml" forHeaderField:OCHTTPHeaderFieldNameContentType]; + [request setValue:@"return=minimal" forHeaderField:OCHTTPHeaderFieldNamePrefer]; // Reduce the HTTP body size by omitting the 404 parts (https://datatracker.ietf.org/doc/html/rfc8144#section-2.1, https://github.com/cs3org/reva/pull/3222) [request setValue:((depth == OCPropfindDepthInfinity) ? @"infinity" : [NSString stringWithFormat:@"%lu", (unsigned long)depth]) forHeaderField:OCHTTPHeaderFieldNameDepth]; return (request); @@ -90,7 +91,7 @@ - (NSData *)bodyData return (_bodyData); } -- (NSArray *)responseItemsForBasePath:(NSString *)basePath reuseUsersByID:(NSMutableDictionary *)usersByUserID withErrors:(NSArray **)errors +- (NSArray *)responseItemsForBasePath:(NSString *)basePath reuseUsersByID:(NSMutableDictionary *)usersByUserID driveID:(nullable OCDriveID)driveID withErrors:(NSArray **)errors { NSArray *responseItems = nil; NSData *responseData = self.httpResponse.bodyData; @@ -125,6 +126,14 @@ - (NSData *)bodyData @synchronized(self) { responseItems = _parseResultItems = parser.parsedObjects; + + if (driveID != nil) + { + for (OCItem *item in responseItems) + { + item.driveID = driveID; + } + } } } diff --git a/ownCloudSDK/HTTP/Request/OCHTTPRequest.h b/ownCloudSDK/HTTP/Request/OCHTTPRequest.h index c025d436..30a0dac7 100644 --- a/ownCloudSDK/HTTP/Request/OCHTTPRequest.h +++ b/ownCloudSDK/HTTP/Request/OCHTTPRequest.h @@ -75,6 +75,7 @@ typedef NSDictionary* OCHTTPRequestResumeInfo; @property(strong) NSURL *url; //!< The URL to request @property(strong) NSURL *effectiveURL; //!< The URL that's effectively requested (generated by -prepareForScheduling) +@property(readonly,nonatomic) NSString *hostname; //!< The hostname the request targets (used in certificate contexts) @property(strong) OCHTTPRequestParameters parameters; //!< The parameters to send as part of the URL (GET) or as the request's body (POST) @property(strong) OCHTTPHeaderFields headerFields;//!< The HTTP headerfields to send alongside the request @property(strong,nonatomic) NSData *bodyData; //!< The HTTP body to send (as body data). Ignored / overwritten if .method is POST and .parameters has key-value pairs. @@ -108,7 +109,7 @@ typedef NSDictionary* OCHTTPRequestResumeInfo; @property(strong) NSNumber *customTimeout; //!< Custom timeout in seconds for request. -@property(assign) OCHTTPRequestPriority priority; //!< Priority of the request from 0.0 (lowest priority) to 1.0 (highest priority). Defaults to NSURLSessionTaskPriorityDefault (= 0.5). +@property(assign) OCHTTPRequestPriority priority; //!< Priority of the request from 0.0 (lowest priority) to 1.0 (highest priority). Defaults to NSURLSessionTaskPriorityDefault (= 0.5). @property(strong) OCHTTPRequestGroupID groupID; //!< ID of the Group the request belongs to (if any). Requests in the same group are executed serially, whereas requests that belong to no group are executed as soon as possible. @property(copy) OCHTTPRequestObserver requestObserver; //!< OCHTTPRequestObserver block called as the request encounters various events @@ -148,7 +149,7 @@ typedef NSDictionary* OCHTTPRequestResumeInfo; - (NSString *)valueForHeaderField:(OCHTTPHeaderFieldName)headerField; - (void)setValue:(NSString *)value forHeaderField:(OCHTTPHeaderFieldName)headerField; -- (void)addHeaderFields:(NSDictionary *)headerFields; +- (void)addHeaderFields:(OCHTTPStaticHeaderFields)headerFields; - (OCHTTPRequestStatusPolicy)policyForStatus:(OCHTTPStatusCode)statusCode; - (void)setPolicy:(OCHTTPRequestStatusPolicy)policy forStatus:(OCHTTPStatusCode)statusCode; @@ -187,8 +188,10 @@ extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameOriginalRequestID; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameContentType; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameContentLength; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameDepth; +extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameETag; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameDestination; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameOverwrite; +extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNamePrefer; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameIfMatch; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameIfNoneMatch; extern OCHTTPHeaderFieldName OCHTTPHeaderFieldNameUserAgent; diff --git a/ownCloudSDK/HTTP/Request/OCHTTPRequest.m b/ownCloudSDK/HTTP/Request/OCHTTPRequest.m index 8a0d4ca5..38f45333 100644 --- a/ownCloudSDK/HTTP/Request/OCHTTPRequest.m +++ b/ownCloudSDK/HTTP/Request/OCHTTPRequest.m @@ -22,6 +22,7 @@ #import "NSProgress+OCExtensions.h" #import "OCMacros.h" #import "OCConnection.h" +#import "NSDictionary+OCFormEncoding.h" @implementation OCHTTPRequest @@ -60,10 +61,6 @@ - (instancetype)init return(self); } -- (void)dealloc -{ -} - + (instancetype)requestWithURL:(NSURL *)url { OCHTTPRequest *request = [self new]; @@ -178,7 +175,7 @@ - (void)setValue:(NSString *)value forHeaderField:(NSString *)headerField } } -- (void)addHeaderFields:(NSDictionary *)headerFields +- (void)addHeaderFields:(OCHTTPStaticHeaderFields)headerFields { if (headerFields != nil) { @@ -238,6 +235,18 @@ - (OCHTTPRequestRedirectPolicy)redirectPolicy return (_redirectPolicy); } +- (NSString *)hostname +{ + NSString *hostname = self.effectiveURL.host; + + if (hostname == nil) + { + hostname = self.url.host; + } + + return (hostname); +} + #pragma mark - Queue scheduling support - (void)prepareForScheduling { @@ -247,18 +256,7 @@ - (void)prepareForScheduling if ([_method isEqual:OCHTTPMethodPOST] || [_method isEqual:OCHTTPMethodPUT]) { // POST and PUT methods: generate body from parameters - NSMutableArray *queryItems = [NSMutableArray array]; - NSURLComponents *urlComponents = [[NSURLComponents alloc] init]; - - [_parameters enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSString *value, BOOL * _Nonnull stop) { - [queryItems addObject:[NSURLQueryItem queryItemWithName:name value:value]]; - }]; - - urlComponents.queryItems = queryItems; - - // NSURLComponents.percentEncodedQuery will NOT escape "+" as "%2B" because Apple argues that's not what's in the standard and causes issues with normalization - // (source: http://www.openradar.me/24076063) - self.bodyData = [[[urlComponents percentEncodedQuery] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"] dataUsingEncoding:NSUTF8StringEncoding]; + self.bodyData = [_parameters urlFormEncodedData]; if (_headerFields[OCHTTPHeaderFieldNameContentType] == nil) { @@ -407,6 +405,13 @@ + (NSString *)bodyDescriptionForURL:(NSURL *)url data:(NSData *)data headers:(NS return (FormatReadableData([NSData dataWithContentsOfURL:url])); } + NSNumber *fileSize = nil; + + if ([url getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]) + { + return ([NSString stringWithFormat:@"%@[Contents from %@ (%ld bytes)]", (prefixed ? @"[body] " : @""), url.path, fileSize.integerValue]); + } + return ([NSString stringWithFormat:@"%@[Contents from %@]", (prefixed ? @"[body] " : @""), url.path]); } @@ -694,8 +699,10 @@ - (void)encodeWithCoder:(NSCoder *)coder OCHTTPHeaderFieldName OCHTTPHeaderFieldNameContentType = @"Content-Type"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameContentLength = @"Content-Length"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameDepth = @"Depth"; +OCHTTPHeaderFieldName OCHTTPHeaderFieldNameETag = @"ETag"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameDestination = @"Destination"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameOverwrite = @"Overwrite"; +OCHTTPHeaderFieldName OCHTTPHeaderFieldNamePrefer = @"Prefer"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameIfMatch = @"If-Match"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameIfNoneMatch = @"If-None-Match"; OCHTTPHeaderFieldName OCHTTPHeaderFieldNameUserAgent = @"User-Agent"; 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/HTTP/Status/NSError+OCHTTPStatus.h b/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.h index 0ce73052..f01f137f 100644 --- a/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.h +++ b/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.h @@ -24,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSError (OCHTTPStatus) - (nullable OCHTTPStatus *)HTTPStatus; +- (BOOL)isHTTPStatusErrorWithCode:(OCHTTPStatusCode)code; @end diff --git a/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.m b/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.m index 9b3b8202..60b0e550 100644 --- a/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.m +++ b/ownCloudSDK/HTTP/Status/NSError+OCHTTPStatus.m @@ -31,4 +31,14 @@ - (nullable OCHTTPStatus *)HTTPStatus return (nil); } +- (BOOL)isHTTPStatusErrorWithCode:(OCHTTPStatusCode)code; +{ + if ([self.domain isEqualToString:OCHTTPStatusErrorDomain]) + { + return (code == self.code); + } + + return (NO); +} + @end diff --git a/ownCloudSDK/HTTP/Status/OCHTTPStatus.h b/ownCloudSDK/HTTP/Status/OCHTTPStatus.h index 65d9af17..ac15ca16 100644 --- a/ownCloudSDK/HTTP/Status/OCHTTPStatus.h +++ b/ownCloudSDK/HTTP/Status/OCHTTPStatus.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSUInteger, OCHTTPStatusCode) // Redirection (3xx) OCHTTPStatusCodeMOVED_PERMANENTLY = 301, OCHTTPStatusCodeMOVED_TEMPORARILY = 302, + OCHTTPStatusCodeNOT_MODIFIED = 304, OCHTTPStatusCodeTEMPORARY_REDIRECT = 307, OCHTTPStatusCodePERMANENT_REDIRECT = 308, @@ -43,6 +44,7 @@ typedef NS_ENUM(NSUInteger, OCHTTPStatusCode) OCHTTPStatusCodePRECONDITION_FAILED = 412, OCHTTPStatusCodePAYLOAD_TOO_LARGE = 413, OCHTTPStatusCodeLOCKED = 423, + OCHTTPStatusCodeTOO_EARLY = 425, // Server Error (5xx) OCHTTPStatusCodeINTERNAL_SERVER_ERROR = 500, diff --git a/ownCloudSDK/HTTP/Status/OCHTTPStatus.m b/ownCloudSDK/HTTP/Status/OCHTTPStatus.m index b7b74bbb..75b4038d 100644 --- a/ownCloudSDK/HTTP/Status/OCHTTPStatus.m +++ b/ownCloudSDK/HTTP/Status/OCHTTPStatus.m @@ -84,6 +84,10 @@ - (NSString *)name return (@"MOVED TEMPORARILY"); break; + case OCHTTPStatusCodeNOT_MODIFIED: + return (@"NOT MODIFIED"); + break; + case OCHTTPStatusCodeTEMPORARY_REDIRECT: return (@"TEMPORARY REDIRECT"); break; @@ -147,6 +151,10 @@ - (NSString *)name case OCHTTPStatusCodeINSUFFICIENT_STORAGE: return (@"INSUFFICIENT STORAGE"); break; + + case OCHTTPStatusCodeTOO_EARLY: + return (@"TOO EARLY"); + break; } return (@(_code).stringValue); diff --git a/ownCloudSDK/Share/OCGroup.h b/ownCloudSDK/Identity/OCGroup.h similarity index 100% rename from ownCloudSDK/Share/OCGroup.h rename to ownCloudSDK/Identity/OCGroup.h diff --git a/ownCloudSDK/Share/OCGroup.m b/ownCloudSDK/Identity/OCGroup.m similarity index 100% rename from ownCloudSDK/Share/OCGroup.m rename to ownCloudSDK/Identity/OCGroup.m diff --git a/ownCloudSDK/Identity/OCIdentity+DataItem.h b/ownCloudSDK/Identity/OCIdentity+DataItem.h new file mode 100644 index 00000000..25c5b2eb --- /dev/null +++ b/ownCloudSDK/Identity/OCIdentity+DataItem.h @@ -0,0 +1,28 @@ +// +// OCIdentity+DataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 18.04.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCIdentity.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCIdentity (DataItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Identity/OCIdentity+DataItem.m b/ownCloudSDK/Identity/OCIdentity+DataItem.m new file mode 100644 index 00000000..3b13e341 --- /dev/null +++ b/ownCloudSDK/Identity/OCIdentity+DataItem.m @@ -0,0 +1,38 @@ +// +// OCIdentity+DataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 18.04.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCIdentity+DataItem.h" + +@implementation OCIdentity (DataItem) + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeIdentity); +} + +- (OCDataItemReference)dataItemReference +{ + return ([[NSString alloc] initWithFormat:@"%lu:%@", (unsigned long)self.type, self.identifier]); +} + +- (OCDataItemVersion)dataItemVersion +{ + return ([NSString stringWithFormat:@"%@%@%@", self.identifier, self.displayName, self.searchResultName]); +} + +@end diff --git a/ownCloudSDK/Share/OCRecipient.h b/ownCloudSDK/Identity/OCIdentity.h similarity index 85% rename from ownCloudSDK/Share/OCRecipient.h rename to ownCloudSDK/Identity/OCIdentity.h index d3d46e22..cc65e208 100644 --- a/ownCloudSDK/Share/OCRecipient.h +++ b/ownCloudSDK/Identity/OCIdentity.h @@ -1,5 +1,5 @@ // -// OCRecipient.h +// OCIdentity.h // ownCloudSDK // // Created by Felix Schwarz on 01.03.19. @@ -10,7 +10,7 @@ * Copyright (C) 2019, ownCloud GmbH. * * This code is covered by the GNU Public License Version 3. - *S + * * 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 . * @@ -33,7 +33,7 @@ typedef NS_ENUM(NSUInteger, OCRecipientMatchType) { OCRecipientMatchTypeAdditional //!< Additional match from search }; -@interface OCRecipient : NSObject +@interface OCIdentity : NSObject @property(assign) OCRecipientType type; @@ -47,11 +47,15 @@ typedef NS_ENUM(NSUInteger, OCRecipientMatchType) { @property(assign) OCRecipientMatchType matchType; -+ (instancetype)recipientWithUser:(OCUser *)user; -+ (instancetype)recipientWithGroup:(OCGroup *)group; ++ (instancetype)identityWithUser:(OCUser *)user; ++ (instancetype)identityWithGroup:(OCGroup *)group; - (instancetype)withSearchResultName:(nullable NSString *)searchResultName; @end +// NSCoding compatibility shim following OCRecipient -> OCIdentity refactoring +@interface OCRecipient : OCIdentity +@end + NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Share/OCRecipient.m b/ownCloudSDK/Identity/OCIdentity.m similarity index 90% rename from ownCloudSDK/Share/OCRecipient.m rename to ownCloudSDK/Identity/OCIdentity.m index a25e8292..7e7ca889 100644 --- a/ownCloudSDK/Share/OCRecipient.m +++ b/ownCloudSDK/Identity/OCIdentity.m @@ -1,5 +1,5 @@ // -// OCRecipient.m +// OCIdentity.m // ownCloudSDK // // Created by Felix Schwarz on 01.03.19. @@ -16,17 +16,17 @@ * */ -#import "OCRecipient.h" +#import "OCIdentity.h" #import "OCMacros.h" -@implementation OCRecipient +@implementation OCIdentity @dynamic identifier; @dynamic displayName; -+ (instancetype)recipientWithUser:(OCUser *)user ++ (instancetype)identityWithUser:(OCUser *)user { - OCRecipient *recipient = [self new]; + OCIdentity *recipient = [self new]; recipient.type = OCRecipientTypeUser; recipient.user = user; @@ -34,9 +34,9 @@ + (instancetype)recipientWithUser:(OCUser *)user return (recipient); } -+ (instancetype)recipientWithGroup:(OCGroup *)group ++ (instancetype)identityWithGroup:(OCGroup *)group { - OCRecipient *recipient = [self new]; + OCIdentity *recipient = [self new]; recipient.type = OCRecipientTypeGroup; recipient.group = group; @@ -91,7 +91,7 @@ - (NSUInteger)hash - (BOOL)isEqual:(id)object { - OCRecipient *otherRecipient = OCTypedCast(object, OCRecipient); + OCIdentity *otherRecipient = OCTypedCast(object, OCIdentity); if (otherRecipient != nil) { @@ -106,7 +106,7 @@ - (BOOL)isEqual:(id)object #pragma mark - Copying - (id)copyWithZone:(NSZone *)zone { - OCRecipient *recipient = [OCRecipient new]; + OCIdentity *recipient = [OCIdentity new]; recipient->_type = _type; recipient->_user = _user; @@ -172,3 +172,7 @@ - (NSString *)description } @end + +// NSCoding compatibility shim following OCRecipient -> OCIdentity refactoring +@implementation OCRecipient +@end diff --git a/ownCloudSDK/Share/OCUser.h b/ownCloudSDK/Identity/OCUser.h similarity index 81% rename from ownCloudSDK/Share/OCUser.h rename to ownCloudSDK/Identity/OCUser.h index c05830e4..64214c4c 100644 --- a/ownCloudSDK/Share/OCUser.h +++ b/ownCloudSDK/Identity/OCUser.h @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN +typedef NSString* OCUserIdentifier; //!< Internal SDK unique identifier for the user + @interface OCUser : NSObject { UIImage *_avatar; @@ -36,9 +38,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,readonly) NSString *remoteUserName; //!< Returns the part before the @ sign for usernames containing an @ sign (nil otherwise) @property(nullable,readonly) NSString *remoteHost; //!< Returns the part after the @ sign for usernames containing an @ sign (nil otherwise) -@property(nullable,strong) NSData *avatarData; //!< Image data for the avatar of the user (or nil if none is available) +@property(nullable,readonly) OCUserIdentifier userIdentifier; //!< Unique SDK internal identifier for the user +@property(nullable,readonly) NSString *localizedInitials; //!< Returns localized initials for user -@property(nullable,readonly,nonatomic) UIImage *avatar; //!< Avatar for the user (or nil if none is available) - auto-generated from avatarData, not archived ++ (nullable NSString *)localizedInitialsForName:(NSString *)name; + (instancetype)userWithUserName:(nullable NSString *)userName displayName:(nullable NSString *)displayName; + (instancetype)userWithUserName:(nullable NSString *)userName displayName:(nullable NSString *)displayName isRemote:(BOOL)isRemote; diff --git a/ownCloudSDK/Share/OCUser.m b/ownCloudSDK/Identity/OCUser.m similarity index 65% rename from ownCloudSDK/Share/OCUser.m rename to ownCloudSDK/Identity/OCUser.m index 55612810..139ad556 100644 --- a/ownCloudSDK/Share/OCUser.m +++ b/ownCloudSDK/Identity/OCUser.m @@ -18,12 +18,12 @@ #import "OCUser.h" #import "OCMacros.h" +#import "OCLogger.h" @implementation OCUser @synthesize userName = _userName; @synthesize displayName = _displayName; -@synthesize avatarData = _avatarData; @dynamic isRemote; @dynamic remoteHost; @@ -100,14 +100,82 @@ - (NSString *)remoteHost return (nil); } -- (UIImage *)avatar +- (OCUserIdentifier)userIdentifier { - if ((_avatar == nil) && (_avatarData != nil)) + return ([NSString stringWithFormat:@"%@:%d", self.userName, self.isRemote]); +} + ++ (NSPersonNameComponentsFormatter *)localizedInitialsFormatter +{ + static dispatch_once_t onceToken; + static NSPersonNameComponentsFormatter *formatter; + dispatch_once(&onceToken, ^{ + formatter = [NSPersonNameComponentsFormatter new]; + formatter.style= NSPersonNameComponentsFormatterStyleAbbreviated; + }); + + return (formatter); +} + ++ (NSString *)localizedInitialsForName:(NSString *)name +{ + if (name.length > 0) + { + NSString *localizedInitials = nil; + + @try { + NSPersonNameComponentsFormatter *localizedFormatter = OCUser.localizedInitialsFormatter; + NSPersonNameComponents *nameComponents; + + if ((nameComponents = [localizedFormatter personNameComponentsFromString:name]) != nil) + { + localizedInitials = [localizedFormatter stringFromPersonNameComponents:nameComponents]; + } + } @catch (NSException *exception) { + OCLogDebug(@"Exception asking the OS for localized initials for %@: %@", name, exception); + } + + if (localizedInitials == nil) + { + // Simple fallback algorithm taking the first letter of each word + NSArray *nameParts = [name componentsSeparatedByString:@" "]; + NSMutableString *initials = [NSMutableString new]; + + for (NSString *namePart in nameParts) + { + if (namePart.length > 0) + { + [initials appendString:[[namePart substringToIndex:1] uppercaseString]]; + } + } + + if (initials.length > 0) + { + localizedInitials = initials; + } + } + + return (localizedInitials); + } + + return (nil); +} + +- (NSString *)localizedInitials +{ + NSString *localizedInitials = nil; + + if (self.displayName.length > 0) + { + localizedInitials = [OCUser localizedInitialsForName:self.displayName]; + } + + if (localizedInitials == nil) { - _avatar = [UIImage imageWithData:self.avatarData]; + localizedInitials = [OCUser localizedInitialsForName:self.userName]; } - return (_avatar); + return (localizedInitials); } #pragma mark - Comparison @@ -124,7 +192,7 @@ - (BOOL)isEqual:(id)object { #define compareVar(var) ((otherUser->var == var) || [otherUser->var isEqual:var]) - return (compareVar(_userName) && compareVar(_displayName) && compareVar(_emailAddress) && compareVar(_avatarData) && compareVar(_forceIsRemote)); + return (compareVar(_userName) && compareVar(_displayName) && compareVar(_emailAddress) && compareVar(_forceIsRemote)); } return (NO); @@ -138,8 +206,6 @@ - (id)copyWithZone:(NSZone *)zone user->_userName = _userName; user->_displayName = _displayName; user->_emailAddress = _emailAddress; - user->_avatarData = _avatarData; - user->_avatar = _avatar; user->_forceIsRemote = _forceIsRemote; return (user); @@ -158,7 +224,6 @@ - (instancetype)initWithCoder:(NSCoder *)decoder self.userName = [decoder decodeObjectOfClass:[NSString class] forKey:@"userName"]; self.displayName = [decoder decodeObjectOfClass:[NSString class] forKey:@"displayName"]; self.emailAddress = [decoder decodeObjectOfClass:[NSString class] forKey:@"emailAddress"]; - self.avatarData = [decoder decodeObjectOfClass:[NSData class] forKey:@"avatarData"]; _forceIsRemote = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"forceIsRemote"]; } @@ -170,14 +235,13 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:self.userName forKey:@"userName"]; [coder encodeObject:self.displayName forKey:@"displayName"]; [coder encodeObject:self.emailAddress forKey:@"emailAddress"]; - [coder encodeObject:self.avatarData forKey:@"avatarData"]; [coder encodeObject:_forceIsRemote forKey:@"forceIsRemote"]; } #pragma mark - Description - (NSString *)description { - return ([NSString stringWithFormat:@"<%@: %p, userName: %@, displayName: %@%@%@>", NSStringFromClass(self.class), self, _userName, _displayName, ((_emailAddress!=nil) ? [NSString stringWithFormat:@", emailAddress: [%@]",_emailAddress] : @""), ((self.avatarData!=nil) ? @", avatarData" : @"")]); + return ([NSString stringWithFormat:@"<%@: %p, userName: %@, displayName: %@%@>", NSStringFromClass(self.class), self, _userName, _displayName, ((_emailAddress!=nil) ? [NSString stringWithFormat:@", emailAddress: [%@]",_emailAddress] : @"")]); } @end diff --git a/ownCloudSDK/Image Types/Avatars/OCAvatar.h b/ownCloudSDK/Image Types/Avatars/OCAvatar.h new file mode 100644 index 00000000..eb803e04 --- /dev/null +++ b/ownCloudSDK/Image Types/Avatars/OCAvatar.h @@ -0,0 +1,37 @@ +// +// OCAvatar.h +// ownCloudSDK +// +// Created by Felix Schwarz on 29.09.20. +// 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 "OCImage.h" +#import "OCTypes.h" +#import "OCUser.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCAvatar : OCImage + +@property(class,nonatomic,readonly) CGSize defaultSize; + +@property(strong,nullable) OCUserIdentifier userIdentifier; +@property(strong,nullable) OCFileETag eTag; + +@property(strong,nullable) NSDate *timestamp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Image Types/Avatars/OCAvatar.m b/ownCloudSDK/Image Types/Avatars/OCAvatar.m new file mode 100644 index 00000000..cd27e0ea --- /dev/null +++ b/ownCloudSDK/Image Types/Avatars/OCAvatar.m @@ -0,0 +1,55 @@ +// +// OCAvatar.m +// ownCloudSDK +// +// Created by Felix Schwarz on 29.09.20. +// 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 "OCAvatar.h" + +@implementation OCAvatar + ++ (CGSize)defaultSize +{ + return (CGSizeMake(128,128)); +} + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + + [coder encodeObject:_userIdentifier forKey:@"userIdentifier"]; + [coder encodeObject:_eTag forKey:@"eTag"]; + [coder encodeObject:_timestamp forKey:@"timestamp"]; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super initWithCoder:decoder]) != nil) + { + _userIdentifier = [decoder decodeObjectOfClass:NSString.class forKey:@"userIdentifier"]; + _eTag = [decoder decodeObjectOfClass:NSString.class forKey:@"eTag"]; + _timestamp = [decoder decodeObjectOfClass:NSDate.class forKey:@"timestamp"]; + } + + return (self); +} + +@end diff --git a/ownCloudSDK/Image Types/Symbols/OCSymbol.h b/ownCloudSDK/Image Types/Symbols/OCSymbol.h new file mode 100644 index 00000000..6365593e --- /dev/null +++ b/ownCloudSDK/Image Types/Symbols/OCSymbol.h @@ -0,0 +1,31 @@ +// +// OCSymbol.h +// ownCloudSDK +// +// Created by Felix Schwarz on 13.10.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 + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCSymbolName; //!< Name of a symbol to load. Abstraction through OCSymbol allows to add caching (if necessary) and customization options (such as weight, color, or rendering bundled sources) in a single string later on. + +@interface OCSymbol : NSObject + ++ (nullable UIImage *)iconForSymbolName:(nullable OCSymbolName)symbolName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Image Types/Symbols/OCSymbol.m b/ownCloudSDK/Image Types/Symbols/OCSymbol.m new file mode 100644 index 00000000..2797b17f --- /dev/null +++ b/ownCloudSDK/Image Types/Symbols/OCSymbol.m @@ -0,0 +1,29 @@ +// +// OCSymbol.m +// ownCloudSDK +// +// Created by Felix Schwarz on 13.10.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 "OCSymbol.h" + +@implementation OCSymbol + ++ (nullable UIImage *)iconForSymbolName:(OCSymbolName)symbolName +{ + if (symbolName == nil) { return (nil); } + return ([[UIImage systemImageNamed:symbolName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]); +} + +@end diff --git a/ownCloudSDK/Issues/OCIssue.h b/ownCloudSDK/Issues/OCIssue.h index 1a8b30fd..7c9b8eda 100644 --- a/ownCloudSDK/Issues/OCIssue.h +++ b/ownCloudSDK/Issues/OCIssue.h @@ -29,7 +29,7 @@ typedef NS_ENUM(NSUInteger, OCIssueLevel) OCIssueLevelInformal, //!< Issue that is purely informal and requires no user action OCIssueLevelWarning, //!< Issue that can ultimately be resolved, but for which the user should be prompted OCIssueLevelError //!< Issue that can't be resolved -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCIssueType) { @@ -41,14 +41,14 @@ typedef NS_ENUM(NSUInteger, OCIssueType) OCIssueTypeGeneric, OCIssueTypeError -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSUInteger, OCIssueDecision) { OCIssueDecisionNone, OCIssueDecisionReject, OCIssueDecisionApprove -}; +} __attribute__((enum_extensibility(closed))); typedef void(^OCIssueHandler)(OCIssue *issue, OCIssueDecision decision); diff --git a/ownCloudSDK/Issues/OCIssueChoice.h b/ownCloudSDK/Issues/OCIssueChoice.h index 2da32b73..0eb32efc 100644 --- a/ownCloudSDK/Issues/OCIssueChoice.h +++ b/ownCloudSDK/Issues/OCIssueChoice.h @@ -29,7 +29,7 @@ typedef NS_ENUM(NSInteger,OCIssueChoiceType) OCIssueChoiceTypeRegular, OCIssueChoiceTypeDefault, OCIssueChoiceTypeDestructive -}; +} __attribute__((enum_extensibility(closed))); @interface OCIssueChoice : NSObject diff --git a/ownCloudSDK/Item/Images/OCImage.h b/ownCloudSDK/Item/Images/OCImage.h index be84e5fc..32459fe5 100644 --- a/ownCloudSDK/Item/Images/OCImage.h +++ b/ownCloudSDK/Item/Images/OCImage.h @@ -18,6 +18,15 @@ #import +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, OCImageFillMode) +{ + OCImageFillModeUnknown, + OCImageFillModeScaleToFit, + OCImageFillModeScaleToFill +}; + @interface OCImage : NSObject { NSURL *_url; @@ -29,14 +38,26 @@ NSRecursiveLock *_processingLock; } -@property(strong) NSURL *url; //!< URL of the image file -@property(strong,nonatomic) NSData *data; //!< Binary data of the image. If none is present, tries to synchronously load the data from the URL. +@property(strong,nullable) NSURL *url; //!< URL of the image file +@property(strong,nullable,nonatomic) NSData *data; //!< Binary data of the image. If none is present, tries to synchronously load the data from the URL. @property(strong) NSString *mimeType; //!< MIME-Type of the image -@property(strong,nonatomic) UIImage *image; //!< The decoded image. Attention: if not decoded already, decodes .data synchronously. For best performance, use -requestImageWithCompletionHandler: +@property(assign) OCImageFillMode fillMode; //!< Fill mode of the image, defaults to unkonwn + +#pragma mark - Basic decoding +@property(strong,nullable,nonatomic) UIImage *image; //!< The decoded image. Attention: if not decoded already, decodes .data synchronously. For best performance, use -requestImageWithCompletionHandler: -- (UIImage *)decodeImage; //!< Called by .image if data hasn't yet been decoded. +- (nullable UIImage *)decodeImage; //!< Called by .image if data hasn't yet been decoded. -- (BOOL)requestImageWithCompletionHandler:(void(^)(OCImage *ocImage, NSError *error, UIImage *image))completionHandler; //!< Returns YES if the image is already available and the completionHandler has already been called. Returns NO if the image is not yet available, will call completionHandler when it is. +- (BOOL)requestImageWithCompletionHandler:(void(^)(OCImage *ocImage, NSError * _Nullable error, UIImage * _Nullable image))completionHandler; //!< Returns YES if the image is already available and the completionHandler has already been called. Returns NO if the image is not yet available, will call completionHandler when it is. + +#pragma mark - Scaled version +@property(assign) CGSize maxPixelSize; + +- (BOOL)requestImageForSize:(CGSize)maximumSizeInPoints scale:(CGFloat)scale withCompletionHandler:(void(^)(OCImage * _Nullable ocImage, NSError * _Nullable error, CGSize maximumSizeInPoints, UIImage * _Nullable image))completionHandler; //!< Returns YES if the image is already available and the completionHandler has already been called. Returns NO if the image is not yet available, will call completionHandler when it is. + +- (BOOL)canProvideForMaximumSizeInPixels:(CGSize)maximumSizeInPixels; @end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Item/Images/OCImage.m b/ownCloudSDK/Item/Images/OCImage.m index 88fb99a7..e2270807 100644 --- a/ownCloudSDK/Item/Images/OCImage.m +++ b/ownCloudSDK/Item/Images/OCImage.m @@ -17,8 +17,12 @@ */ #import "OCImage.h" +#import "UIImage+OCTools.h" @implementation OCImage +{ + NSMutableDictionary *_imageByRequestedMaximumSize; +} @synthesize url = _url; @@ -38,7 +42,6 @@ - (instancetype)init return(self); } - - (NSData *)data { NSData *returnData = nil; @@ -131,7 +134,7 @@ - (BOOL)requestImageWithCompletionHandler:(void(^)(OCImage *ocImage, NSError *er } else { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ UIImage *image = nil; image = self.image; @@ -155,6 +158,130 @@ - (BOOL)requestImageWithCompletionHandler:(void(^)(OCImage *ocImage, NSError *er return (imageAlreadyLoaded); } +#pragma mark - Scaled version +- (BOOL)requestImageForSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale withCompletionHandler:(void (^)(OCImage * _Nullable ocImage, NSError * _Nullable error, CGSize, UIImage * _Nullable image))completionHandler +{ + CGSize requestedMaximumSizeInPixels; + NSValue *requestedMaximumSizeInPixelsValue; + UIImage *existingImage = nil; + + if (scale==0) + { + scale = UIScreen.mainScreen.scale; + } + + requestedMaximumSizeInPixels = CGSizeMake(requestedMaximumSizeInPoints.width * scale, requestedMaximumSizeInPoints.height * scale); + requestedMaximumSizeInPixelsValue = [NSValue valueWithCGSize:requestedMaximumSizeInPixels]; + + @synchronized(self) + { + if (_imageByRequestedMaximumSize == nil) + { + _imageByRequestedMaximumSize = [NSMutableDictionary new]; + } + + existingImage = [_imageByRequestedMaximumSize objectForKey:requestedMaximumSizeInPixelsValue]; + } + + if (existingImage == nil) + { + // No existing image, compute async + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + UIImage *returnImage = nil; + + [self->_processingLock lock]; // Lock to make any subsequent (possibly identical) computations wait until this one is done, in order not to do the same computations twice + + { + UIImage *sourceImage = nil; + + // Check if, by now, what is being requested is already there + @synchronized(self) + { + returnImage = [self->_imageByRequestedMaximumSize objectForKey:requestedMaximumSizeInPixelsValue]; + + if (returnImage == nil) + { + // Check if an existing image can be used. If so, go for the smallest, existing image that's still bigger than what was requested, so computation is fastest. + CGFloat sourceImagePixelCount = 0; + + for (NSValue *otherMaximumSizeValue in self->_imageByRequestedMaximumSize) + { + CGSize otherMaximumSize = otherMaximumSizeValue.CGSizeValue; + + if ((otherMaximumSize.width > requestedMaximumSizeInPixels.width) && (otherMaximumSize.height > requestedMaximumSizeInPixels.height)) + { + CGFloat pixelCount = otherMaximumSize.width * otherMaximumSize.height; + + if ((pixelCount < sourceImagePixelCount) || (sourceImagePixelCount == 0)) + { + sourceImage = self->_imageByRequestedMaximumSize[otherMaximumSizeValue]; + sourceImagePixelCount = pixelCount; + } + } + } + } + } + + if (returnImage == nil) + { + // Compute a new image + if (sourceImage == nil) + { + sourceImage = [self decodeImage]; // Don't cache the decoded image to save memory + } + + if (sourceImage != nil) + { + // Could offer performance advantage on iOS 15, but seems to return a too-low res image (Simulator iPhone 12 mini, iOS 15.4) + // + // if (@available(iOS 15, *)) + // { + // returnImage = [sourceImage imageByPreparingThumbnailOfSize:requestedMaximumSizeInPoints]; + // } + + if (returnImage == nil) + { + returnImage = [sourceImage scaledImageFittingInSize:requestedMaximumSizeInPoints scale:scale]; + } + + if (returnImage != nil) + { + @synchronized(self) + { + [self->_imageByRequestedMaximumSize removeAllObjects]; + [self->_imageByRequestedMaximumSize setObject:returnImage forKey:requestedMaximumSizeInPixelsValue]; + } + } + } + } + } + + [self->_processingLock unlock]; // Done! Unlock! + + if (completionHandler != nil) + { + completionHandler(self, nil, requestedMaximumSizeInPoints, returnImage); + } + }); + + return (NO); + } + + // Image exists already + if (completionHandler != nil) + { + completionHandler(self, nil, requestedMaximumSizeInPoints, existingImage); + } + + return (YES); +} + +- (BOOL)canProvideForMaximumSizeInPixels:(CGSize)maximumSizeInPixels +{ + return ((maximumSizeInPixels.width <= _maxPixelSize.width) && (maximumSizeInPixels.height <= _maxPixelSize.height)); +} + + #pragma mark - Secure Coding + (BOOL)supportsSecureCoding { @@ -163,18 +290,20 @@ + (BOOL)supportsSecureCoding - (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:_url forKey:@"url"]; - [coder encodeObject:_data forKey:@"data"]; - [coder encodeObject:_mimeType forKey:@"mimeType"]; + [coder encodeObject:_url forKey:@"url"]; + [coder encodeObject:_data forKey:@"data"]; + [coder encodeObject:_mimeType forKey:@"mimeType"]; + [coder encodeCGSize:_maxPixelSize forKey:@"maxPixelSize"]; } - (instancetype)initWithCoder:(NSCoder *)decoder { if ((self = [self init]) != nil) { - _url = [decoder decodeObjectOfClass:[NSURL class] forKey:@"url"]; - _data = [decoder decodeObjectOfClass:[NSData class] forKey:@"data"]; - _mimeType = [decoder decodeObjectOfClass:[NSString class] forKey:@"mimeType"]; + _url = [decoder decodeObjectOfClass:NSURL.class forKey:@"url"]; + _data = [decoder decodeObjectOfClass:NSData.class forKey:@"data"]; + _mimeType = [decoder decodeObjectOfClass:NSString.class forKey:@"mimeType"]; + _maxPixelSize = [decoder decodeCGSizeForKey:@"maxPixelSize"]; } return (self); diff --git a/ownCloudSDK/Item/Images/OCItemThumbnail.h b/ownCloudSDK/Item/Images/OCItemThumbnail.h index 1c101f98..9de0e2e2 100644 --- a/ownCloudSDK/Item/Images/OCItemThumbnail.h +++ b/ownCloudSDK/Item/Images/OCItemThumbnail.h @@ -20,25 +20,17 @@ #import "OCTypes.h" #import "OCItemVersionIdentifier.h" +NS_ASSUME_NONNULL_BEGIN + @interface OCItemThumbnail : OCImage { - CGSize _maximumSizeInPixels; - - NSMutableDictionary *_imageByRequestedMaximumSize; - OCItemVersionIdentifier *_itemVersionIdentifier; - NSString *_specID; } -@property(assign) CGSize maximumSizeInPixels; - -@property(strong) OCItemVersionIdentifier *itemVersionIdentifier; - -@property(strong) NSString *specID; - -- (BOOL)requestImageForSize:(CGSize)maximumSizeInPoints scale:(CGFloat)scale withCompletionHandler:(void(^)(OCItemThumbnail *thumbnail, NSError *error, CGSize maximumSizeInPoints, UIImage *image))completionHandler; //!< Returns YES if the image is already available and the completionHandler has already been called. Returns NO if the image is not yet available, will call completionHandler when it is. - -- (BOOL)canProvideForMaximumSizeInPixels:(CGSize)maximumSizeInPixels; +@property(strong,nullable) OCItemVersionIdentifier *itemVersionIdentifier; +@property(strong,nullable) NSString *specID; @end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Item/Images/OCItemThumbnail.m b/ownCloudSDK/Item/Images/OCItemThumbnail.m index c0fd1bb0..2698317d 100644 --- a/ownCloudSDK/Item/Images/OCItemThumbnail.m +++ b/ownCloudSDK/Item/Images/OCItemThumbnail.m @@ -25,116 +25,6 @@ @implementation OCItemThumbnail @synthesize specID = _specID; -- (BOOL)requestImageForSize:(CGSize)requestedMaximumSizeInPoints scale:(CGFloat)scale withCompletionHandler:(void(^)(OCItemThumbnail *thumbnail, NSError *error, CGSize maximumSizeInPoints, UIImage *image))completionHandler -{ - CGSize requestedMaximumSizeInPixels; - NSValue *requestedMaximumSizeInPixelsValue; - UIImage *existingImage = nil; - - if (scale==0) - { - scale = UIScreen.mainScreen.scale; - } - - requestedMaximumSizeInPixels = CGSizeMake(requestedMaximumSizeInPoints.width * scale, requestedMaximumSizeInPoints.height * scale); - requestedMaximumSizeInPixelsValue = [NSValue valueWithCGSize:requestedMaximumSizeInPixels]; - - @synchronized(self) - { - if (_imageByRequestedMaximumSize == nil) - { - _imageByRequestedMaximumSize = [NSMutableDictionary new]; - } - - existingImage = [_imageByRequestedMaximumSize objectForKey:requestedMaximumSizeInPixelsValue]; - } - - if (existingImage == nil) - { - // No existing image, compute async - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ - UIImage *returnImage = nil; - - [self->_processingLock lock]; // Lock to make any subsequent (possibly identical) computations wait until this one is done, in order not to do the same computations twice - - { - UIImage *sourceImage = nil; - - // Check if, by now, what is being requested is already there - @synchronized(self) - { - returnImage = [self->_imageByRequestedMaximumSize objectForKey:requestedMaximumSizeInPixelsValue]; - - if (returnImage == nil) - { - // Check if an existing image can be used. If so, go for the smallest, existing image that's still bigger than what was requested, so computation is fastest. - CGFloat sourceImagePixelCount = 0; - - for (NSValue *otherMaximumSizeValue in self->_imageByRequestedMaximumSize) - { - CGSize otherMaximumSize = otherMaximumSizeValue.CGSizeValue; - - if ((otherMaximumSize.width < requestedMaximumSizeInPixels.width) || (otherMaximumSize.height < requestedMaximumSizeInPixels.height)) - { - CGFloat pixelCount = otherMaximumSize.width * otherMaximumSize.height; - - if ((pixelCount < sourceImagePixelCount) || (sourceImagePixelCount == 0)) - { - sourceImage = self->_imageByRequestedMaximumSize[otherMaximumSizeValue]; - sourceImagePixelCount = pixelCount; - } - } - } - } - } - - if (returnImage == nil) - { - // Compute a new image - if (sourceImage == nil) - { - sourceImage = [self decodeImage]; // Don't cache the decoded image to save memory - } - - if (sourceImage != nil) - { - if ((returnImage = [sourceImage scaledImageFittingInSize:requestedMaximumSizeInPoints scale:scale]) != nil) - { - @synchronized(self) - { - [self->_imageByRequestedMaximumSize removeAllObjects]; - [self->_imageByRequestedMaximumSize setObject:returnImage forKey:requestedMaximumSizeInPixelsValue]; - } - } - } - } - } - - [self->_processingLock unlock]; // Done! Unlock! - - if (completionHandler != nil) - { - completionHandler(self, nil, requestedMaximumSizeInPoints, returnImage); - } - }); - - return (NO); - } - - // Image exists already - if (completionHandler != nil) - { - completionHandler(self, nil, requestedMaximumSizeInPoints, existingImage); - } - - return (YES); -} - -- (BOOL)canProvideForMaximumSizeInPixels:(CGSize)maximumSizeInPixels -{ - return ((maximumSizeInPixels.width <= _maximumSizeInPixels.width) && (maximumSizeInPixels.height <= _maximumSizeInPixels.height)); -} - #pragma mark - Secure Coding + (BOOL)supportsSecureCoding { @@ -145,7 +35,6 @@ - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; - [coder encodeCGSize:_maximumSizeInPixels forKey:@"maximumSizeInPixels"]; [coder encodeObject:_itemVersionIdentifier forKey:@"itemVersionIdentifier"]; [coder encodeObject:_specID forKey:@"specID"]; } @@ -154,7 +43,6 @@ - (instancetype)initWithCoder:(NSCoder *)decoder { if ((self = [super initWithCoder:decoder]) != nil) { - _maximumSizeInPixels = [decoder decodeCGSizeForKey:@"maximumSizeInPixels"]; _itemVersionIdentifier = [decoder decodeObjectOfClass:[OCItemVersionIdentifier class] forKey:@"itemVersionIdentifier"]; _specID = [decoder decodeObjectOfClass:[NSString class] forKey:@"specID"]; } diff --git a/ownCloudSDK/Item/OCItem+OCDataItem.h b/ownCloudSDK/Item/OCItem+OCDataItem.h new file mode 100644 index 00000000..9c72d8bb --- /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+OCFileURLMetadata.h b/ownCloudSDK/Item/OCItem+OCFileURLMetadata.h index e23a7abc..a88ff22a 100644 --- a/ownCloudSDK/Item/OCItem+OCFileURLMetadata.h +++ b/ownCloudSDK/Item/OCItem+OCFileURLMetadata.h @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2018, 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.h" NS_ASSUME_NONNULL_BEGIN diff --git a/ownCloudSDK/Item/OCItem+OCFileURLMetadata.m b/ownCloudSDK/Item/OCItem+OCFileURLMetadata.m index e428d28b..a1e1dde7 100644 --- a/ownCloudSDK/Item/OCItem+OCFileURLMetadata.m +++ b/ownCloudSDK/Item/OCItem+OCFileURLMetadata.m @@ -6,6 +6,16 @@ // Copyright © 2018 ownCloud GmbH. All rights reserved. // +/* + * Copyright (C) 2018, 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+OCFileURLMetadata.h" #import diff --git a/ownCloudSDK/Item/OCItem+OCTypeAlias.h b/ownCloudSDK/Item/OCItem+OCTypeAlias.h new file mode 100644 index 00000000..782eeb3a --- /dev/null +++ b/ownCloudSDK/Item/OCItem+OCTypeAlias.h @@ -0,0 +1,37 @@ +// +// OCItem+OCTypeAlias.h +// ownCloudApp +// +// Created by Felix Schwarz on 12.09.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.h" +#import "OCTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCItem (MIMETypeAliases) + +@property(class, readonly,nonatomic) NSDictionary *mimeTypeToAliasesMap; + ++ (nullable OCTypeAlias)typeAliasForMIMEType:(nullable OCMIMEType)mimeType; ++ (NSArray *)mimeTypesMatching:(BOOL(^)(OCMIMEType mimeType, OCTypeAlias alias))matcher; + +@property(readonly,nonatomic) OCTypeAlias typeAlias; + +@end + +extern OCTypeAlias OCTypeAliasMIMEPrefix; //!< If no OCTypeAlias is available for an OCMIMEType, the returned OCTypeAlias is (OCTypeAliasMIMEPrefix + mimeType) + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Item/OCItem+OCTypeAlias.m b/ownCloudSDK/Item/OCItem+OCTypeAlias.m new file mode 100644 index 00000000..5ee4db18 --- /dev/null +++ b/ownCloudSDK/Item/OCItem+OCTypeAlias.m @@ -0,0 +1,167 @@ +// +// OCItem+OCTypeAlias.m +// ownCloudApp +// +// Created by Felix Schwarz on 12.09.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+OCTypeAlias.h" +#import "OCLogger.h" + +@implementation OCItem (MIMETypeAliases) + ++ (NSDictionary *)mimeTypeToAliasesMap +{ + static dispatch_once_t onceToken; + static NSDictionary *mimeTypeAliases; + + dispatch_once(&onceToken, ^{ + mimeTypeAliases = @{ + // List taken from https://github.com/owncloud/core/blob/master/resources/config/mimetypealiases.dist.json on 2022-09-12 + // (previously taken from https://github.com/owncloud/core/blob/master/core/js/mimetypelist.js) + @"application/coreldraw": @"image", + @"application/epub+zip": @"text", + @"application/font-sfnt": @"image", + @"application/font-woff": @"image", + @"application/gzip": @"package/x-generic", + @"application/illustrator": @"image", + @"application/javascript": @"text/code", + @"application/json": @"text/code", + @"application/msaccess": @"file", + @"application/msexcel": @"x-office/spreadsheet", + @"application/msonenote": @"x-office/document", + @"application/mspowerpoint": @"x-office/presentation", + @"application/msword": @"x-office/document", + @"application/octet-stream": @"file", + @"application/postscript": @"image", + @"application/rss+xml": @"application/xml", + @"application/vnd.android.package-archive": @"package/x-generic", + @"application/vnd.lotus-wordpro": @"x-office/document", + @"application/vnd.ms-excel": @"x-office/spreadsheet", + @"application/vnd.ms-excel.addin.macroEnabled.12": @"x-office/spreadsheet", + @"application/vnd.ms-excel.sheet.binary.macroEnabled.12": @"x-office/spreadsheet", + @"application/vnd.ms-excel.sheet.macroEnabled.12": @"x-office/spreadsheet", + @"application/vnd.ms-excel.template.macroEnabled.12": @"x-office/spreadsheet", + @"application/vnd.ms-fontobject": @"image", + @"application/vnd.ms-powerpoint": @"x-office/presentation", + @"application/vnd.ms-powerpoint.addin.macroEnabled.12": @"x-office/presentation", + @"application/vnd.ms-powerpoint.presentation.macroEnabled.12": @"x-office/presentation", + @"application/vnd.ms-powerpoint.slideshow.macroEnabled.12": @"x-office/presentation", + @"application/vnd.ms-powerpoint.template.macroEnabled.12": @"x-office/presentation", + @"application/vnd.ms-word.document.macroEnabled.12": @"x-office/document", + @"application/vnd.ms-word.template.macroEnabled.12": @"x-office/document", + @"application/vnd.oasis.opendocument.presentation": @"x-office/presentation", + @"application/vnd.oasis.opendocument.presentation-template": @"x-office/presentation", + @"application/vnd.oasis.opendocument.spreadsheet": @"x-office/spreadsheet", + @"application/vnd.oasis.opendocument.spreadsheet-template": @"x-office/spreadsheet", + @"application/vnd.oasis.opendocument.text": @"x-office/document", + @"application/vnd.oasis.opendocument.text-master": @"x-office/document", + @"application/vnd.oasis.opendocument.text-template": @"x-office/document", + @"application/vnd.oasis.opendocument.text-web": @"x-office/document", + @"application/vnd.oasis.opendocument.graphics-flat-xml": @"x-office/drawing", + @"application/vnd.oasis.opendocument.graphics": @"x-office/drawing", + @"application/vnd.oasis.opendocument.graphics-template": @"x-office/drawing", + @"application/vnd.openxmlformats-officedocument.presentationml.presentation": @"x-office/presentation", + @"application/vnd.openxmlformats-officedocument.presentationml.slideshow": @"x-office/presentation", + @"application/vnd.openxmlformats-officedocument.presentationml.template": @"x-office/presentation", + @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": @"x-office/spreadsheet", + @"application/vnd.openxmlformats-officedocument.spreadsheetml.template": @"x-office/spreadsheet", + @"application/vnd.openxmlformats-officedocument.wordprocessingml.document": @"x-office/document", + @"application/vnd.openxmlformats-officedocument.wordprocessingml.template": @"x-office/document", + @"application/vnd.visio": @"x-office/document", + @"application/vnd.wordperfect": @"x-office/document", + @"application/x-7z-compressed": @"package/x-generic", + @"application/x-bzip2": @"package/x-generic", + @"application/x-cbr": @"text", + @"application/x-compressed": @"package/x-generic", + @"application/x-dcraw": @"image", + @"application/x-deb": @"package/x-generic", + @"application/x-font": @"image", + @"application/x-gimp": @"image", + @"application/x-gzip": @"package/x-generic", + @"application/x-perl": @"text/code", + @"application/x-photoshop": @"image", + @"application/x-php": @"text/code", + @"application/x-rar-compressed": @"package/x-generic", + @"application/x-tar": @"package/x-generic", + @"application/x-tex": @"text", + @"application/xml": @"text/html", + @"application/yaml": @"text/code", + @"application/zip": @"package/x-generic", + @"database": @"file", + @"httpd/unix-directory": @"dir", + @"message/rfc822": @"text", + @"text/css": @"text/code", + @"text/csv": @"x-office/spreadsheet", + @"text/html": @"text/code", + @"text/x-c": @"text/code", + @"text/x-c++src": @"text/code", + @"text/x-h": @"text/code", + @"text/x-java-source": @"text/code", + @"text/x-python": @"text/code", + @"text/x-shellscript": @"text/code", + @"web": @"text/code" + }; + }); + + return (mimeTypeAliases); +} + ++ (OCTypeAlias)typeAliasForMIMEType:(OCMIMEType)mimeType +{ + static dispatch_once_t onceToken; + static NSMutableDictionary *typeAliasByMIMEType; + dispatch_once(&onceToken, ^{ + typeAliasByMIMEType = [[NSMutableDictionary alloc] initWithDictionary:self.mimeTypeToAliasesMap]; + }); + + if (mimeType == nil) + { + return (nil); + } + + OCTypeAlias typeAlias; + + if ((typeAlias = typeAliasByMIMEType[mimeType]) == nil) + { + typeAlias = [OCTypeAliasMIMEPrefix stringByAppendingString:mimeType]; + typeAliasByMIMEType[mimeType] = typeAlias; + } + + return (typeAlias); +} + + ++ (NSArray *)mimeTypesMatching:(BOOL(^)(NSString *mimeType, NSString *alias))matcher +{ + NSMutableArray *matchedMIMETypes = [NSMutableArray new]; + + [self.mimeTypeToAliasesMap enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull mimeType, NSString * _Nonnull alias, BOOL * _Nonnull stop) { + if (matcher(mimeType, alias)) + { + [matchedMIMETypes addObject:mimeType]; + } + }]; + + return (matchedMIMETypes); +} + +- (OCTypeAlias)typeAlias +{ + return ([OCItem typeAliasForMIMEType:self.mimeType]); +} + +@end + +OCTypeAlias OCTypeAliasMIMEPrefix = @"MIME:"; diff --git a/ownCloudSDK/Item/OCItem+OCXMLObjectCreation.m b/ownCloudSDK/Item/OCItem+OCXMLObjectCreation.m index 80236f5a..0c2ad30f 100644 --- a/ownCloudSDK/Item/OCItem+OCXMLObjectCreation.m +++ b/ownCloudSDK/Item/OCItem+OCXMLObjectCreation.m @@ -269,7 +269,16 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)responseNode xmlParser:(OCXM if ((httpStatus = propstatNode.keyValues[@"d:status"]) != nil) { - if (httpStatus.isSuccess) { + BOOL handleContainedTags = httpStatus.isSuccess; // Handle parts with 2xx status + + if (httpStatus.code == OCHTTPStatusCodeTOO_EARLY) // Handle parts with 425 status + { + // Item is processed server side => adjust state + item.state = OCItemStateServerSideProcessing; + handleContainedTags = YES; + } + + if (handleContainedTags) { [propstatNode enumerateChildNodesWithName:@"d:prop" usingBlock:^(OCXMLParserNode *propNode) { // Collection? __block BOOL isCollection = NO; diff --git a/ownCloudSDK/Item/OCItem.h b/ownCloudSDK/Item/OCItem.h index a7a2d33e..90109e17 100644 --- a/ownCloudSDK/Item/OCItem.h +++ b/ownCloudSDK/Item/OCItem.h @@ -15,11 +15,13 @@ * You should have received a copy of this license along with this program. If not, see . * */ + #import #import "OCTypes.h" #import "OCItemThumbnail.h" #import "OCItemVersionIdentifier.h" #import "OCClaim.h" +#import "OCLocation.h" #import "OCTUSHeader.h" @class OCFile; @@ -31,6 +33,12 @@ typedef NS_ENUM(NSInteger, OCItemType) { OCItemTypeFile, //!< This item is a file. OCItemTypeCollection //!< This item is a collection (usually a directory) +} __attribute__((enum_extensibility(closed))); + +typedef NS_OPTIONS(NSInteger, OCItemState) +{ + OCItemStateNormal, //!< The item is in normal state + OCItemStateServerSideProcessing //!< The item is in server-side processing (f.ex. via a workflow) and can't be downloaded }; typedef NS_OPTIONS(NSInteger, OCItemSyncActivity) @@ -54,8 +62,11 @@ typedef NS_OPTIONS(NSInteger, OCItemPermissions) OCItemPermissionCreateFolder = (1<<5), //!< Code "K" Folder can create folder (mkdir) OCItemPermissionDelete = (1<<6), //!< Code "D" File or Folder can delete file or folder OCItemPermissionRename = (1<<7), //!< Code "N" File or Folder can rename file or folder - OCItemPermissionMove = (1<<8) //!< Code "V" File or Folder can move file or folder -}; + OCItemPermissionMove = (1<<8), //!< Code "V" File or Folder can move file or folder + + // unimplemented in parsing at the time of writing (2023-05-05) + OCItemPermissionDeniable = (1<<9) //!< Code "Z" File or Folder can limit access (experimental as of 2023-05-05) +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSInteger, OCItemThumbnailAvailability) { @@ -64,7 +75,7 @@ typedef NS_ENUM(NSInteger, OCItemThumbnailAvailability) OCItemThumbnailAvailabilityNone, //!< No thumbnail is available for this item OCItemThumbnailAvailabilityInternal = -1 //!< Internal value. Don't use. -}; +} __attribute__((enum_extensibility(closed))); typedef NS_ENUM(NSInteger, OCItemCloudStatus) { @@ -72,7 +83,9 @@ typedef NS_ENUM(NSInteger, OCItemCloudStatus) OCItemCloudStatusLocalCopy, //!< Item is a local copy of a file on the server OCItemCloudStatusLocallyModified, //!< Item is a modified copy of a file on the server OCItemCloudStatusLocalOnly //!< Item only exists locally. There's no remote copy. -}; +} __attribute__((enum_extensibility(closed))); + +typedef NSInteger OCItemVersionSeed; //!< Version seed (opaque format) that changes whenever an item changes #import "OCShare.h" @@ -88,11 +101,15 @@ NS_ASSUME_NONNULL_BEGIN NSTimeInterval _localAttributesLastModified; NSString *_creationHistory; + + OCItemVersionSeed _versionSeed; + + OCBookmarkUUIDString _bookmarkUUID; } @property(assign) OCItemType type; //!< The type of the item (e.g. file, collection, ..) -@property(nullable,strong) NSString *mimeType; //!< MIME type ("Content Type") of the item +@property(nullable,strong) OCMIMEType mimeType; //!< MIME type ("Content Type") of the item @property(readonly,nonatomic) OCItemCloudStatus cloudStatus; //!< the cloud status of the item (computed using the item's metadata) @@ -108,7 +125,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) OCItem *remoteItem; //!< If .locallyModified==YES or .localRelativePath!=nil and a different version is available remotely (on the server), the item as retrieved from the server. -@property(nullable,strong) OCPath path; //!< Path of the item on the server relative to root +@property(nullable,strong,nonatomic) OCPath path; //!< Path of the item on the server relative to root +@property(nullable,readonly,nonatomic) OCPath parentPath; //!< Parent path of the item on the server relative to root. The parentPath of "/" is "/" (follows NSString.stringByDeletingLastPathComponent logic) @property(nullable,readonly,nonatomic) NSString *name; //!< Name of the item, derived from .path. (dynamic/ephermal) @property(nullable,strong) OCPath previousPath; //!< A previous path of the item, f.ex. before being moved (dynamic/ephermal) @@ -119,12 +137,18 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) NSArray *checksums; //!< (Optional) checksums of the item. Typically only requested for uploaded files. +@property(nullable,strong,nonatomic) OCDriveID driveID; //!< Identifier of the drive the item is located on +@property(nullable,strong,nonatomic) OCLocation *location; //!< Abstract location (encapsulates all information needed to find the item's location in an account) +@property(readonly,nonatomic) OCLocationString locationString; //!< Single-string representation of locationString that can be used to determine if one item is located inside another (like paths, but with awareness for driveIDs) + @property(nullable,strong,nonatomic) OCFileID parentFileID; //!< Unique identifier of the parent folder (persists over lifetime of file, incl. across modifications) @property(nullable,strong,nonatomic) OCFileID fileID; //!< Unique identifier of the item on the server (persists over lifetime of file, incl. across modifications) @property(nullable,strong,nonatomic) OCFileETag eTag; //!< ETag of the item on the server (changes with every modification) @property(nullable,readonly,nonatomic) OCItemVersionIdentifier *itemVersionIdentifier; // (dynamic/ephermal) @property(readonly,nonatomic) BOOL isPlaceholder; //!< YES if this a placeholder item +@property(assign) OCItemState state; //!< .normal for "normal" items, .serverSideProcessing if item is being processed (f.ex. by a workflow on the server) + @property(readonly,nonatomic) BOOL isRoot; //!< YES if this item is representing the root folder @property(readonly,nonatomic) BOOL hasLocalAttributes; //!< Returns YES if the item has any local attributes @@ -156,8 +180,7 @@ NS_ASSUME_NONNULL_BEGIN @property(readonly,nonatomic) UInt64 tusMaximumSize; //!< For folders only: Tus maximum chunk size; undefined for files // @property(strong,nullable) OCTUSHeader *tusHeader; //!< For folders only: detailed Tus support info (optional); nil for files -@property(readonly,nonatomic) OCItemThumbnailAvailability thumbnailAvailability; //!< Availability of thumbnails for this item. If OCItemThumbnailAvailabilityUnknown, call -[OCCore retrieveThumbnailFor:resultHandler:] to update it. -@property(nullable,strong,nonatomic) OCItemThumbnail *thumbnail; //!< Thumbnail for the item. +@property(readonly,nonatomic) OCItemThumbnailAvailability thumbnailAvailability; //!< Availability of thumbnails for this item. @property(nullable,strong) OCDatabaseID databaseID; //!< OCDatabase-specific ID referencing the item in the database @property(nullable,strong) OCDatabaseTimestamp databaseTimestamp; //!< OCDatabase-specific: ((NSUInteger)NSDate.timeIntervalSinceReferenceDate) value this item was added or last updated in the database (most useful when reading items from the database). Not preserved (ephermal!), read-only. @@ -167,6 +190,10 @@ 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 + +@property(nullable,strong) OCBookmarkUUIDString bookmarkUUID; //!< BookmarkUUIDString for temporary use (not serialized) + + (OCLocalID)generateNewLocalID; //!< Generates a new, unique OCLocalID + (OCFileID)generatePlaceholderFileID; //!< Generates a new, unique placeholder OCFileID @@ -193,6 +220,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; @@ -212,9 +244,13 @@ extern OCItemPropertyName OCItemPropertyNameLocalAttributes; // Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameType; //!< Supported by OCQueryCondition SQLBuilder +extern OCItemPropertyName OCItemPropertyNameDriveID; //!< Supported by OCQueryCondition SQLBuilder +extern OCItemPropertyName OCItemPropertyNameLocationString; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNamePath; //!< Supported by OCQueryCondition SQLBuilder +extern OCItemPropertyName OCItemPropertyNameParentPath; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameName; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameMIMEType; //!< Supported by OCQueryCondition SQLBuilder +extern OCItemPropertyName OCItemPropertyNameTypeAlias; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameSize; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameCloudStatus; //!< Supported by OCQueryCondition SQLBuilder extern OCItemPropertyName OCItemPropertyNameHasLocalAttributes; //!< Supported by OCQueryCondition SQLBuilder diff --git a/ownCloudSDK/Item/OCItem.m b/ownCloudSDK/Item/OCItem.m index b02182df..d52000cd 100644 --- a/ownCloudSDK/Item/OCItem.m +++ b/ownCloudSDK/Item/OCItem.m @@ -22,11 +22,16 @@ #import "OCFile.h" #import "OCItem+OCItemCreationDebugging.h" #import "OCMacros.h" +#import "NSString+OCPath.h" @implementation OCItem +{ + OCLocation *_location; +} @dynamic cloudStatus; @dynamic hasLocalAttributes; +@dynamic parentPath; + (OCLocalID)generateNewLocalID { @@ -47,6 +52,8 @@ - (instancetype)init _thumbnailAvailability = OCItemThumbnailAvailabilityInternal; _localID = [OCItem generateNewLocalID]; + + [self regenerateSeed]; } return (self); @@ -98,6 +105,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_parentLocalID forKey:@"parentLocalID"]; [coder encodeObject:_localID forKey:@"localID"]; + [coder encodeObject:_driveID forKey:@"driveID"]; + [coder encodeObject:_checksums forKey:@"checksums"]; [coder encodeObject:_parentFileID forKey:@"parentFileID"]; @@ -115,6 +124,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_isFavorite forKey:@"isFavorite"]; + [coder encodeInteger:_state forKey:@"state"]; + [coder encodeObject:_localAttributes forKey:@"localAttributes"]; [coder encodeDouble:_localAttributesLastModified forKey:@"localAttributesLastModified"]; @@ -130,6 +141,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_quotaBytesRemaining forKey:@"quotaBytesRemaining"]; [coder encodeObject:_quotaBytesUsed forKey:@"quotaBytesUsed"]; + [coder encodeInteger:_versionSeed forKey:@"versionSeed"]; + } - (instancetype)initWithCoder:(NSCoder *)decoder @@ -142,54 +155,60 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _type = [decoder decodeIntegerForKey:@"type"]; - _mimeType = [decoder decodeObjectOfClass:[NSString class] forKey:@"mimeType"]; + _mimeType = [decoder decodeObjectOfClass:NSString.class forKey:@"mimeType"]; _permissions = [decoder decodeIntegerForKey:@"permissions"]; _localRelativePath = [decoder decodeObjectOfClass:NSString.class forKey:@"localRelativePath"]; _locallyModified = [decoder decodeBoolForKey:@"locallyModified"]; - _localCopyVersionIdentifier = [decoder decodeObjectOfClass:[OCItemVersionIdentifier class] forKey:@"localCopyVersionIdentifier"]; - _downloadTriggerIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"downloadTriggerIdentifier"]; - _fileClaim = [decoder decodeObjectOfClass:[OCClaim class] forKey:@"fileClaim"]; + _localCopyVersionIdentifier = [decoder decodeObjectOfClass:OCItemVersionIdentifier.class forKey:@"localCopyVersionIdentifier"]; + _downloadTriggerIdentifier = [decoder decodeObjectOfClass:NSString.class forKey:@"downloadTriggerIdentifier"]; + _fileClaim = [decoder decodeObjectOfClass:OCClaim.class forKey:@"fileClaim"]; - _remoteItem = [decoder decodeObjectOfClass:[OCItem class] forKey:@"remoteItem"]; + _remoteItem = [decoder decodeObjectOfClass:OCItem.class forKey:@"remoteItem"]; - _path = [decoder decodeObjectOfClass:[NSString class] forKey:@"path"]; + _path = [decoder decodeObjectOfClass:NSString.class forKey:@"path"]; - _parentLocalID = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentLocalID"]; - _localID = [decoder decodeObjectOfClass:[NSString class] forKey:@"localID"]; + _parentLocalID = [decoder decodeObjectOfClass:NSString.class forKey:@"parentLocalID"]; + _localID = [decoder decodeObjectOfClass:NSString.class forKey:@"localID"]; + + _driveID = [decoder decodeObjectOfClass:NSString.class forKey:@"driveID"]; _checksums = [decoder decodeObjectOfClasses:[[NSSet alloc] initWithObjects:[NSArray class], [OCChecksum class], nil] forKey:@"checksums"]; - _parentFileID = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentFileID"]; - _fileID = [decoder decodeObjectOfClass:[NSString class] forKey:@"fileID"]; - _eTag = [decoder decodeObjectOfClass:[NSString class] forKey:@"eTag"]; + _parentFileID = [decoder decodeObjectOfClass:NSString.class forKey:@"parentFileID"]; + _fileID = [decoder decodeObjectOfClass:NSString.class forKey:@"fileID"]; + _eTag = [decoder decodeObjectOfClass:NSString.class forKey:@"eTag"]; _activeSyncRecordIDs = [decoder decodeObjectOfClasses:[[NSSet alloc] initWithObjects:NSArray.class, NSNumber.class, nil] forKey:@"activeSyncRecordIDs"]; _syncActivity = [decoder decodeIntegerForKey:@"syncActivity"]; - _syncActivityCounts = [decoder decodeObjectOfClasses:[[NSSet alloc] initWithObjects:[NSCountedSet class], [NSNumber class], nil] forKey:@"syncActivityCounts"]; + _syncActivityCounts = [decoder decodeObjectOfClasses:[[NSSet alloc] initWithObjects:[NSCountedSet class], NSNumber.class, nil] forKey:@"syncActivityCounts"]; _size = [decoder decodeIntegerForKey:@"size"]; - _creationDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"creationDate"]; - _lastModified = [decoder decodeObjectOfClass:[NSDate class] forKey:@"lastModified"]; - _lastUsed = [decoder decodeObjectOfClass:[NSDate class] forKey:@"lastUsed"]; + _creationDate = [decoder decodeObjectOfClass:NSDate.class forKey:@"creationDate"]; + _lastModified = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastModified"]; + _lastUsed = [decoder decodeObjectOfClass:NSDate.class forKey:@"lastUsed"]; + + _isFavorite = [decoder decodeObjectOfClass:NSNumber.class forKey:@"isFavorite"]; - _isFavorite = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"isFavorite"]; + _state = [decoder decodeIntegerForKey:@"state"]; _localAttributes = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"localAttributes"]; _localAttributesLastModified = [decoder decodeDoubleForKey:@"localAttributesLastModified"]; _shareTypesMask = [decoder decodeIntegerForKey:@"shareTypesMask"]; - _owner = [decoder decodeObjectOfClass:[OCUser class] forKey:@"owner"]; + _owner = [decoder decodeObjectOfClass:OCUser.class forKey:@"owner"]; - _privateLink = [decoder decodeObjectOfClass:[NSURL class] forKey:@"privateLink"]; + _privateLink = [decoder decodeObjectOfClass:NSURL.class forKey:@"privateLink"]; _tusInfo = (UInt64)[decoder decodeInt64ForKey:@"tusInfo"]; - _databaseID = [decoder decodeObjectOfClass:[NSValue class] forKey:@"databaseID"]; + _databaseID = [decoder decodeObjectOfClass:NSValue.class forKey:@"databaseID"]; - _quotaBytesRemaining = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"quotaBytesRemaining"]; - _quotaBytesUsed = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"quotaBytesUsed"]; + _quotaBytesRemaining = [decoder decodeObjectOfClass:NSNumber.class forKey:@"quotaBytesRemaining"]; + _quotaBytesUsed = [decoder decodeObjectOfClass:NSNumber.class forKey:@"quotaBytesUsed"]; + + _versionSeed = [decoder decodeIntegerForKey:@"versionSeed"]; } return (self); @@ -214,7 +233,7 @@ - (NSData *)serializedData #pragma mark - Metadata - (NSString *)name { - return ([self.path lastPathComponent]); + return (self.path.lastPathComponent); } - (void)setETag:(OCFileETag)eTag @@ -254,6 +273,54 @@ - (NSString *)ownerUserName return (_owner.userName); } +- (OCLocationString)locationString +{ + return ([[NSString alloc] initWithFormat:@";%@:%@", ((_driveID != nil) ? _driveID : @""), ((_path != nil) ? _path : @"")]); +} + +- (void)setPath:(OCPath)path +{ + _location = nil; + _path = path; +} + +- (void)setDriveID:(OCDriveID)driveID +{ + _location = nil; + _driveID = driveID; +} + +- (OCLocation *)location +{ + if (_location != nil) + { + return (_location); + } + + OCLocation *location = [[OCLocation alloc] initWithDriveID:_driveID path:(_type == OCItemTypeCollection) ? _path.normalizedDirectoryPath : _path]; + + if (self.bookmarkUUID != nil) + { + location.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:self.bookmarkUUID]; + } + + _location = location; + + return (location); +} + +- (void)setLocation:(OCLocation *)location +{ + _driveID = location.driveID; + _path = location.path; + _location = location; +} + +- (OCPath)parentPath +{ + return (_path.parentPath); +} + #pragma mark - Thumbnails - (OCItemThumbnailAvailability)thumbnailAvailability { @@ -280,16 +347,6 @@ - (OCItemThumbnailAvailability)thumbnailAvailability return (_thumbnailAvailability); } -- (void)setThumbnail:(OCItemThumbnail *)thumbnail -{ - _thumbnail = thumbnail; - - if (thumbnail != nil) - { - _thumbnailAvailability = OCItemThumbnailAvailabilityAvailable; - } -} - #pragma mark - Local attributes - (BOOL)hasLocalAttributes { @@ -575,6 +632,8 @@ - (void)copyMetadataFrom:(OCItem *)item except:(NSSet *)exc CloneMetadata(@"fileID"); CloneMetadata(@"eTag"); + CloneMetadata(@"driveID"); + CloneMetadata(@"localAttributes"); CloneMetadata(@"localAttributesLastModified"); @@ -589,6 +648,8 @@ - (void)copyMetadataFrom:(OCItem *)item except:(NSSet *)exc CloneMetadata(@"isFavorite"); + CloneMetadata(@"state"); + CloneMetadata(@"thumbnail"); CloneMetadata(@"shares"); @@ -635,6 +696,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 { @@ -763,13 +845,17 @@ - (NSString *)description { NSString *shareTypesDescription = [self _shareTypesDescription]; - return ([NSString stringWithFormat:@"<%@: %p, type: %lu, name: %@, path: %@, size: %lu bytes, MIME-Type: %@, Last modified: %@, Last used: %@ fileID: %@, eTag: %@, parentID: %@, localID: %@, parentLocalID: %@%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, (unsigned long)self.type, self.name, self.path, self.size, self.mimeType, self.lastModified, self.lastUsed, self.fileID, self.eTag, self.parentFileID, self.localID, self.parentLocalID, ((shareTypesDescription!=nil) ? [NSString stringWithFormat:@", shareTypes: [%@]",shareTypesDescription] : @""), (self.isSharedWithUser ? @", sharedWithUser" : @""), (self.isShareable ? @", shareable" : @""), ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), (_removed ? @", removed" : @""), (_isFavorite.boolValue ? @", favorite" : @""), (_privateLink ? [NSString stringWithFormat:@", privateLink: %@", _privateLink] : @""), (_checksums ? [NSString stringWithFormat:@", checksums: %@", _checksums] : @""), [self _tusSupportDescription], (_downloadTriggerIdentifier ? [NSString stringWithFormat:@", downloadTrigger: %@", _downloadTriggerIdentifier] : @""), ((_fileClaim!=nil) ? @", fileClaim: yes" : @""), ((_localRelativePath!=nil) ? [NSString stringWithFormat:@", localRelativePath: %@", _localRelativePath] : @""), [self syncActivityDescription]]); + return ([NSString stringWithFormat:@"<%@: %p, type: %lu, name: %@, path: %@, size: %lu bytes, MIME-Type: %@, Last modified: %@, Last used: %@, driveID: %@, fileID: %@, eTag: %@, parentID: %@, localID: %@, parentLocalID: %@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, (unsigned long)self.type, self.name, self.path, self.size, self.mimeType, self.lastModified, self.lastUsed, self.driveID, self.fileID, self.eTag, self.parentFileID, self.localID, self.parentLocalID, ((shareTypesDescription!=nil) ? [NSString stringWithFormat:@", shareTypes: [%@]",shareTypesDescription] : @""), (self.isSharedWithUser ? @", sharedWithUser" : @""), (self.isShareable ? @", shareable" : @""), ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), (_removed ? @", removed" : @""), (_isFavorite.boolValue ? @", favorite" : @""), (_privateLink ? [NSString stringWithFormat:@", privateLink: %@", _privateLink] : @""), (_checksums ? [NSString stringWithFormat:@", checksums: %@", _checksums] : @""), [self _tusSupportDescription], (_downloadTriggerIdentifier ? [NSString stringWithFormat:@", downloadTrigger: %@", _downloadTriggerIdentifier] : @""), ((_fileClaim!=nil) ? @", fileClaim: yes" : @""), ((_localRelativePath!=nil) ? [NSString stringWithFormat:@", localRelativePath: %@", _localRelativePath] : @""), ((_state!=OCItemStateNormal) ? [NSString stringWithFormat:@", state: %ld", (long)_state] : @""), ((_bookmarkUUID!=nil) ? [NSString stringWithFormat:@", bookmarkUUID: %@", _bookmarkUUID] : @""), [self syncActivityDescription]]); } #pragma mark - Copying - (id)copyWithZone:(nullable NSZone *)zone { - return ([[self class] itemFromSerializedData:self.serializedData]); + OCItem *copy = [[self class] itemFromSerializedData:self.serializedData]; + + copy.bookmarkUUID = _bookmarkUUID; + + return (copy); } @end @@ -795,11 +881,15 @@ - (id)copyWithZone:(nullable NSZone *)zone OCItemDownloadTriggerID OCItemDownloadTriggerIDAvailableOffline = @"availableOffline"; OCItemPropertyName OCItemPropertyNameType = @"type"; +OCItemPropertyName OCItemPropertyNameDriveID = @"driveID"; +OCItemPropertyName OCItemPropertyNameLocationString = @"locationString"; OCItemPropertyName OCItemPropertyNamePath = @"path"; +OCItemPropertyName OCItemPropertyNameParentPath = @"parentPath"; OCItemPropertyName OCItemPropertyNameName = @"name"; OCItemPropertyName OCItemPropertyNameOwnerUserName = @"ownerUserName"; OCItemPropertyName OCItemPropertyNameSize = @"size"; OCItemPropertyName OCItemPropertyNameMIMEType = @"mimeType"; +OCItemPropertyName OCItemPropertyNameTypeAlias = @"typeAlias"; OCItemPropertyName OCItemPropertyNameLocallyModified = @"locallyModified"; OCItemPropertyName OCItemPropertyNameLocalRelativePath = @"localRelativePath"; diff --git a/ownCloudSDK/Locale/OCLocale+SystemLanguage.h b/ownCloudSDK/Locale/OCLocale+SystemLanguage.h new file mode 100644 index 00000000..ff53dbd3 --- /dev/null +++ b/ownCloudSDK/Locale/OCLocale+SystemLanguage.h @@ -0,0 +1,30 @@ +// +// OCLocale+SystemLanguage.h +// ownCloudSDK +// +// Created by Felix Schwarz on 20.10.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 "OCLocale.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCLocale (SystemLanguage) + +@property(strong,readonly,nonatomic,nullable) NSString *acceptLanguageString; +@property(strong,readonly,nonatomic,nullable) NSString *primaryUserLanguage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Locale/OCLocale+SystemLanguage.m b/ownCloudSDK/Locale/OCLocale+SystemLanguage.m new file mode 100644 index 00000000..3ca40009 --- /dev/null +++ b/ownCloudSDK/Locale/OCLocale+SystemLanguage.m @@ -0,0 +1,60 @@ +// +// OCLocale+SystemLanguage.m +// ownCloudSDK +// +// Created by Felix Schwarz on 20.10.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 "OCLocale+SystemLanguage.h" + +@implementation OCLocale (SystemLanguage) + +#pragma mark - Locale +- (NSString *)acceptLanguageString +{ + NSArray *preferredLocalizations = [NSBundle preferredLocalizationsFromArray:NSBundle.mainBundle.localizations forPreferences:nil]; + + if (preferredLocalizations.count > 0) + { + NSString *acceptLanguage; + NSMutableArray *iso6391Languages = [NSMutableArray new]; + + for (NSString *preferredLocalization in preferredLocalizations) + { + if (preferredLocalization.length > 2) + { + // Ensure that codes are formatted correctly (f.ex. en-US rather than en_US) + [iso6391Languages addObject:[preferredLocalization stringByReplacingOccurrencesOfString:@"_" withString:@"-"]]; + } + else + { + [iso6391Languages addObject:preferredLocalization]; + } + } + + if ((acceptLanguage = [iso6391Languages componentsJoinedByString:@","].lowercaseString) != nil) + { + return (acceptLanguage); + } + } + + return (nil); +} + +- (NSString *)primaryUserLanguage +{ + return ([NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations] forPreferences:nil].firstObject.lowercaseString); +} + +@end diff --git a/ownCloudSDK/Locale/OCLocale.h b/ownCloudSDK/Locale/OCLocale.h index 61cd9cbb..beb4aa82 100644 --- a/ownCloudSDK/Locale/OCLocale.h +++ b/ownCloudSDK/Locale/OCLocale.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)localizeString:(NSString *)string; + (NSString *)localizeString:(NSString *)string options:(nullable OCLocaleOptions)options; + (NSString *)localizeString:(NSString *)string table:(NSString *)table; ++ (NSString *)localizeString:(NSString *)string bundle:(NSBundle *)bundle; + (NSString *)localizeString:(NSString *)string bundleOfClass:(Class)class; + (NSString *)localizeString:(NSString *)string bundleOfClass:(Class)class options:(nullable OCLocaleOptions)options; + (NSString *)localizeString:(NSString *)string bundleOfClass:(Class)class table:(NSString *)table; diff --git a/ownCloudSDK/Locale/OCLocale.m b/ownCloudSDK/Locale/OCLocale.m index a347071b..2a39fc6f 100644 --- a/ownCloudSDK/Locale/OCLocale.m +++ b/ownCloudSDK/Locale/OCLocale.m @@ -80,6 +80,11 @@ + (NSString *)localizeString:(NSString *)string table:(NSString *)table return ([OCLocale.sharedLocale localizeString:string bundle:nil table:table options:nil]); } ++ (NSString *)localizeString:(NSString *)string bundle:(NSBundle *)bundle +{ + return ([OCLocale.sharedLocale localizeString:string bundle:bundle table:nil options:nil]); +} + + (NSString *)localizeString:(NSString *)string bundleOfClass:(Class)class { return ([OCLocale.sharedLocale localizeString:string bundle:[NSBundle bundleForClass:class] table:nil options:nil]); diff --git a/ownCloudSDK/Location/OCLocation.h b/ownCloudSDK/Location/OCLocation.h new file mode 100644 index 00000000..4ddffb68 --- /dev/null +++ b/ownCloudSDK/Location/OCLocation.h @@ -0,0 +1,79 @@ +// +// OCLocation.h +// ownCloudSDK +// +// Created by Felix Schwarz on 04.02.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 "OCTypes.h" +#import "OCBookmark.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCLocationString; //!< DriveID + path encoded into a single string. Format should be assumed private. +typedef NSData* OCLocationData; + +typedef NS_OPTIONS(NSInteger, OCLocationType) { + OCLocationTypeUnknown = 0, + + OCLocationTypeFile = (1 << 0), //!< Location points to a file + OCLocationTypeFolder = (1 << 1), //!< Location points to a folder + OCLocationTypeDrive = (1 << 2), //!< Location points to a space or its root folder + OCLocationTypeAccount = (1 << 3) //!< Location points to an entire account +}; + +@interface OCLocation : NSObject + +@property(class,strong,readonly,nonatomic) OCLocation *legacyRootLocation; ++ (OCLocation *)legacyRootPath:(nullable OCPath)path; + ++ (OCLocation *)withVFSPath:(nullable OCPath)path; + +@property(strong,nullable) OCBookmarkUUID bookmarkUUID; //!< UUID of the account containing the location. A nil value represents the account managed by the receiver of the location. + +@property(strong,nullable,nonatomic) OCDriveID driveID; //!< DriveID of the drive. A nil value indicates a legacy WebDAV endpoint path. +@property(strong,nullable) OCPath path; //!< The path of the location inside the drive and account. + +- (instancetype)initWithDriveID:(nullable OCDriveID)driveID path:(nullable OCPath)path; +- (instancetype)initWithBookmarkUUID:(nullable OCBookmarkUUID)bookmarkUUID driveID:(nullable OCDriveID)driveID path:(nullable OCPath)path; + +#pragma mark - Tools +@property(strong,nullable,readonly,nonatomic) OCLocation *parentLocation; //! Returns nil if .path is nil +@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; +@property(readonly,nonatomic) BOOL isDriveRoot; +@property(readonly,nonatomic) OCLocationType type; + ++ (BOOL)driveID:(nullable OCDriveID)driveID1 isEqualDriveID:(nullable OCDriveID)driveID2; + +- (BOOL)isLocatedIn:(nullable OCLocation *)location; + +#pragma mark - En-/Decoding as unified string +@property(strong,readonly,nonatomic) OCLocationString string; ++ (nullable instancetype)fromString:(OCLocationString)string; + +#pragma mark - En-/Decoding to opaque data +@property(strong,readonly,nullable,nonatomic) OCLocationData data; ++ (nullable instancetype)fromData:(OCLocationData)data; + +@end + +extern NSString* OCLocationDataTypeIdentifier; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Location/OCLocation.m b/ownCloudSDK/Location/OCLocation.m new file mode 100644 index 00000000..aee4da6b --- /dev/null +++ b/ownCloudSDK/Location/OCLocation.m @@ -0,0 +1,424 @@ +// +// OCLocation.m +// ownCloudSDK +// +// Created by Felix Schwarz on 04.02.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 "OCLocation.h" +#import "OCMacros.h" +#import "NSString+OCPath.h" +#import "OCDrive.h" +#import "OCLogger.h" +#import "OCDataRenderer.h" +#import "OCDataConverter.h" +#import "OCDataItemPresentable.h" +#import "OCSymbol.h" + +@interface OCLocation () +{ + OCLocation *_parentLocation; + OCLocation *_normalizedDirectoryPathLocation; + OCLocation *_normalizedFilePathLocation; +} +@end + +@implementation OCLocation + ++ (OCLocation *)legacyRootLocation +{ + return ([[OCLocation alloc] initWithDriveID:nil path:@"/"]); +} + ++ (OCLocation *)legacyRootPath:(nullable OCPath)path +{ + return ([[OCLocation alloc] initWithDriveID:nil path:path]); +} + ++ (OCLocation *)withVFSPath:(nullable OCPath)path +{ + return ([[OCLocation alloc] initWithDriveID:nil path:path]); +} + +- (instancetype)initWithDriveID:(nullable OCDriveID)driveID path:(nullable OCPath)path +{ + return ([self initWithBookmarkUUID:nil driveID:driveID path:path]); +} + +- (instancetype)initWithBookmarkUUID:(nullable OCBookmarkUUID)bookmarkUUID driveID:(nullable OCDriveID)driveID path:(nullable OCPath)path +{ + if ((self = [super init]) != nil) + { + _bookmarkUUID = bookmarkUUID; + self.driveID = driveID; + _path = path; + } + + return (self); +} + +- (void)setDriveID:(OCDriveID)driveID +{ + if ((driveID != nil) && ([driveID isKindOfClass:NSNull.class])) + { + driveID = nil; + } + + _driveID = driveID; +} + +#pragma mark - Tools +- (OCLocation *)parentLocation +{ + if (_parentLocation == nil) + { + OCPath parentPath; + + if ((parentPath = _path.parentPath) != nil) + { + _parentLocation = [[OCLocation alloc] initWithBookmarkUUID:_bookmarkUUID driveID:_driveID path:parentPath]; + } + } + + return (_parentLocation); +} + +- (OCLocation *)normalizedDirectoryPathLocation +{ + if (_normalizedDirectoryPathLocation == nil) + { + OCPath normalizedDirectoryPath; + + if ((normalizedDirectoryPath = _path.normalizedDirectoryPath) != nil) + { + _normalizedDirectoryPathLocation = [[OCLocation alloc] initWithBookmarkUUID:_bookmarkUUID driveID:_driveID path:normalizedDirectoryPath]; + } + } + + return (_normalizedDirectoryPathLocation); +} + +- (OCLocation *)normalizedFilePathLocation +{ + if (_normalizedFilePathLocation == nil) + { + OCPath normalizedFilePath; + + if ((normalizedFilePath = _path.normalizedFilePath) != nil) + { + _normalizedFilePathLocation = [[OCLocation alloc] initWithBookmarkUUID:_bookmarkUUID driveID:_driveID path:normalizedFilePath]; + } + } + + return (_normalizedFilePathLocation); +} + +- (NSString *)lastPathComponent +{ + return (_path.lastPathComponent); +} + +- (BOOL)isRoot +{ + return (_path.isRootPath); +} + +- (BOOL)isDriveRoot +{ + return (_path.isRootPath && (_driveID != nil)); +} + +- (OCLocationType)type +{ + if (_path != nil) + { + if (_path.isNormalizedDirectoryPath) + { + if (_driveID != nil) + { + if (_path.isRootPath) + { + return (OCLocationTypeDrive); + } + } + + return (OCLocationTypeFolder); + } + + return (OCLocationTypeFile); + } + + if (_driveID != nil) + { + return (OCLocationTypeDrive); + } + + if (_bookmarkUUID != nil) + { + return (OCLocationTypeAccount); + } + + return (OCLocationTypeUnknown); +} + ++ (BOOL)driveID:(nullable OCDriveID)driveID1 isEqualDriveID:(nullable OCDriveID)driveID2 +{ + driveID1 = OCDriveIDUnwrap(driveID1); + driveID2 = OCDriveIDUnwrap(driveID2); + + return ((driveID1 == driveID2) || [driveID1 isEqual:driveID2]); +} + +- (BOOL)isLocatedIn:(nullable OCLocation *)location +{ + if ((location == nil) || (location.path == nil) || (_path == nil)) { return (NO); } + + if ([OCLocation driveID:location.driveID isEqualDriveID:_driveID]) + { + return ([_path hasPrefix:location.path] && location.path.isNormalizedDirectoryPath); + } + + return (NO); +} + +#pragma mark - String composition / decomposition +- (OCLocationString)string +{ + // Format: BOOKMARKUUID;DRIVEID:PATH + // - ";" is the devider between BOOKMARKUUID and DRIVEID + // - ":" is the devider between DRIVEID and PATH + // - missing elements are encoded as empty string ("") + return ([NSString stringWithFormat:@";%@:%@", ((_driveID != nil) ? _driveID : @""), ((_path != nil) ? _path : @"")]); +} + ++ (instancetype)fromString:(OCLocationString)string +{ + NSRange semicolonRange = [string rangeOfString:@";"]; + + if (semicolonRange.location != NSNotFound) + { + NSRange colonDividerRange = [string rangeOfString:@":"]; + + if (colonDividerRange.location != NSNotFound) + { + NSString *bookmarkUUIDString = (semicolonRange.location > 0) ? [string substringWithRange:NSMakeRange(1, semicolonRange.location)] : nil; + NSString *driveID = (colonDividerRange.location > (semicolonRange.location+semicolonRange.length)) ? [string substringWithRange:NSMakeRange((semicolonRange.location+semicolonRange.length), colonDividerRange.location-(semicolonRange.location+semicolonRange.length))] : nil; + NSString *path = ((colonDividerRange.location+colonDividerRange.length) < string.length) ? [string substringFromIndex:colonDividerRange.location+colonDividerRange.length] : nil; + + OCLocation *location; + + location = [[self alloc] initWithDriveID:driveID path:path]; + if (bookmarkUUIDString != nil) + { + location.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:bookmarkUUIDString]; + } + + return (location); + } + } + + return (nil); +} + +#pragma mark - En-/Decoding to opaque data +- (OCLocationData)data +{ + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self requiringSecureCoding:YES error:&error]; + + if (error != nil) + { + OCLogError(@"Serialization of %@ to data failed with error: %@", self, error); + } + + return (data); +} + ++ (nullable instancetype)fromData:(OCLocationData)data +{ + NSError *error = nil; + OCLocation *location = [NSKeyedUnarchiver unarchivedObjectOfClass:OCLocation.class fromData:data error:&error]; + + if (error != nil) + { + OCLogError(@"Deserialization of data to OCLocation failed with error: %@", error); + } + + return (location); +} + +#pragma mark - Secure coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _bookmarkUUID = [decoder decodeObjectOfClass:NSUUID.class forKey:@"bookmarkUUID"]; + _driveID = [decoder decodeObjectOfClass:NSString.class forKey:@"driveID"]; + _path = [decoder decodeObjectOfClass:NSString.class forKey:@"path"]; + } + + return (self); +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_bookmarkUUID forKey:@"bookmarkUUID"]; + [coder encodeObject:_driveID forKey:@"driveID"]; + [coder encodeObject:_path forKey:@"path"]; +} + +#pragma mark - Copying +- (id)copyWithZone:(NSZone *)zone +{ + OCLocation *location = [OCLocation new]; + + location->_bookmarkUUID = _bookmarkUUID; + location->_driveID = _driveID; + location->_path = _path; + + return (location); +} + +#pragma mark - Description +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@>", NSStringFromClass(self.class), self, + OCExpandVar(driveID), + OCExpandVar(path) + ]); +} + +#pragma mark - Comparison +- (NSUInteger)hash +{ + return (_driveID.hash ^ _path.hash); +} + +- (BOOL)isEqual:(id)object +{ + OCLocation *otherLocation = OCTypedCast(object, OCLocation); + + if (otherLocation != nil) + { + #define compareVar(var) ((otherLocation->var == var) || [otherLocation->var isEqual:var]) + + if (compareVar(_driveID) && compareVar(_path)) + { + if (_driveID != nil) + { + // If driveID is set and identical, assume the _bookmarkUUID is identical + // if one location has it set and the other hasn't + if (((_bookmarkUUID != nil) && (otherLocation->_bookmarkUUID == nil)) || + ((_bookmarkUUID == nil) && (otherLocation->_bookmarkUUID != nil))) + { + return (YES); + } + } + + // Compare _bookmarkUUID for identity + return (compareVar(_bookmarkUUID)); + } + } + + return (NO); +} + +#pragma mark - OCDataItem +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeLocation); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.string); +} + +#pragma mark - OCDataItemVersioning +- (OCDataItemVersion)dataItemVersion +{ + // Version remains the same as the reference changes when the content changes + return (@(0)); +} + +#pragma mark - OCDataConverter for OCLocation ++ (void)load +{ + OCDataConverter *locationToPresentableConverter; + + locationToPresentableConverter = [[OCDataConverter alloc] initWithInputType:OCDataItemTypeLocation outputType:OCDataItemTypePresentable conversion:^id _Nullable(OCDataConverter * _Nonnull converter, OCLocation * _Nullable inLocation, OCDataRenderer * _Nullable renderer, NSError * _Nullable __autoreleasing * _Nullable outError, OCDataViewOptions _Nullable options) { + OCDataItemPresentable *presentable = nil; + + if (inLocation != nil) + { + presentable = [[OCDataItemPresentable alloc] initWithItem:inLocation]; + presentable.title = inLocation.lastPathComponent; + + switch (inLocation.type) + { + case OCLocationTypeFile: + presentable.image = [OCSymbol iconForSymbolName:@"doc"]; + break; + + case OCLocationTypeDrive: + presentable.image = [OCSymbol iconForSymbolName:@"square.grid.2x2"]; + break; + + default: + case OCLocationTypeFolder: + if (inLocation.isRoot) + { + if (inLocation.driveID == nil) + { + // Identical to ocis + // presentable.title = OCLocalized(@"Personal"); + // presentable.image = [OCSymbol iconForSymbolName:@"person"]; + + // OC10 style + presentable.title = OCLocalized(@"Files"); + presentable.image = [OCSymbol iconForSymbolName:@"folder"]; + } + else + { + presentable.image = [OCSymbol iconForSymbolName:@"square.grid.2x2"]; + } + } + else + { + presentable.image = [OCSymbol iconForSymbolName:@"folder"]; + } + break; + + case OCLocationTypeAccount: + presentable.image = [OCSymbol iconForSymbolName:@"person"]; + break; + } + } + + return (presentable); + }]; + + [OCDataRenderer.defaultRenderer addConverters:@[ + locationToPresentableConverter + ]]; +} + +@end + +NSString* OCLocationDataTypeIdentifier = @"com.owncloud.ios-app.location-data"; diff --git a/ownCloudSDK/Logging/OCLogger.h b/ownCloudSDK/Logging/OCLogger.h index 2fa51b7d..e64099d8 100644 --- a/ownCloudSDK/Logging/OCLogger.h +++ b/ownCloudSDK/Logging/OCLogger.h @@ -141,6 +141,7 @@ extern OCClassSettingsKey OCClassSettingsKeyLogOnlyMatching; extern OCClassSettingsKey OCClassSettingsKeyLogOmitMatching; extern OCClassSettingsKey OCClassSettingsKeyLogBlankFilteredMessages; extern OCClassSettingsKey OCClassSettingsKeyLogSingleLined; +extern OCClassSettingsKey OCClassSettingsKeyLogReplaceNewLine; extern OCClassSettingsKey OCClassSettingsKeyLogMaximumLogMessageSize; extern OCClassSettingsKey OCClassSettingsKeyLogFormat; diff --git a/ownCloudSDK/Logging/OCLogger.m b/ownCloudSDK/Logging/OCLogger.m index 3e70fa84..f0107f9d 100644 --- a/ownCloudSDK/Logging/OCLogger.m +++ b/ownCloudSDK/Logging/OCLogger.m @@ -43,6 +43,7 @@ static OCLogFormat sOCLogFormat; static BOOL sOCLogSingleLined; +static BOOL sOCLogReplaceNewLine; static NSUInteger sOCLogMessageMaximumSize; static OCLogger *sharedLogger; @@ -207,7 +208,8 @@ + (OCClassSettingsIdentifier)classSettingsIdentifier OCClassSettingsKeyLogSynchronousLogging : @(NO), OCClassSettingsKeyLogBlankFilteredMessages : @(NO), OCClassSettingsKeyLogColored : @(NO), - OCClassSettingsKeyLogSingleLined : @(YES), + OCClassSettingsKeyLogSingleLined : @(NO), + OCClassSettingsKeyLogReplaceNewLine : @(YES), OCClassSettingsKeyLogMaximumLogMessageSize : @(0), OCClassSettingsKeyLogFormat : @"text" }); @@ -358,6 +360,14 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCClassSettingsMetadataKeyCategory : @"Logging", OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) + }, + + OCClassSettingsKeyLogReplaceNewLine : @{ + OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean, + OCClassSettingsMetadataKeyDescription : @"Controls whether messages spanning more than one line should be logged as a single line, after replacing new line characters with \"\\n\".", + OCClassSettingsMetadataKeyCategory : @"Logging", + OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, + OCClassSettingsMetadataKeyFlags : @(OCClassSettingsFlagDenyUserPreferences) } }); } @@ -408,6 +418,7 @@ + (OCLogLevel)logLevel } withOwner:self]; sOCLogSingleLined = [[self classSettingForOCClassSettingsKey:OCClassSettingsKeyLogSingleLined] boolValue]; + sOCLogReplaceNewLine = [[self classSettingForOCClassSettingsKey:OCClassSettingsKeyLogReplaceNewLine] boolValue]; sOCLogMessageMaximumSize = [[self classSettingForOCClassSettingsKey:OCClassSettingsKeyLogMaximumLogMessageSize] unsignedIntegerValue]; @@ -674,15 +685,28 @@ - (void)_rawAppendLogLevel:(OCLogLevel)logLevel functionName:(NSString * _Nullab NSArray *lines = nil; - if (sOCLogSingleLined) + if (sOCLogReplaceNewLine || sOCLogSingleLined) { - static NSString *splitNewLine; + static NSString *newLineString; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - splitNewLine = [[NSString alloc] initWithFormat:@"\n"]; + newLineString = [[NSString alloc] initWithFormat:@"\n"]; }); - lines = [logMessage componentsSeparatedByString:splitNewLine]; + if (sOCLogReplaceNewLine) + { + static NSString *newLineReplacementString; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + newLineReplacementString = [[NSString alloc] initWithFormat:@"\\n"]; + }); + + logMessage = [logMessage stringByReplacingOccurrencesOfString:newLineString withString:newLineReplacementString]; + } + else if (sOCLogSingleLined) + { + lines = [logMessage componentsSeparatedByString:newLineString]; + } } for (OCLogWriter *writer in self->_writers) @@ -1033,5 +1057,6 @@ @implementation NSArray (OCLogTagMerge) OCClassSettingsKey OCClassSettingsKeyLogOmitMatching = @"omit-matching"; OCClassSettingsKey OCClassSettingsKeyLogBlankFilteredMessages = @"blank-filtered-messages"; OCClassSettingsKey OCClassSettingsKeyLogSingleLined = @"single-lined"; +OCClassSettingsKey OCClassSettingsKeyLogReplaceNewLine = @"replace-newline"; OCClassSettingsKey OCClassSettingsKeyLogMaximumLogMessageSize = @"maximum-message-size"; OCClassSettingsKey OCClassSettingsKeyLogFormat = @"format"; diff --git a/ownCloudSDK/OCMacros.h b/ownCloudSDK/OCMacros.h index 17f61054..c4d0fca4 100644 --- a/ownCloudSDK/OCMacros.h +++ b/ownCloudSDK/OCMacros.h @@ -23,6 +23,7 @@ #define OCLocalizedString(key,comment) [OCLocale localizeString:key bundleOfClass:[self class]] #define OCLocalized(key) [OCLocale localizeString:key bundleOfClass:[self class]] +#define OCLocalizedViaLocalizationBundle(key) [OCLocale localizeString:key bundle:localizationBundle] #define OCLocalizedFormat(key,variables) [OCLocale localizeString:key bundleOfClass:[self class] options:@{ OCLocaleOptionKeyVariables : variables }] // Macros to simplify usage of dispatch groups (and allow switching to more efficient mechanisms in the future) @@ -33,6 +34,7 @@ #define OCWaitDidFinishTask(label) dispatch_group_leave(label) #define OCWaitForCompletion(label) dispatch_group_wait(label, DISPATCH_TIME_FOREVER) #define OCWaitForCompletionWithTimeout(label,timeout) dispatch_group_wait(label, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))) +#define OCWaitOnCompletion(label,queue,block) dispatch_group_notify(label, queue, block); // Macros to simplify the use of async APIs in a synchronous fashion #define OCSyncExec(label,code) dispatch_semaphore_t label = dispatch_semaphore_create(0); \ @@ -42,9 +44,17 @@ #define OCSyncExecDone(label) dispatch_semaphore_signal(label) #define OCTypedCast(var,className) ([var isKindOfClass:[className class]] ? ((className *)var) : nil) +#define OCConformanceCast(var,protocolName) ([var conformsToProtocol:@protocol(protocolName)] ? ((id)var) : nil) // nil-aware comparisons #define OCNANotEqual(a,b) ((![(a) isEqual:(b)]) && ((a) != (b))) #define OCNAIsEqual(a,b) (([(a) isEqual:(b)]) || ((a) == (b))) +// debug description +#define OCExpandVar(var) ((_##var!=nil) ? [@", " #var ": " stringByAppendingString:_##var.description] : @"") + +// nil -> NSNull wrapper/unwrapper +#define OCNullProtect(object) (((object)!=nil) ? (object) : NSNull.null) +#define OCNullResolved(object) ([(object) isKindOfClass:[NSNull class]] ? nil : (object)) + #endif /* OCMacros_h */ diff --git a/ownCloudSDK/OCTypes.h b/ownCloudSDK/OCTypes.h index 3e887d07..c8ec3a4d 100644 --- a/ownCloudSDK/OCTypes.h +++ b/ownCloudSDK/OCTypes.h @@ -21,6 +21,10 @@ typedef NSString* OCPath; //!< NSString representing the path relative to the server's root directory. +typedef NSString* OCDriveID; //!< Unique identifier for an oCIS drive / space. oC10 items have no drive ID. + +typedef NSUInteger OCSeed; //!< Generic seed value type. Higher values mean newer objects. + typedef NSString* OCLocalID; //!< Unique local identifier of the item (persists over lifetime of file, incl. across modifications and placeholder -> item transition). typedef NSString* OCFileID; //!< Unique identifier of the item on the server (persists over lifetime of file, incl. across modifications) (files and folders) @@ -28,6 +32,10 @@ typedef NSString* OCFileETag; //!< Identifier unique to a specific combination o typedef NSString* OCFileIDUniquePrefix; //!< Unique fileID prefix of an item on the server. Background is that OC 10 FileIDs are composed of an 8-digit (%08ld) number and the server's ID (apparently identical across files). That number is unique for every file and also used as the number component in OC10 private links. By using a prefix here, it's possible to support both OC10-style fileID prefixes as well as future full-length fileIDs for searching for items. +typedef NSString* OCMIMEType; //!< MIME Type (f.ex. "application/msexcel") +typedef NSString* OCTypeAlias; //!< Type Alias (f.ex. "x-office/spreadsheet" for "application/msexcel"). For MIME types where no alias exists, the alias is (OCTypeAliasMIMEPrefix + mimeType) +typedef NSString* OCFileExtension; //!< File extension string (without leading dot) + typedef NSString* OCLocalAttribute NS_TYPED_ENUM; //!< Identifier uniquely identifying a local attribute typedef NSNumber* OCItemFavorite; //!< Favorite status of an item (boolean) diff --git a/ownCloudSDK/Platforms/OCPlatform.h b/ownCloudSDK/Platforms/OCPlatform.h new file mode 100644 index 00000000..8918ccb6 --- /dev/null +++ b/ownCloudSDK/Platforms/OCPlatform.h @@ -0,0 +1,43 @@ +// +// OCPlatform.h +// ownCloudSDK +// +// Created by Felix Schwarz on 17.01.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 + +#pragma mark - iOS, iPadOS, watchOS, tvOS + +#if TARGET_OS_IPHONE +#import + +#define OCView UIView +#endif /* TARGET_OS_IPHONE */ + +#pragma mark - macOS + +#if TARGET_OS_OSX +#import + +#define OCView NSView +#endif /* TARGET_OS_OSX */ + +NS_ASSUME_NONNULL_BEGIN + +@interface OCPlatform : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Platforms/OCPlatform.m b/ownCloudSDK/Platforms/OCPlatform.m new file mode 100644 index 00000000..07026d0c --- /dev/null +++ b/ownCloudSDK/Platforms/OCPlatform.m @@ -0,0 +1,23 @@ +// +// OCPlatform.m +// ownCloudSDK +// +// Created by Felix Schwarz on 17.01.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 "OCPlatform.h" + +@implementation OCPlatform + +@end diff --git a/ownCloudSDK/Protocols/OCViewProvider.h b/ownCloudSDK/Protocols/OCViewProvider.h new file mode 100644 index 00000000..ad498032 --- /dev/null +++ b/ownCloudSDK/Protocols/OCViewProvider.h @@ -0,0 +1,31 @@ +// +// OCViewProvider.h +// ownCloudSDK +// +// Created by Felix Schwarz on 17.01.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 "OCPlatform.h" +#import "OCViewProviderContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol OCViewProvider + +@required +- (void)provideViewForSize:(CGSize)size inContext:(nullable OCViewProviderContext *)context completion:(void(^)(OCView * _Nullable view))completionHandler; //!< Returns a view suitable to display the object. Sizes are only passed to allow optimization. Pass CGSizeZero if the size at which the object will be displayed is unknown. + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Protocols/OCViewProviderContext.h b/ownCloudSDK/Protocols/OCViewProviderContext.h new file mode 100644 index 00000000..f8cb8ea2 --- /dev/null +++ b/ownCloudSDK/Protocols/OCViewProviderContext.h @@ -0,0 +1,41 @@ +// +// OCViewProviderContext.h +// ownCloudSDK +// +// Created by Felix Schwarz on 17.01.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 . + * + */ + +/* + The View Provider Context can be used to pass a set of attributes to a view provider, f.ex. to allow further customization or hooks. + OCViewProviders may only read but not alter the context, so the contact can be initialized once and then passed to an unlimited number of OCViewProviders, i.e. in a large list. +*/ + +#import + +NS_ASSUME_NONNULL_BEGIN + +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 +extern OCViewProviderContextKey OCViewProviderContextKeyUseCircular; //!< BOOL for using a circular masking view or not + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Protocols/OCViewProviderContext.m b/ownCloudSDK/Protocols/OCViewProviderContext.m new file mode 100644 index 00000000..799680ce --- /dev/null +++ b/ownCloudSDK/Protocols/OCViewProviderContext.m @@ -0,0 +1,36 @@ +// +// OCViewProviderContext.m +// ownCloudSDK +// +// Created by Felix Schwarz on 17.01.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 "OCViewProviderContext.h" + +@implementation OCViewProviderContext + +- (instancetype)initWithAttributes:(NSDictionary *)attributes +{ + if ((self = [super init]) != nil) + { + _attributes = attributes; + } + + return (self); +} + +@end + +OCViewProviderContextKey OCViewProviderContextKeyContentMode = @"contentMode"; +OCViewProviderContextKey OCViewProviderContextKeyUseCircular = @"useCircular"; diff --git a/ownCloudSDK/Query/Condition/OCQueryCondition.h b/ownCloudSDK/Query/Condition/OCQueryCondition.h index 57b783a6..1150a4bc 100644 --- a/ownCloudSDK/Query/Condition/OCQueryCondition.h +++ b/ownCloudSDK/Query/Condition/OCQueryCondition.h @@ -38,6 +38,8 @@ typedef NS_ENUM(NSUInteger, OCQueryConditionOperator) OCQueryConditionOperatorNegate }; +typedef NSString* OCQueryConditionUserInfoKey _NS_TYPED_ENUM; + @interface OCQueryCondition : NSObject @property(assign) OCQueryConditionOperator operator; @@ -50,6 +52,8 @@ typedef NS_ENUM(NSUInteger, OCQueryConditionOperator) @property(strong,nullable) NSNumber *maxResultCount; //!< If non-nil, the maximum number of results to fetch from a database +@property(strong,nullable) NSDictionary *userInfo; //!< User info dictionary, not used by the OCQueryCondition itself + + (instancetype)where:(OCItemPropertyName)property isGreaterThan:(id)value; + (instancetype)where:(OCItemPropertyName)property isLessThan:(id)value; + (instancetype)where:(OCItemPropertyName)property isEqualTo:(id)value; diff --git a/ownCloudSDK/Query/Condition/OCQueryCondition.m b/ownCloudSDK/Query/Condition/OCQueryCondition.m index f22220db..be19a840 100644 --- a/ownCloudSDK/Query/Condition/OCQueryCondition.m +++ b/ownCloudSDK/Query/Condition/OCQueryCondition.m @@ -124,6 +124,8 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _sortAscending = [decoder decodeBoolForKey:@"sortAscending"]; _maxResultCount = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"maxResultCount"]; + + _userInfo = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"userInfo"]; } return (self); @@ -140,6 +142,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeBool:_sortAscending forKey:@"sortAscending"]; [coder encodeObject:_maxResultCount forKey:@"maxResultCount"]; + + [coder encodeObject:_userInfo forKey:@"userInfo"]; } #pragma mark - Description diff --git a/ownCloudSDK/Query/OCQuery+Internal.h b/ownCloudSDK/Query/OCQuery+Internal.h index d6868980..1ae82494 100644 --- a/ownCloudSDK/Query/OCQuery+Internal.h +++ b/ownCloudSDK/Query/OCQuery+Internal.h @@ -17,6 +17,7 @@ */ #import "OCQuery.h" +#import "OCDataTypes.h" @interface OCQuery (Internal) @@ -28,8 +29,12 @@ - (OCCoreItemList *)fullQueryResultsItemList; +#pragma mark - Data source +- (void)updateDataSourceSpecialItemsForItems:(NSArray *)items; + #pragma mark - Update processed results - (void)updateProcessedResultsIfNeeded:(BOOL)ifNeeded; +- (OCDataSourceState)_dataSourceState; #pragma mark - Needs recomputation - (void)setNeedsRecomputation; diff --git a/ownCloudSDK/Query/OCQuery+Internal.m b/ownCloudSDK/Query/OCQuery+Internal.m index a0b13c71..d3904c0c 100644 --- a/ownCloudSDK/Query/OCQuery+Internal.m +++ b/ownCloudSDK/Query/OCQuery+Internal.m @@ -18,6 +18,7 @@ #import "OCQuery+Internal.h" #import "OCCoreItemList.h" +#import "OCStatistic.h" @implementation OCQuery (Internal) @@ -27,6 +28,7 @@ - (void)setFullQueryResults:(NSMutableArray *)fullQueryResults @synchronized(self) { _fullQueryResults = fullQueryResults; + _fullQueryResultsSetOnce = YES; // Release cached item list _fullQueryResultsItemList = nil; @@ -154,17 +156,98 @@ - (void)updateProcessedResultsIfNeeded:(BOOL)ifNeeded // We just recomputed _processedQueryResults = newProcessedResults; _needsRecomputation = NO; + + _queryResultsDataSource.state = [self _dataSourceState]; + [self updateDataSourceSpecialItemsForItems:newProcessedResults]; + [_queryResultsDataSource setVersionedItems:(NSArray> *)newProcessedResults]; + } + } +} + +- (void)updateDataSourceSpecialItemsForItems:(NSArray *)items +{ + if (self.queryResultsDataSourceIncludesStatistics) + { + NSUInteger fileCount = 0, folderCount = 0, sizeInBytes = 0; + + for (OCItem *item in items) + { + switch (item.type) + { + case OCItemTypeFile: + fileCount += 1; + break; + + case OCItemTypeCollection: + folderCount += 1; + break; + } + + NSInteger size = item.size; + + if (size > 0) + { + sizeInBytes += item.size; + } } + + OCStatistic *statistic = [OCStatistic new]; + + statistic.itemCount = @(folderCount + fileCount); + statistic.folderCount = @(folderCount); + statistic.fileCount = @(fileCount); + statistic.sizeInBytes = @(sizeInBytes); + + _queryResultsDataSource.specialItems = @{ + OCDataSourceSpecialItemFolderStatistics : statistic + }; + } +} + +- (OCDataSourceState)_dataSourceState +{ + switch (self.state) + { + case OCQueryStateStarted: + return (OCDataSourceStateLoading); + break; + + case OCQueryStateWaitingForServerReply: + if (!_fullQueryResultsSetOnce) + { + return (OCDataSourceStateLoading); + } + + case OCQueryStateStopped: + case OCQueryStateContentsFromCache: + case OCQueryStateTargetRemoved: + case OCQueryStateIdle: + return (OCDataSourceStateIdle); + break; } + + return (OCDataSourceStateIdle); } #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 bf15a4cd..81cb145b 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,8 @@ 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. + BOOL _fullQueryResultsSetOnce; // YES if fullQueryResults have been set at least once + OCDataSourceArray *_queryResultsDataSource; OCCoreItemList *_fullQueryResultsItemList; // Cached item list of _fullQueryResults used in the default @@ -87,9 +90,9 @@ typedef void(^OCQueryCustomSource)(OCCore *core, OCQuery *query, OCQueryCustomRe #pragma mark - Initializers // Native queries -+ (instancetype)queryForPath:(OCPath)queryPath; //!< Query for directory -+ (instancetype)queryWithItem:(OCItem *)item; //!< Query for single file item -+ (instancetype)queryForChangesSinceSyncAnchor:(OCSyncAnchor)syncAnchor; //!< Query for changed folders since (but not including) a particular sync anchor ++ (instancetype)queryForLocation:(OCLocation *)location; //!< Query for directory ++ (instancetype)queryWithItem:(OCItem *)item; //!< Query for single file item ++ (instancetype)queryForChangesSinceSyncAnchor:(OCSyncAnchor)syncAnchor; //!< Query for changed folders since (but not including) a particular sync anchor // Custom queries /** @@ -109,7 +112,7 @@ typedef void(^OCQueryCustomSource)(OCCore *core, OCQuery *query, OCQueryCustomRe + (instancetype)queryWithCondition:(OCQueryCondition *)condition inputFilter:(nullable id)inputFilter; //!< Custom query for items matching the condition. An inputFilter is automatically generated from the condition. However, for best performance, an inputFilter should be supplied where possible. #pragma mark - Location -@property(nullable, strong) OCPath queryPath; //!< Path targeted by the query, relative to the server's root directory. +@property(nullable, strong) OCLocation *queryLocation; //!< Location targeted by the query, relative to the server's root directory. @property(nullable, strong) OCItem *queryItem; //!< For queries targeting single items, the item being targeted by the query. @property(nullable, strong) OCSyncAnchor querySinceSyncAnchor; //!< For queries targeting all changes occuring since a particular sync anchor. @@ -122,8 +125,8 @@ typedef void(^OCQueryCustomSource)(OCCore *core, OCQuery *query, OCQueryCustomRe - (void)updateWithAddedItems:(nullable OCCoreItemList *)addedItems updatedItems:(nullable OCCoreItemList *)updatedItems removedItems:(nullable OCCoreItemList *)removedItems; //!< OCQuery subclasses can provide their own custom updating logic for custom queries (.isCustom = YES) here. The default implementation uses .inputFilter in combination with -modifyFullQueryResults: to keep the internal full query results up-to-date. #pragma mark - State -@property(assign) OCQueryState state; //!< Current state of the query -@property(strong,nullable) OCCancelAction *stopAction; //!< Cancel action thats invoked when the query +@property(assign) OCQueryState state; //!< Current state of the query +@property(strong,nullable) OCCancelAction *stopAction; //!< Cancel action thats invoked when the query is stopped #pragma mark - Sorting @property(nullable,copy,nonatomic) NSComparator sortComparator; //!< Comparator used to sort the query results @@ -137,10 +140,13 @@ 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 .queryLocation/.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. +@property(assign) BOOL queryResultsDataSourceIncludesStatistics; //!< Controls whether the data source also provide an OCStatistic as special item. Ideally, this should be set before starting the query as special items currently don't trigger updates when changed independent of items. Defaults to NO. + #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 24f6f42a..2caa110a 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 @@ -28,7 +29,7 @@ @implementation OCQuery #pragma mark - Location -@synthesize queryPath = _queryPath; +@synthesize queryLocation = _queryLocation; @synthesize queryItem = _queryItem; #pragma mark - State @@ -51,14 +52,14 @@ @implementation OCQuery @synthesize changesAvailableNotificationHandler = _changesAvailableNotificationHandler; #pragma mark - Initializers -+ (instancetype)queryForPath:(OCPath)queryPath ++ (instancetype)queryForLocation:(OCLocation *)location { OCQuery *query = [self new]; - query.queryPath = queryPath; + query.queryLocation = location; query.includeRootItem = NO; - OCMeasurement *measurement = [OCMeasurement measurementWithTitle:[NSString stringWithFormat:@"Query for path %@", queryPath]]; + OCMeasurement *measurement = [OCMeasurement measurementWithTitle:[NSString stringWithFormat:@"Query for path %@ on drive %@", location.path, location.driveID]]; [query attachMeasurement:measurement]; return (query); @@ -352,6 +353,23 @@ - (void)removeFilter:(id)filter return (queryResults); } +#pragma mark - Data sources +- (OCDataSource *)queryResultsDataSource +{ + @synchronized(self) + { + if (_queryResultsDataSource == nil) + { + _queryResultsDataSource = [[OCDataSourceArray alloc] init]; + + [self updateDataSourceSpecialItemsForItems:_processedQueryResults]; + [_queryResultsDataSource setVersionedItems:_processedQueryResults]; + } + } + + return (_queryResultsDataSource); +} + #pragma mark - Change Sets - (void)setHasChangesAvailable:(BOOL)hasChangesAvailable { @@ -470,8 +488,8 @@ - (NSString *)_stateAsString - (NSString *)description { return ([NSString stringWithFormat:@"<%@: %p, %@=%@, state=%@>", NSStringFromClass(self.class), self, - ((self.queryPath != nil) ? @"path" : ((self.queryItem != nil) ? @"item" : @"?")), - ((self.queryPath != nil) ? self.queryPath : ((self.queryItem != nil) ? [NSString stringWithFormat:@"%@ (localID: %@)", self.queryItem.path, self.queryItem.localID] : @"?")), + ((self.queryLocation != nil) ? @"location" : ((self.queryItem != nil) ? @"item" : @"?")), + ((self.queryLocation != nil) ? self.queryLocation : ((self.queryItem != nil) ? [NSString stringWithFormat:@"%@ (localID: %@)", self.queryItem.location, self.queryItem.localID] : @"?")), [self _stateAsString] ]); } diff --git a/ownCloudSDK/Resource Management/OCBookmarkManager.h b/ownCloudSDK/Resource Management/OCBookmarkManager.h index d3a99866..b8f30457 100644 --- a/ownCloudSDK/Resource Management/OCBookmarkManager.h +++ b/ownCloudSDK/Resource Management/OCBookmarkManager.h @@ -19,6 +19,7 @@ #import #import "OCBookmark.h" #import "OCIPNotificationCenter.h" +#import "OCDataSource.h" NS_ASSUME_NONNULL_BEGIN @@ -49,6 +50,9 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)updateBookmark:(OCBookmark *)bookmark; //!< Notify the manager that properties of the bookmark have been changed. Will return YES if the bookmark is managed by the manager, NO if it's not. +#pragma mark - Data sources +@property(readonly, nonatomic, strong) OCDataSource *bookmarksDatasource; + #pragma mark - Acessing bookmarks - (nullable OCBookmark *)bookmarkAtIndex:(NSUInteger)index; - (nullable OCBookmark *)bookmarkForUUID:(OCBookmarkUUID)uuid; diff --git a/ownCloudSDK/Resource Management/OCBookmarkManager.m b/ownCloudSDK/Resource Management/OCBookmarkManager.m index 1c129b09..4aee1ef0 100644 --- a/ownCloudSDK/Resource Management/OCBookmarkManager.m +++ b/ownCloudSDK/Resource Management/OCBookmarkManager.m @@ -19,8 +19,13 @@ #import "OCBookmarkManager.h" #import "OCAppIdentity.h" #import "OCLogger.h" +#import "OCDataSourceArray.h" +#import "OCBookmark+DataItem.h" @implementation OCBookmarkManager +{ + OCDataSourceArray *_bookmarksDatasource; +} @synthesize bookmarks = _bookmarks; @@ -77,11 +82,79 @@ - (void)loadBookmarks { if ((bookmarkData = [[NSData alloc] initWithContentsOfURL:bookmarkStoreURL]) != nil) { - NSMutableArray *loadedBookmarks = nil; + NSMutableArray *reconstructedBookmarks = nil; @try { + NSArray *existingBookmarks = nil; + NSMutableArray *loadedBookmarks = nil; + loadedBookmarks = [NSKeyedUnarchiver unarchiveObjectWithData:bookmarkData]; + + @synchronized(self) + { + existingBookmarks = [_bookmarks copy]; + } + + if (existingBookmarks != nil) + { + // Look for changed bookmarks and update only those that don't match the existing instances + reconstructedBookmarks = [NSMutableArray new]; + + for (OCBookmark *loadedBookmark in loadedBookmarks) + { + OCBookmark *existingBookmark = nil; + + for (OCBookmark *bookmark in existingBookmarks) + { + if ([bookmark.uuid isEqual:loadedBookmark.uuid]) + { + existingBookmark = bookmark; + break; + } + } + + if (existingBookmark == nil) + { + // New bookmark + [reconstructedBookmarks addObject:loadedBookmark]; + } + else + { + // Existing bookmark - check for changes + NSError *error = nil; + NSData *existingBookmarkData = nil, *loadedBookmarkData = nil; + BOOL isIdentical = NO; + + if ((existingBookmarkData = [NSKeyedArchiver archivedDataWithRootObject:existingBookmark requiringSecureCoding:NO error:&error]) != nil) + { + if ((loadedBookmarkData = [NSKeyedArchiver archivedDataWithRootObject:loadedBookmark requiringSecureCoding:NO error:&error]) != nil) + { + if ([existingBookmarkData isEqual:loadedBookmarkData]) + { + isIdentical = YES; + } + } + } + + if (isIdentical) + { + // Bookmark unchanged - use existing copy + [reconstructedBookmarks addObject:existingBookmark]; + } + else + { + // Bookmark changed - use loaded copy + [reconstructedBookmarks addObject:loadedBookmark]; + } + } + } + } + else + { + // No bookmarks previously loaded - just use the loaded ones + reconstructedBookmarks = loadedBookmarks; + } } @catch(NSException *exception) { OCLogError(@"Error loading bookmarks: %@", OCLogPrivate(exception)); @@ -89,14 +162,16 @@ - (void)loadBookmarks @synchronized(self) { - if (loadedBookmarks != nil) + if (reconstructedBookmarks != nil) { - _bookmarks = loadedBookmarks; + _bookmarks = reconstructedBookmarks; } else { [_bookmarks removeAllObjects]; } + + [_bookmarksDatasource setVersionedItems:_bookmarks]; } } else @@ -104,6 +179,7 @@ - (void)loadBookmarks @synchronized(self) { [_bookmarks removeAllObjects]; + [_bookmarksDatasource setVersionedItems:_bookmarks]; } } } @@ -172,6 +248,8 @@ - (void)addBookmark:(OCBookmark *)bookmark @synchronized(self) { [_bookmarks addObject:bookmark]; + + [_bookmarksDatasource setVersionedItems:_bookmarks]; } [self saveBookmarks]; @@ -184,6 +262,8 @@ - (void)removeBookmark:(OCBookmark *)bookmark @synchronized(self) { [_bookmarks removeObject:bookmark]; + + [_bookmarksDatasource setVersionedItems:_bookmarks]; } [self saveBookmarks]; @@ -197,6 +277,8 @@ - (void)moveBookmarkFrom:(NSUInteger)fromIndex to:(NSUInteger)toIndex [_bookmarks removeObject:bookmark]; [_bookmarks insertObject:bookmark atIndex:toIndex]; + + [_bookmarksDatasource setVersionedItems:_bookmarks]; } [self saveBookmarks]; @@ -215,12 +297,31 @@ - (BOOL)updateBookmark:(OCBookmark *)bookmark if (saveAndPostUpdate) { + @synchronized (self) + { + // [_bookmarksDatasource setItems:_bookmarks updated:[NSSet setWithObject:bookmark]] is more accurate, but leads to unnecessary recreation and navigation issues since this forces a recreation of mapped objects in the client sidebar even if there are no relevant changes. Therefore, make sure that user-facing important changes are included in OCBookmark+DataItem versioning - and depend on its mechanisms for this data source. + [_bookmarksDatasource setVersionedItems:_bookmarks]; + } [self saveBookmarks]; } return (saveAndPostUpdate); } +#pragma mark - Data sources +- (OCDataSource *)bookmarksDatasource +{ + @synchronized(self) + { + if (_bookmarksDatasource == nil) + { + _bookmarksDatasource = [[OCDataSourceArray alloc] initWithItems:_bookmarks]; + _bookmarksDatasource.trackItemVersions = YES; + } + } + return (_bookmarksDatasource); +} + #pragma mark - Acessing bookmarks - (OCBookmark *)bookmarkAtIndex:(NSUInteger)index { diff --git a/ownCloudSDK/Resources/de.lproj/Localizable.strings b/ownCloudSDK/Resources/de.lproj/Localizable.strings index 101375704c0a5dc8f5ac0c98a6c1f3bb18997ebf..ae4cc5c4036ac7646e765e25869d7fd704cd0403 100644 GIT binary patch delta 6631 zcmcIpTWnO<6+KW4jsY8Y=EfLz?CU`eF|`AhBQ=&usWFCFCfLO`sA)+UKQNB%8GAg2 zKuJfbD*937rCn7~q((*RcN;W+m0FcZO@G5jD^>j@= z&Bsp+447&B%V98!4WU`aqFMaS;aM|_CWFy=d}T3r&>S*#AT$=AmC@}T4aczOqPdUt zi+Bs=pKm3KPlajIZ`XHVbPfA2f)eLf@jh=U`k5SNW-yn>tY_KHAGdtWV%MzQSGju> zyJj%X-C69mryetdAejR>E}p}YmqCOg6%1K(S{aAVkbwP%l|bEy+)lee#3WCu=hJ&dRM^OX-8)$J^w+retiI{{PnUy<3UzO|4VT{<)-ji)hc1H-2$kQsGCG0uPqsfB&cPNm*pRX%DyKnC{`7~9( zB`@D>4dne)Ya2IF3!$0B;wdh-PuALZuJpCm-Yv~(^IiNSSETIdzHVNZH5DMjGcluVwENe|<`y*!bxy5@(f%p?7No7EvuUHfZzKb`(6Z|bJUtg2D!tnF z$6cFaFS4uAT~(mm{rIX%qy_Qtzfh;g92UI zg@r-O)B(^5F}I9$WN;eCdD^QM7moI%Ib%*?O^5t0Z0H^-3pH4=r;?v&kK7It-?|56 zUc@OpiAvRQ6X#@3Avnc41~qz{ zhDXn99Rlh>P#FhYXdqG~{A`dc;=`$S7{^`4p(H|4s9X|N6Zl{@WEq>YM`bJml+Cf& zdMeAQY83B_ND>;4D^DoKj|eS?mqCWEz?5Mf1X>Sa!5l!Xo=+K8tj_s1QvvnU7-wiw z?R_qjU%^tUMWa|~Uc&Y{3lVsb41B#wF1(gLlt#E_n7o1~9}7~u71Nj>nrJxb-re=C7Ee z322Z7a}QtYMxi0r$T<@w%}vK=G@~=y_#=1HsiJc?)&rBE$W*I{i(6s+jJoLNGOfkv zF~Xr>!P&FsHONXw0@MzJc`Bh&P%44AV}{MyP5dHiGji)hqWJm3U&21E2b}xR#0o;i zu4jVEqgohf*Q-qd$vG;HNnyGOJyH_p66*dMo_V|#%E61;9E^m@V@_o!_B@7O#CRdf z4h%)gfJL{hwl>!7ZgS!P(DAVb!Z!%d4*;{A6%(ThS^9$xtnen_0mjXrV{nOib$i1dYBYpy~0|} z$yANTD2afRFDxF^gLq76n@h-sV~N>J!zxD?E1>Mvv3cNl(MH%zl;6A?RgY`Xn0z{x zZsM#|Ysc%a(oenC9+>hU*LST#v^|edUwjnQYfAAoxK{_`2OGnJO+;*=*~GJsQzH~u z=Gc_NnxBb_D+(r4ivHS3Is0c3fNOybW(*1Wm2L*dMWMEZE)5fZ6B> zR|p@6?dNU6jyD$Z>Xmhg7a^J{+5(0LGk1HgT>%!qQaEW@)iNtCpZ4Hl{j-1mQs)5Bw2ZkitjO7HORaIWE9NJ_BMKS0eJ~*t=aWVH zHnq-*hR7gdkr$cDurgDnwc}{E_AhL@HS>=GWMG{5xf~9 zWfas&d$`uC+(zqMm7W6$`NjD5{r={q+>v6Vilp^RLEgU6)Jay>cc(1FXp*P^!dpSM z>}-;I$$HuQy~ELU!3b`S@)m@Q1JNZ_>FwwKG%Oz+?Hbm8u3$!?9YQAFbH#f@?F<#~ z)LC^WiW=}}^B}KAwCnU%-ncW2$M|wTC@V)&M;#Sg*-Ra3<2vVjL>>7HvkukLXS0s`PGD{m%t5+wEu zefGJhQn=I%b)IJeX0;6re+*8%D#hKd+&vq}cTU#!xN*H~<*hHn5yK^J(;^$iU1~_) zz1|#{ppBrm<(vt&{%#=I;2cD};H5r`8R` zu&YJ5lsnmWxlpVv4&!uQOhuZ2HhJ4y9bAc%Q6TPBtev*}u173&Lw8{$3xR)L`euN@ zO8RjddP2%q0d#Z~{^3q|}09324-$#dJi?^Zy3;%UfwbX9J21%=Y#%v3i8~ zDqtAeqsA-ed0JE_Nc;7K)V0<%xi-8!gVV5^h;5*+$D)ihz8uIWeT(wjg)hrH{o7i~ zp%iJ7MYdkF(^`4@L%_gZHLjk4oA=;IuY8)TOU2W+#v<|^*<5h5{Cs6<*EMkrIDEwO XtVRu7MaY{sn&qA6lBMkTKid62VWt-s delta 67 zcmV-J0KEUk=K`>x0. + * + */ + +#import +#import "OCCertificate.h" +#import "OCCertificateStoreRecord.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCertificateStore : NSObject + +- (instancetype)initWithMigrationOfCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname lastModifiedDate:(NSDate *)lastModifiedDate; //!< Convenience initializer for migration from bookmarks without certificate store + +#pragma mark - Store & retrieve certificates +- (void)storeCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname; //!< Stores the provided certificate for the provided hostname +- (nullable OCCertificate *)certificateForHostname:(nullable NSString *)hostname lastModified:(NSDate * _Nullable * _Nullable)outLastModified; //!< Returns the certificate and last modified date for the provided hostname + +- (nullable NSArray *)hostnamesForCertificate:(OCCertificate *)certificate; //!< Returns all host names (or nil) for which the provided certificate is stored + +@property(readonly,nonatomic,strong) NSArray *allRecords; //!< Returns the contents of the certificate store + +#pragma mark - Remove certificates +- (BOOL)removeCertificateForHostname:(NSString *)hostname; //!< Returns YES if a certificate for the provided hostname was removed +- (void)removeAllCertificates; //!< Removes all certificates from the store + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Security/OCCertificateStore.m b/ownCloudSDK/Security/OCCertificateStore.m new file mode 100644 index 00000000..c604c8aa --- /dev/null +++ b/ownCloudSDK/Security/OCCertificateStore.m @@ -0,0 +1,148 @@ +// +// OCCertificateStore.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.12.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 "OCCertificateStore.h" +#import "OCCertificateStoreRecord.h" + +@implementation OCCertificateStore +{ + NSMutableDictionary *_recordsByHostname; +} + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _recordsByHostname = [NSMutableDictionary new]; + } + + return (self); +} + +- (instancetype)initWithMigrationOfCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname lastModifiedDate:(NSDate *)lastModifiedDate +{ + if ((self = [self init]) != nil) + { + if (hostname != nil) + { + _recordsByHostname[hostname] = [[OCCertificateStoreRecord alloc] initWithCertificate:certificate forHostname:hostname lastModifiedDate:lastModifiedDate]; + } + } + + return (self); +} + +#pragma mark - Store & retrieve certificates +- (void)storeCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname +{ + if ((hostname != nil) && (hostname.length > 0)) + { + @synchronized(_recordsByHostname) + { + _recordsByHostname[hostname] = [[OCCertificateStoreRecord alloc] initWithCertificate:certificate forHostname:hostname]; + } + } +} + +- (nullable OCCertificate *)certificateForHostname:(NSString *)hostname lastModified:(NSDate * _Nullable * _Nullable)outLastModified +{ + OCCertificateStoreRecord *record = nil; + + if ((hostname != nil) && (hostname.length > 0)) + { + @synchronized(_recordsByHostname) + { + record = _recordsByHostname[hostname]; + } + } + + return (record.certificate); +} + +- (NSArray *)hostnamesForCertificate:(OCCertificate *)certificate +{ + NSMutableSet *hostnames = [NSMutableSet new]; + + @synchronized(_recordsByHostname) { + [_recordsByHostname enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull hostname, OCCertificateStoreRecord * _Nonnull record, BOOL * _Nonnull stop) { + if ([record.certificate isEqual:certificate]) + { + [hostnames addObject:record.hostname]; + } + }]; + } + + return ((hostnames.count > 0) ? hostnames.allObjects : nil); +} + +- (NSArray *)allRecords +{ + @synchronized(_recordsByHostname) + { + return (_recordsByHostname.allValues); + } +} + +#pragma mark - Remove certificates +- (BOOL)removeCertificateForHostname:(NSString *)hostname +{ + if ((hostname != nil) && (hostname.length > 0)) + { + @synchronized(_recordsByHostname) + { + if (_recordsByHostname[hostname] != nil) + { + _recordsByHostname[hostname] = nil; + return (YES); + } + } + } + + return (NO); +} + +- (void)removeAllCertificates +{ + @synchronized(_recordsByHostname) + { + [_recordsByHostname removeAllObjects]; + } +} + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [coder encodeObject:_recordsByHostname forKey:@"recordsByHostname"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [self init]) != nil) + { + _recordsByHostname = [coder decodeObjectOfClasses:[NSSet setWithObjects:OCCertificateStoreRecord.class, NSDictionary.class, nil] forKey:@"recordsByHostname"]; + } + + return (self); +} + +@end diff --git a/ownCloudSDK/Security/OCCertificateStoreRecord.h b/ownCloudSDK/Security/OCCertificateStoreRecord.h new file mode 100644 index 00000000..a8fcdf16 --- /dev/null +++ b/ownCloudSDK/Security/OCCertificateStoreRecord.h @@ -0,0 +1,35 @@ +// +// OCCertificateStoreRecord.h +// ownCloudSDK +// +// Created by Felix Schwarz on 08.12.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 "OCCertificate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCertificateStoreRecord : NSObject + +@property(strong,readonly) NSString *hostname; //!< Hostname this certificate is used for +@property(strong,readonly) NSDate *lastModifiedDate; //!< Date the certificate stored in this record was last modified. +@property(strong,readonly) OCCertificate *certificate; + +- (instancetype)initWithCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname; +- (instancetype)initWithCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname lastModifiedDate:(NSDate *)lastModifiedDate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Security/OCCertificateStoreRecord.m b/ownCloudSDK/Security/OCCertificateStoreRecord.m new file mode 100644 index 00000000..4cbe5666 --- /dev/null +++ b/ownCloudSDK/Security/OCCertificateStoreRecord.m @@ -0,0 +1,75 @@ +// +// OCCertificateStoreRecord.m +// ownCloudSDK +// +// Created by Felix Schwarz on 08.12.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 "OCCertificateStoreRecord.h" +#import "OCMacros.h" + +@implementation OCCertificateStoreRecord + +#pragma mark - Secure Coding ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (instancetype)initWithCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname +{ + return ([self initWithCertificate:certificate forHostname:hostname lastModifiedDate:NSDate.new]); +} + +- (instancetype)initWithCertificate:(OCCertificate *)certificate forHostname:(NSString *)hostname lastModifiedDate:(NSDate *)lastModifiedDate; +{ + if ((self = [super init]) != nil) + { + _lastModifiedDate = lastModifiedDate; + _certificate = certificate; + _hostname = hostname; + } + + return (self); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [coder encodeObject:_certificate forKey:@"certificate"]; + [coder encodeObject:_hostname forKey:@"hostname"]; + [coder encodeObject:_lastModifiedDate forKey:@"lastModifiedDate"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder +{ + if ((self = [super init]) != nil) + { + _certificate = [coder decodeObjectOfClass:OCCertificate.class forKey:@"certificate"]; + _hostname = [coder decodeObjectOfClass:NSString.class forKey:@"hostname"]; + _lastModifiedDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastModifiedDate"]; + } + + return (self); +} + +- (NSString *)description +{ + return ([NSString stringWithFormat:@"<%@: %p%@%@%@>", NSStringFromClass(self.class), self, + OCExpandVar(hostname), + OCExpandVar(certificate), + OCExpandVar(lastModifiedDate) + ]); +} + +@end diff --git a/ownCloudSDK/Settings/OCClassSettings+Validation.m b/ownCloudSDK/Settings/OCClassSettings+Validation.m index 904e15b2..718253a9 100644 --- a/ownCloudSDK/Settings/OCClassSettings+Validation.m +++ b/ownCloudSDK/Settings/OCClassSettings+Validation.m @@ -271,7 +271,7 @@ - (id)_validatedValue:(id)value forPossibleValues:(id)possibleValues ofSettingsI }]; } - if (error != nil) + if ((error != nil) && (outError != NULL)) { *outError = error; } diff --git a/ownCloudSDK/Settings/OCClassSettings.m b/ownCloudSDK/Settings/OCClassSettings.m index a8990b47..1262f34f 100644 --- a/ownCloudSDK/Settings/OCClassSettings.m +++ b/ownCloudSDK/Settings/OCClassSettings.m @@ -20,6 +20,7 @@ #import "OCClassSettingsFlatSourceManagedConfiguration.h" #import "OCClassSettingsFlatSourceEnvironment.h" #import "OCClassSettingsUserPreferences.h" +#import "OCClassSettingsFlatSourcePostBuild.h" #import "OCLogger.h" @interface OCClassSettings () @@ -42,6 +43,7 @@ + (instancetype)sharedSettings [sharedClassSettings addSource:[OCClassSettingsFlatSourceManagedConfiguration new]]; [sharedClassSettings addSource:[OCClassSettingsUserPreferences sharedUserPreferences]]; + [sharedClassSettings addSource:[OCClassSettingsFlatSourcePostBuild sharedPostBuildSettings]]; [sharedClassSettings addSource:[[OCClassSettingsFlatSourceEnvironment alloc] initWithPrefix:@"oc:"]]; }); diff --git a/ownCloudSDK/Settings/Sources/NSDictionary+OCExpand.m b/ownCloudSDK/Settings/Sources/NSDictionary+OCExpand.m index d8fdfc3e..84d558e4 100644 --- a/ownCloudSDK/Settings/Sources/NSDictionary+OCExpand.m +++ b/ownCloudSDK/Settings/Sources/NSDictionary+OCExpand.m @@ -116,14 +116,12 @@ - (NSDictionary *)expandedDictionary for (NSString *key in keys) { NSRange escapeRange = [key rangeOfString:@"$"]; - NSString *effectiveKey = key; id value = [sourceDict valueForKey:key]; if (escapeRange.location != NSNotFound) { NSArray *pathComponents = [[key substringFromIndex:escapeRange.location+1] componentsSeparatedByString:@"."]; - - effectiveKey = [key substringToIndex:escapeRange.location]; + NSString *effectiveKey = [key substringToIndex:escapeRange.location]; // Navigate hierarchy id targetCollection = resultDict; diff --git a/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.h b/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.h new file mode 100644 index 00000000..8d1899d0 --- /dev/null +++ b/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.h @@ -0,0 +1,39 @@ +// +// OCClassSettingsFlatSourcePostBuild.h +// ownCloudSDK +// +// Created by Felix Schwarz on 16.01.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCClassSettingsFlatSourcePostBuild : OCClassSettingsFlatSource + +@property(class,nonatomic,readonly) OCClassSettingsFlatSourcePostBuild *sharedPostBuildSettings; + +- (nullable NSError *)setValue:(nullable id)value forFlatIdentifier:(OCClassSettingsFlatIdentifier)flatID; +- (nullable id)valueForFlatIdentifier:(OCClassSettingsFlatIdentifier)flatID; + +- (nullable NSError *)clear; + +@end + +extern OCClassSettingsSourceIdentifier OCClassSettingsSourceIdentifierPostBuild; + +extern OCClassSettingsIdentifier OCClassSettingsIdentifierPostBuildSettings; +extern OCClassSettingsKey OCClassSettingsKeyPostBuildAllowedSettings; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.m b/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.m new file mode 100644 index 00000000..bbf0e820 --- /dev/null +++ b/ownCloudSDK/Settings/Sources/OCClassSettingsFlatSourcePostBuild.m @@ -0,0 +1,161 @@ +// +// OCClassSettingsFlatSourcePostBuild.m +// ownCloudSDK +// +// Created by Felix Schwarz on 16.01.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCClassSettingsFlatSourcePostBuild.h" +#import "OCLogger.h" + +@implementation OCClassSettingsFlatSourcePostBuild + +#pragma mark - Class Settings support ++ (OCClassSettingsIdentifier)classSettingsIdentifier +{ + return (OCClassSettingsIdentifierPostBuildSettings); +} + ++ (NSDictionary *)defaultSettingsForIdentifier:(OCClassSettingsIdentifier)identifier +{ + return (@{ + OCClassSettingsKeyPostBuildAllowedSettings : @[ ] + }); +} + ++ (OCClassSettingsFlatSourcePostBuild *)sharedPostBuildSettings +{ + static dispatch_once_t onceToken; + static OCClassSettingsFlatSourcePostBuild *sharedSource; + + dispatch_once(&onceToken, ^{ + sharedSource = [OCClassSettingsFlatSourcePostBuild new]; + }); + + return (sharedSource); +} + ++ (OCClassSettingsMetadataCollection)classSettingsMetadata +{ + return (@{ + // Allowed Post Build Flat Identifiers + OCClassSettingsKeyPostBuildAllowedSettings : @{ + OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeStringArray, + OCClassSettingsMetadataKeyDescription : @"List of settings (as flat identifiers) that are allowed to be changed post-build via the app's URL scheme. Including a value of \"*\" allows any setting to be changed. Defaults to an empty array (equalling not allowed). ", + OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced, + OCClassSettingsMetadataKeyCategory : @"Security" + }, + }); +} + +#pragma mark - File URL & loading +- (NSURL *)postBuildSettingsFileURL +{ + return ([OCAppIdentity.sharedAppIdentity.appGroupContainerURL URLByAppendingPathComponent:@"postBuildSettings.plist"]); +} + +- (NSMutableDictionary *)_settingsDict +{ + NSError *error = nil; + NSMutableDictionary *settingsDict = nil; + + settingsDict = (id) [[NSDictionary dictionaryWithContentsOfURL:self.postBuildSettingsFileURL error:&error] mutableCopy]; // Invoking this method on NSMutableDictionary still returns a NSDictionary here, but we need a NSMutableDictionary. Therefore using NSDictionary + mutableCopy to get a mutable copy + + if (settingsDict == nil) + { + settingsDict = [NSMutableDictionary new]; + } + + return (settingsDict); +} + +#pragma mark - Access and modification +- (nullable id)valueForFlatIdentifier:(OCClassSettingsFlatIdentifier)flatID +{ + return ([[self _settingsDict] objectForKey:flatID]); +} + +- (nullable NSError *)setValue:(nullable id)value forFlatIdentifier:(OCClassSettingsFlatIdentifier)flatID +{ + NSMutableDictionary *settingsDict = [self _settingsDict]; + NSError *error = nil; + NSArray *allowedKeys = [self classSettingForOCClassSettingsKey:OCClassSettingsKeyPostBuildAllowedSettings]; + + if ( (allowedKeys == nil) || + ((allowedKeys != nil) && ![allowedKeys containsObject:flatID] && ![allowedKeys containsObject:@"*"]) + ) + { + OCTLogError(@[@"PostBuild"], @"Change not allowed: %@=%@", flatID, value); + return(OCErrorWithDescription(OCErrorInternal, ([NSString stringWithFormat:@"%@ not allowed to be modified post-build.", flatID]))); + } + + settingsDict[flatID] = value; + + [settingsDict writeToURL:self.postBuildSettingsFileURL error:&error]; + + if (error != nil) + { + OCTLogError(@[@"PostBuild"], @"Error saving %@=%@ to %@", flatID, value, self.postBuildSettingsFileURL); + } + else + { + OCTLog(@[@"PostBuild"], @"Saved %@=%@", flatID, value); + } + + return (error); +} + +- (nullable NSError *)clear +{ + NSError *error = nil; + + if (![NSFileManager.defaultManager removeItemAtURL:self.postBuildSettingsFileURL error:&error]) + { + return (error); + } + + return (nil); +} + +#pragma mark - OCClassSettingsFlatSource subclassing +- (OCClassSettingsSourceIdentifier)settingsSourceIdentifier +{ + return (OCClassSettingsSourceIdentifierPostBuild); +} + +- (nullable NSDictionary *)flatSettingsDictionary +{ + // Maybe this is not possible as we can't request a setting before the shared class settings instance isn't set up ?! + +// NSArray *allowedKeys; +// +// if ((allowedKeys = [self classSettingForOCClassSettingsKey:OCClassSettingsKeyPostBuildAllowedSettings]) && +// (allowedKeys.count > 0)) +// { +// // Make sure only those settings are applied that are allowed at the time +// return ([[self _settingsDict] dictionaryWithValuesForKeys:allowedKeys]); +// } +// +// return (nil); + + // Return full contents of post-build settings file + return ([self _settingsDict]); +} + +@end + +OCClassSettingsSourceIdentifier OCClassSettingsSourceIdentifierPostBuild = @"pb"; + +OCClassSettingsIdentifier OCClassSettingsIdentifierPostBuildSettings = @"post-build"; +OCClassSettingsKey OCClassSettingsKeyPostBuildAllowedSettings = @"allowed-settings"; diff --git a/ownCloudSDK/Settings/User Preferences/OCClassSettingsUserPreferences.m b/ownCloudSDK/Settings/User Preferences/OCClassSettingsUserPreferences.m index 67facc15..61ce9c9c 100644 --- a/ownCloudSDK/Settings/User Preferences/OCClassSettingsUserPreferences.m +++ b/ownCloudSDK/Settings/User Preferences/OCClassSettingsUserPreferences.m @@ -94,12 +94,12 @@ - (BOOL)setValue:(id)value forClassSettingsKey:(OCClassSettingsK if ((classSettingsIdentifier = [theClass classSettingsIdentifier]) != nil) { [self _setValue:value forClassSettingsKey:key classSettingsIdentifier:classSettingsIdentifier]; - } - [OCClassSettings.sharedSettings clearSourceCache]; + [OCClassSettings.sharedSettings clearSourceCache]; - [NSNotificationCenter.defaultCenter postNotificationName:OCClassSettingsChangedNotification object:[NSString flatIdentifierFromIdentifier:classSettingsIdentifier key:key]]; - [OCIPNotificationCenter.sharedNotificationCenter postNotificationForName:OCIPCNotificationNameClassSettingsUserPreferencesChanged ignoreSelf:YES]; + [NSNotificationCenter.defaultCenter postNotificationName:OCClassSettingsChangedNotification object:[NSString flatIdentifierFromIdentifier:classSettingsIdentifier key:key]]; + [OCIPNotificationCenter.sharedNotificationCenter postNotificationForName:OCIPCNotificationNameClassSettingsUserPreferencesChanged ignoreSelf:YES]; + } } return (changeAllowed); diff --git a/ownCloudSDK/Setup/OCBookmark+ServerInstance.h b/ownCloudSDK/Setup/OCBookmark+ServerInstance.h new file mode 100644 index 00000000..64bfa31c --- /dev/null +++ b/ownCloudSDK/Setup/OCBookmark+ServerInstance.h @@ -0,0 +1,33 @@ +// +// OCBookmark+ServerInstance.h +// ownCloudSDK +// +// Created by Felix Schwarz on 07.03.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCBookmark.h" + +@class OCBookmark; +@class OCServerInstance; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCBookmark (ServerInstance) + +- (void)applyServerInstance:(OCServerInstance *)serverInstance; //!< Applies the server instance on the bookmark, so that the bookmark can be used to connect to the server instance + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Setup/OCBookmark+ServerInstance.m b/ownCloudSDK/Setup/OCBookmark+ServerInstance.m new file mode 100644 index 00000000..e13b92fa --- /dev/null +++ b/ownCloudSDK/Setup/OCBookmark+ServerInstance.m @@ -0,0 +1,29 @@ +// +// OCBookmark+ServerInstance.m +// ownCloudSDK +// +// Created by Felix Schwarz on 07.03.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCBookmark+ServerInstance.h" +#import "OCServerInstance.h" + +@implementation OCBookmark (ServerInstance) + +- (void)applyServerInstance:(OCServerInstance *)serverInstance +{ + self.url = serverInstance.url; +} + +@end diff --git a/ownCloudSDK/Setup/OCServerInstance.h b/ownCloudSDK/Setup/OCServerInstance.h new file mode 100644 index 00000000..d3b14bc3 --- /dev/null +++ b/ownCloudSDK/Setup/OCServerInstance.h @@ -0,0 +1,33 @@ +// +// OCServerInstance.h +// ownCloudSDK +// +// Created by Felix Schwarz on 07.03.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCServerInstance : NSObject + +@property(strong) NSURL *url; +@property(strong,nullable) NSDictionary *titlesByLanguageCode; +@property(readonly,nullable,nonatomic) NSString *localizedTitle; + +- (instancetype)initWithURL:(NSURL *)url; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Setup/OCServerInstance.m b/ownCloudSDK/Setup/OCServerInstance.m new file mode 100644 index 00000000..56b22860 --- /dev/null +++ b/ownCloudSDK/Setup/OCServerInstance.m @@ -0,0 +1,51 @@ +// +// OCServerInstance.m +// ownCloudSDK +// +// Created by Felix Schwarz on 07.03.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCServerInstance.h" + +@implementation OCServerInstance + +@dynamic localizedTitle; + +- (instancetype)initWithURL:(NSURL *)url +{ + if ((self = [super init]) != nil) + { + _url = url; + } + + return (self); +} + +- (NSString *)localizedTitle +{ + if (_titlesByLanguageCode != nil) + { + NSString *preferredLanguage; + + if ((preferredLanguage = [NSBundle preferredLocalizationsFromArray:_titlesByLanguageCode.allKeys].firstObject) != nil) { + return (_titlesByLanguageCode[preferredLanguage]); + }; + + return (_titlesByLanguageCode.allValues.firstObject); + } + + return (nil); +} + +@end diff --git a/ownCloudSDK/Share/OCShare+OCDataItem.h b/ownCloudSDK/Share/OCShare+OCDataItem.h new file mode 100644 index 00000000..35de5a32 --- /dev/null +++ b/ownCloudSDK/Share/OCShare+OCDataItem.h @@ -0,0 +1,28 @@ +// +// OCShare+OCDataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 19.12.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 "OCDataTypes.h" +#import "OCShare.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCShare (OCDataItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Share/OCShare+OCDataItem.m b/ownCloudSDK/Share/OCShare+OCDataItem.m new file mode 100644 index 00000000..7cde7ed1 --- /dev/null +++ b/ownCloudSDK/Share/OCShare+OCDataItem.m @@ -0,0 +1,75 @@ +// +// OCShare+OCDataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 19.12.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 "OCShare+OCDataItem.h" +#import "OCDataRenderer.h" +#import "OCDataConverter.h" +#import "OCDataItemPresentable.h" + +@implementation OCShare (OCDataItem) + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeShare); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.identifier); +} + +- (OCDataItemVersion)dataItemVersion +{ + NSArray *otherItemShares = self.otherItemShares; + NSString *otherItemSharesVersions = @""; + + if ((otherItemShares != nil) && (otherItemShares.count > 0)) + { + for (OCShare *share in otherItemShares) + { + otherItemSharesVersions = [otherItemSharesVersions stringByAppendingString:(NSString *)share.dataItemVersion]; + } + } + + return ([NSString stringWithFormat:@"%@%lx%@%@%@%d%@_%@%@%@", self.itemLocation.string, self.permissions, self.name, self.token, self.url, self.protectedByPassword, self.state, self.accepted, self.expirationDate, otherItemSharesVersions]); +} + +#pragma mark - OCDataConverter for OCDrives ++ (void)load +{ + OCDataConverter *shareToPresentableConverter; + + shareToPresentableConverter = [[OCDataConverter alloc] initWithInputType:OCDataItemTypeShare outputType:OCDataItemTypePresentable conversion:^id _Nullable(OCDataConverter * _Nonnull converter, OCShare * _Nullable inShare, OCDataRenderer * _Nullable renderer, NSError * _Nullable __autoreleasing * _Nullable outError, OCDataViewOptions _Nullable options) { + OCDataItemPresentable *presentable = nil; + + if (inShare != nil) + { + presentable = [[OCDataItemPresentable alloc] initWithItem:inShare]; + presentable.title = inShare.itemLocation.path; + presentable.subtitle = inShare.owner.displayName; + } + + return (presentable); + }]; + + [OCDataRenderer.defaultRenderer addConverters:@[ + shareToPresentableConverter + ]]; +} + +@end diff --git a/ownCloudSDK/Share/OCShare+OCXMLObjectCreation.m b/ownCloudSDK/Share/OCShare+OCXMLObjectCreation.m index 48a5fef6..f4dbb336 100644 --- a/ownCloudSDK/Share/OCShare+OCXMLObjectCreation.m +++ b/ownCloudSDK/Share/OCShare+OCXMLObjectCreation.m @@ -20,6 +20,7 @@ #import "OCXMLParserNode.h" #import "NSDate+OCDateParser.h" #import "NSString+OCPath.h" +#import "OCDrive.h" @implementation OCShare (OCXMLObjectCreation) @@ -40,6 +41,9 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa // Identifier share.identifier = shareID; + // Category + share.category = ((NSNumber *)xmlParser.options[@"_shareCategory"]).unsignedIntegerValue; + // Type NSString *shareType; @@ -56,10 +60,49 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa } } + // State + OCShareState state; + + if ((state = shareNode.keyValues[@"state"]) != nil) + { + share.state = state; + } + // Item path - share.itemPath = shareNode.keyValues[@"path"]; + OCPath sharePath = shareNode.keyValues[@"path"]; + + if (sharePath != nil) + { + NSString *itemSource; + + if ((itemSource = shareNode.keyValues[@"item_source"]) != nil) + { + if ([itemSource containsString:@"!"] && [state isEqual:OCShareStateAccepted] && (share.category == OCShareCategoryWithMe)) + { + // Compute location and FileID in Shares Jail + share.itemFileID = [OCDriveIDSharesJail stringByAppendingFormat:@"!%@", shareID]; // Item ID in Shares Jail = OCDriveIDSharesJail + "!" + shareID (via https://github.com/owncloud/web/blob/master/packages/web-client/src/helpers/space/functions.ts#L53 ) + share.itemLocation = [[OCLocation alloc] initWithDriveID:OCDriveIDSharesJail path:[@"/" stringByAppendingString:sharePath.lastPathComponent]]; // Item is located in Shares Jail + } + else + { + // OCIS (drive ID could be extracted from item_source, which follows format "[driveID]![fileID]") + NSArray *itemSourceIDs = [itemSource componentsSeparatedByString:@"!"]; + + if (itemSourceIDs.count == 2) + { + share.itemFileID = itemSource; + share.itemLocation = [[OCLocation alloc] initWithDriveID:itemSourceIDs.firstObject path:sharePath]; + } + } + } - if (share.itemPath == nil) + if (share.itemLocation == nil) + { + // Fall back to OC10 legacy location + share.itemLocation = [OCLocation legacyRootPath:sharePath]; + } + } + else { // Special case: federated share NSString *mountPoint = nil; @@ -70,7 +113,7 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa if (![mountPoint hasPrefix:@"{{TemporaryMountPointName#"]) { - share.itemPath = mountPoint; + share.itemLocation = [OCLocation legacyRootPath:mountPoint]; } } } @@ -82,11 +125,11 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa { if ([itemType isEqual:@"file"]) { - share.itemType = OCItemTypeFile; + share.itemType = OCLocationTypeFile; } else if ([itemType isEqual:@"folder"]) { - share.itemType = OCItemTypeCollection; + share.itemType = OCLocationTypeFolder; } } else @@ -98,19 +141,19 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa { if ([type isEqual:@"file"]) { - share.itemType = OCItemTypeFile; + share.itemType = OCLocationTypeFile; } else if ([type isEqual:@"dir"]) { - share.itemType = OCItemTypeCollection; + share.itemType = OCLocationTypeFolder; } } } - if (share.itemType == OCItemTypeCollection) + if (share.itemType == OCLocationTypeFolder) { - // Ensure itemPath conforms to OCPath convention that directories end with a "/" - share.itemPath = [share.itemPath normalizedDirectoryPath]; + // Ensure itemLocation.path conforms to OCPath convention that directories end with a "/" + share.itemLocation = share.itemLocation.normalizedDirectoryPathLocation; } // Item owner @@ -224,11 +267,11 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa { recipientDisplayName = [recipientDisplayName stringByAppendingString:[NSString stringWithFormat:@" (%@)", share_with_additional_info]]; } - share.recipient = [OCRecipient recipientWithUser:[OCUser userWithUserName:recipientName displayName:recipientDisplayName]]; + share.recipient = [OCIdentity identityWithUser:[OCUser userWithUserName:recipientName displayName:recipientDisplayName]]; break; case OCShareTypeGroupShare: - share.recipient = [OCRecipient recipientWithGroup:[OCGroup groupWithIdentifier:recipientName name:recipientDisplayName]]; + share.recipient = [OCIdentity identityWithGroup:[OCGroup groupWithIdentifier:recipientName name:recipientDisplayName]]; break; case OCShareTypeLink: @@ -249,7 +292,7 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa if ((user = shareNode.keyValues[@"user"]) != nil) { - share.recipient = [OCRecipient recipientWithUser:[OCUser userWithUserName:user displayName:nil]]; + share.recipient = [OCIdentity identityWithUser:[OCUser userWithUserName:user displayName:nil]]; } } @@ -268,14 +311,6 @@ + (instancetype)instanceFromNode:(OCXMLParserNode *)shareNode xmlParser:(OCXMLPa { share.accepted = @(accepted.integerValue); } - - // State - OCShareState state; - - if ((state = shareNode.keyValues[@"state"]) != nil) - { - share.state = state; - } } } diff --git a/ownCloudSDK/Share/OCShare.h b/ownCloudSDK/Share/OCShare.h index a257dae1..ee346ab3 100644 --- a/ownCloudSDK/Share/OCShare.h +++ b/ownCloudSDK/Share/OCShare.h @@ -18,7 +18,7 @@ #import #import "OCUser.h" -#import "OCRecipient.h" +#import "OCIdentity.h" typedef NS_ENUM(NSInteger, OCShareType) { @@ -46,6 +46,7 @@ typedef NS_OPTIONS(NSInteger, OCShareTypesMask) typedef NS_OPTIONS(NSInteger, OCSharePermissionsMask) { OCSharePermissionsMaskNone = 0, + OCSharePermissionsMaskInternal = 0, // 0 OCSharePermissionsMaskRead = (1<<0), // 1 OCSharePermissionsMaskUpdate = (1<<1), // 2 OCSharePermissionsMaskCreate = (1<<2), // 4 @@ -64,6 +65,13 @@ typedef NS_ENUM(NSUInteger, OCShareScope) OCShareScopeSubItems //!< Return shares for items contained in the provided (container) item }; +typedef NS_ENUM(NSUInteger, OCShareCategory) +{ + OCShareCategoryUnknown, + OCShareCategoryWithMe, + OCShareCategoryByMe +}; + typedef NSString* OCShareOptionKey NS_TYPED_ENUM; typedef NSDictionary* OCShareOptions; @@ -77,12 +85,14 @@ NS_ASSUME_NONNULL_BEGIN @interface OCShare : NSObject -@property(nullable,strong) OCShareID identifier; //!< Server-issued unique identifier of the share +@property(nullable,strong) OCShareID identifier; //!< Server-issued unique identifier of the share (server-provided instances are guaranteed to have an ID, locally created ones do typically NOT have an ID) @property(assign) OCShareType type; //!< The type of share (i.e. public or user) +@property(assign) OCShareCategory category; //!< Category of share (with me/by me) -@property(strong) OCPath itemPath; //!< Path of the shared item -@property(assign) OCItemType itemType; //!< Type of the shared item +@property(strong,nonatomic) OCLocation *itemLocation; //!< Location of the shared item +@property(strong,nullable) OCFileID itemFileID; //!< File ID of item +@property(assign) OCLocationType itemType; //!< Type of the shared item @property(nullable,strong) OCUser *itemOwner; //!< Owner of the item @property(nullable,strong) NSString *itemMIMEType; //!< MIME-Type of the shared item @@ -105,40 +115,48 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable,strong) NSString *password; //!< Password of the share (not always available) @property(nullable,strong) OCUser *owner; //!< Owner of the share -@property(nullable,strong) OCRecipient *recipient; //!< Recipient of the share +@property(nullable,strong) OCIdentity *recipient; //!< Recipient of the share @property(nullable,strong) NSString *mountPoint; //!< Mount point of federated share (if accepted, itemPath contains a sanitized path to the location inside the user's account) @property(nullable,strong) OCShareState state; //!< Local share is pending, accepted or rejected @property(nullable,strong) NSNumber *accepted; //!< Federated share has been accepted +@property(nullable,readonly,nonatomic) OCShareState effectiveState; //!< Unified state information for both remote and local shares + +@property(nullable,strong) NSArray *otherItemShares; //!< Other shares targeting the same item (! not serialized !) + #pragma mark - Convenience constructors /** Creates an object that can be used to create a share on the server. @param recipient The recipient representing the user or group to share with. - @param path The path of the item to share. Can be retrieved from OCItem.path. + @param location The location of the item to share. Can be retrieved from OCItem.location. @param permissions Bitmask of permissions. @param expirationDate Optional expiration date. @return An OCShare instance configured with the respective options. */ -+ (instancetype)shareWithRecipient:(OCRecipient *)recipient path:(OCPath)path permissions:(OCSharePermissionsMask)permissions expiration:(nullable NSDate *)expirationDate; ++ (instancetype)shareWithRecipient:(OCIdentity *)recipient location:(OCLocation *)location permissions:(OCSharePermissionsMask)permissions expiration:(nullable NSDate *)expirationDate; /** Creates an object that can be used to create a public link. - @param path The path of the item to share. Can be retrieved from OCItem.path. + @param location The location of the item to share. Can be retrieved from OCItem.location. @param name Optional name for the public link. @param permissions Bitmask of permissions: OCSharePermissionsMaskRead for "Download + View". OCSharePermissionsMaskCreate for "Upload" (specify only this for a folder to create a file drop). OCSharePermissionsMaskUpdate|OCSharePermissionsMaskDelete to also allow changes / deletion of items. @param password Optional password to control access. @param expirationDate Optional expiration date. @return An OCShare instance configured with the respective options. */ -+ (instancetype)shareWithPublicLinkToPath:(OCPath)path linkName:(nullable NSString *)name permissions:(OCSharePermissionsMask)permissions password:(nullable NSString *)password expiration:(nullable NSDate *)expirationDate; ++ (instancetype)shareWithPublicLinkToLocation:(OCLocation *)location linkName:(nullable NSString *)name permissions:(OCSharePermissionsMask)permissions password:(nullable NSString *)password expiration:(nullable NSDate *)expirationDate; + +#pragma mark - Conversions ++ (OCShareTypesMask)maskForType:(OCShareType)type; //!< Converts share types into mask values. ++ (OCShareType)typeForMask:(OCShareTypesMask)mask; //!< Converts single-type mask values into types. Multi-type masks are returned as OCShareTypeUnknown @end extern OCShareState OCShareStateAccepted; extern OCShareState OCShareStatePending; -extern OCShareState OCShareStateRejected; +extern OCShareState OCShareStateDeclined; NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Share/OCShare.m b/ownCloudSDK/Share/OCShare.m index c800ab5a..4da98685 100644 --- a/ownCloudSDK/Share/OCShare.m +++ b/ownCloudSDK/Share/OCShare.m @@ -40,7 +40,7 @@ - (instancetype)init } #pragma mark - Convenience constructors -+ (instancetype)shareWithRecipient:(OCRecipient *)recipient path:(OCPath)path permissions:(OCSharePermissionsMask)permissions expiration:(nullable NSDate *)expirationDate ++ (instancetype)shareWithRecipient:(OCIdentity *)recipient location:(OCLocation *)location permissions:(OCSharePermissionsMask)permissions expiration:(NSDate *)expirationDate { OCShare *share = [OCShare new]; @@ -60,9 +60,11 @@ + (instancetype)shareWithRecipient:(OCRecipient *)recipient path:(OCPath)path pe } } + share.category = OCShareCategoryByMe; + share.recipient = recipient; - share.itemPath = path; + share.itemLocation = location; share.permissions = permissions; share.expirationDate = expirationDate; @@ -70,15 +72,16 @@ + (instancetype)shareWithRecipient:(OCRecipient *)recipient path:(OCPath)path pe return (share); } -+ (instancetype)shareWithPublicLinkToPath:(OCPath)path linkName:(nullable NSString *)name permissions:(OCSharePermissionsMask)permissions password:(nullable NSString *)password expiration:(nullable NSDate *)expirationDate ++ (instancetype)shareWithPublicLinkToLocation:(OCLocation *)location linkName:(NSString *)name permissions:(OCSharePermissionsMask)permissions password:(NSString *)password expiration:(NSDate *)expirationDate { OCShare *share = [OCShare new]; share.type = OCShareTypeLink; + share.category = OCShareCategoryByMe; share.name = name; - share.itemPath = path; + share.itemLocation = location; share.password = password; @@ -126,10 +129,41 @@ - (void)setProtectedByPassword:(BOOL)protectedByPassword _protectedByPassword = protectedByPassword; } +#pragma mark - Conversions ++ (OCShareTypesMask)maskForType:(OCShareType)type +{ + switch (type) + { + case OCShareTypeUnknown: return (OCShareTypesMaskNone); + case OCShareTypeUserShare: return (OCShareTypesMaskUserShare); + case OCShareTypeGroupShare: return (OCShareTypesMaskGroupShare); + case OCShareTypeLink: return (OCShareTypesMaskLink); + case OCShareTypeGuest: return (OCShareTypesMaskGuest); + case OCShareTypeRemote: return (OCShareTypesMaskRemote); + } + + return (OCShareTypesMaskNone); +} + ++ (OCShareType)typeForMask:(OCShareTypesMask)mask +{ + switch (mask) + { + case OCShareTypesMaskNone: return (OCShareTypeUnknown); + case OCShareTypesMaskUserShare: return (OCShareTypeUserShare); + case OCShareTypesMaskGroupShare: return (OCShareTypeGroupShare); + case OCShareTypesMaskLink: return (OCShareTypeLink); + case OCShareTypesMaskGuest: return (OCShareTypeGuest); + case OCShareTypesMaskRemote: return (OCShareTypeRemote); + } + + return (OCShareTypeUnknown); +} + #pragma mark - Comparison - (NSUInteger)hash { - return (_identifier.hash ^ _token.hash ^ _itemPath.hash ^ _owner.hash ^ _recipient.hash ^ _itemOwner.hash ^ _type); + return (_identifier.hash ^ _token.hash ^ _itemLocation.hash ^ _owner.hash ^ _recipient.hash ^ _itemOwner.hash ^ _type); } - (BOOL)isEqual:(id)object @@ -143,8 +177,9 @@ - (BOOL)isEqual:(id)object return (compareVar(_identifier) && (otherShare->_type == _type) && + (otherShare->_category == _category) && - compareVar(_itemPath) && + compareVar(_itemLocation) && (otherShare->_itemType == _itemType) && compareVar(_itemOwner) && compareVar(_itemMIMEType) && @@ -185,9 +220,27 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _identifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; _type = [decoder decodeIntegerForKey:@"type"]; + _category = [decoder decodeIntegerForKey:@"category"]; + + _itemLocation = [decoder decodeObjectOfClass:OCLocation.class forKey:@"itemLocation"]; + if (_itemLocation == nil) + { + NSString *itemPath; + + if ((itemPath = [decoder decodeObjectOfClass:[NSString class] forKey:@"itemPath"]) != nil) + { + _itemLocation = [OCLocation legacyRootPath:itemPath]; + } + } + + OCItemType legacyType = [decoder decodeIntegerForKey:@"itemType"]; + _itemType = (legacyType == OCItemTypeFile) ? OCLocationTypeFile : OCLocationTypeFolder; + OCLocationType itemType = [decoder decodeIntegerForKey:@"itemLType"]; + if (itemType != OCLocationTypeUnknown) + { + _itemType = itemType; + } - _itemPath = [decoder decodeObjectOfClass:[NSString class] forKey:@"itemPath"]; - _itemType = [decoder decodeIntegerForKey:@"itemType"]; _itemOwner = [decoder decodeObjectOfClass:[OCUser class] forKey:@"itemOwner"]; _itemMIMEType = [decoder decodeObjectOfClass:[NSString class] forKey:@"itemMIMEType"]; @@ -203,7 +256,7 @@ - (instancetype)initWithCoder:(NSCoder *)decoder _expirationDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"expirationDate"]; _owner = [decoder decodeObjectOfClass:[OCUser class] forKey:@"owner"]; - _recipient = [decoder decodeObjectOfClass:[OCRecipient class] forKey:@"recipient"]; + _recipient = [decoder decodeObjectOfClass:[OCIdentity class] forKey:@"recipient"]; _mountPoint = [decoder decodeObjectOfClass:[NSString class] forKey:@"mountPoint"]; _accepted = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"accepted"]; @@ -218,9 +271,10 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_identifier forKey:@"identifier"]; [coder encodeInteger:_type forKey:@"type"]; + [coder encodeInteger:_category forKey:@"category"]; - [coder encodeObject:_itemPath forKey:@"itemPath"]; - [coder encodeInteger:_itemType forKey:@"itemType"]; + [coder encodeObject:_itemLocation forKey:@"itemLocation"]; + [coder encodeInteger:_itemType forKey:@"itemLType"]; [coder encodeObject:_itemOwner forKey:@"itemOwner"]; [coder encodeObject:_itemMIMEType forKey:@"itemMIMEType"]; @@ -246,7 +300,7 @@ - (void)encodeWithCoder:(NSCoder *)coder #pragma mark - Description - (NSString *)description { - NSString *typeAsString = nil, *permissionsString = @""; + NSString *typeAsString = nil, *permissionsString = @"", *itemTypeString = nil, *categoryAsString = nil; switch (_type) { @@ -275,6 +329,19 @@ - (NSString *)description break; } + switch (_category) + { + case OCShareCategoryUnknown: + categoryAsString = @"unknown"; + break; + case OCShareCategoryByMe: + categoryAsString = @"by me"; + break; + case OCShareCategoryWithMe: + categoryAsString = @"with me"; + break; + } + if (self.canRead) { permissionsString = [permissionsString stringByAppendingString:@"read, "]; } if (self.canUpdate) { permissionsString = [permissionsString stringByAppendingString:@"update, "]; } if (self.canCreate) { permissionsString = [permissionsString stringByAppendingString:@"create, "]; } @@ -290,7 +357,41 @@ - (NSString *)description permissionsString = @"none"; } - return ([NSString stringWithFormat:@"<%@: %p, identifier: %@, type: %@, name: %@, itemPath: %@, itemType: %@, itemMIMEType: %@, itemOwner: %@, creationDate: %@, expirationDate: %@, permissions: %@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, _identifier, typeAsString, _name, _itemPath, ((_itemType == OCItemTypeFile) ? @"file" : @"folder"), _itemMIMEType, _itemOwner, _creationDate, _expirationDate, permissionsString, ((_password!=nil) ? @", password: [redacted]" : (_protectedByPassword ? @", protectedByPassword" : @"")), ((_token!=nil)?[NSString stringWithFormat:@", token: %@", _token] : @""), ((_url!=nil)?[NSString stringWithFormat:@", url: %@", _url] : @""), ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), ((_recipient!=nil) ? [NSString stringWithFormat:@", recipient: %@", _recipient] : @""), ((_accepted!=nil) ? [NSString stringWithFormat:@", accepted: %@", _accepted] : @""), ((_state!=nil) ? ([_state isEqual:OCShareStateAccepted] ? @", state: accepted" : ([_state isEqual:OCShareStateRejected] ? @", state: rejected" : [_state isEqual:OCShareStatePending] ? @", state: pending" : @", state: ?!")) : @"")]); + switch (_itemType) + { + case OCLocationTypeUnknown: + itemTypeString = @"unknown"; + break; + case OCLocationTypeFile: + itemTypeString = @"file"; + break; + case OCLocationTypeFolder: + itemTypeString = @"folder"; + break; + case OCLocationTypeDrive: + itemTypeString = @"drive"; + break; + case OCLocationTypeAccount: + itemTypeString = @"account"; + break; + } + + return ([NSString stringWithFormat:@"<%@: %p, identifier: %@, type: %@, category: %@, name: %@, itemLocation: %@, itemType: %@, itemMIMEType: %@, itemOwner: %@, creationDate: %@, expirationDate: %@, permissions: %@%@%@%@%@%@%@%@>", NSStringFromClass(self.class), self, _identifier, typeAsString, categoryAsString, _name, _itemLocation, itemTypeString, _itemMIMEType, _itemOwner, _creationDate, _expirationDate, permissionsString, ((_password!=nil) ? @", password: [redacted]" : (_protectedByPassword ? @", protectedByPassword" : @"")), ((_token!=nil)?[NSString stringWithFormat:@", token: %@", _token] : @""), ((_url!=nil)?[NSString stringWithFormat:@", url: %@", _url] : @""), ((_owner!=nil) ? [NSString stringWithFormat:@", owner: %@", _owner] : @""), ((_recipient!=nil) ? [NSString stringWithFormat:@", recipient: %@", _recipient] : @""), ((_accepted!=nil) ? [NSString stringWithFormat:@", accepted: %@", _accepted] : @""), ((_state!=nil) ? ([_state isEqual:OCShareStateAccepted] ? @", state: accepted" : ([_state isEqual:OCShareStateDeclined] ? @", state: declined" : [_state isEqual:OCShareStatePending] ? @", state: pending" : @", state: ?!")) : @"")]); +} + +- (OCShareState)effectiveState +{ + if (_type == OCShareTypeRemote) + { + if (self.accepted.boolValue) + { + return (OCShareStateAccepted); + } + + return (OCShareStatePending); + } + + return (_state); } #pragma mark - Copying @@ -300,8 +401,9 @@ - (id)copyWithZone:(NSZone *)zone copiedShare->_identifier = _identifier; copiedShare->_type = _type; + copiedShare->_category = _category; - copiedShare->_itemPath = _itemPath; + copiedShare->_itemLocation = _itemLocation; copiedShare->_itemType = _itemType; copiedShare->_itemOwner = _itemOwner; copiedShare->_itemMIMEType = _itemMIMEType; @@ -334,4 +436,4 @@ - (id)copyWithZone:(NSZone *)zone // Values via https://github.com/owncloud/core/blob/master/lib/private/Share/Constants.php OCShareState OCShareStateAccepted = @"0"; OCShareState OCShareStatePending = @"1"; -OCShareState OCShareStateRejected = @"2"; +OCShareState OCShareStateDeclined = @"2"; diff --git a/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.h b/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.h new file mode 100644 index 00000000..8be8faf3 --- /dev/null +++ b/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.h @@ -0,0 +1,28 @@ +// +// OCShareRole+OCDataItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 20.04.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCShareRole.h" +#import "OCDataTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCShareRole (OCDataItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.m b/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.m new file mode 100644 index 00000000..40b04f02 --- /dev/null +++ b/ownCloudSDK/Share/Roles/OCShareRole+OCDataItem.m @@ -0,0 +1,38 @@ +// +// OCShareRole+OCDataItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 20.04.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, 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 "OCShareRole+OCDataItem.h" + +@implementation OCShareRole (OCDataItem) + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeShareRole); +} + +- (OCDataItemReference)dataItemReference +{ + return (self.description); +} + +- (OCDataItemVersion)dataItemVersion +{ + return (self.description); +} + +@end diff --git a/ownCloudSDK/Share/Roles/OCShareRole.h b/ownCloudSDK/Share/Roles/OCShareRole.h new file mode 100644 index 00000000..ddda0096 --- /dev/null +++ b/ownCloudSDK/Share/Roles/OCShareRole.h @@ -0,0 +1,54 @@ +// +// OCShareRole.h +// ownCloudSDK +// +// Created by Felix Schwarz on 13.10.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 "OCSymbol.h" +#import "OCShare.h" +#import "OCLocation.h" + +typedef NSString* OCShareRoleType NS_TYPED_ENUM; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCShareRole : NSObject + +@property(strong) OCShareRoleType type; //!< Abstract, internal type (like "editor" or "viewer") + +@property(assign) OCShareTypesMask shareTypes; //!< Types of share (link, group, user, …) + +@property(assign) OCSharePermissionsMask permissions; //!< Mask of permissions set on the share +@property(assign) OCSharePermissionsMask customizablePermissions; //!< Mask of permissions the user is allowed to customize +@property(assign) OCLocationType locations; //!< Mask of OCLocationTypes this role can be used with + +@property(strong) OCSymbolName symbolName; +@property(strong) NSString *localizedName; +@property(strong) NSString *localizedDescription; + +- (instancetype)initWithType:(OCShareRoleType)type shareTypes:(OCShareTypesMask)shareTypes permissions:(OCSharePermissionsMask)permissions customizablePermissions:(OCSharePermissionsMask)customizablePermissions locations:(OCLocationType)locations symbolName:(OCSymbolName)symbolName localizedName:(NSString *)localizedName localizedDescription:(NSString *)localizedDescription; + +@end + +extern OCShareRoleType OCShareRoleTypeInternal; +extern OCShareRoleType OCShareRoleTypeViewer; +extern OCShareRoleType OCShareRoleTypeUploader; +extern OCShareRoleType OCShareRoleTypeEditor; +extern OCShareRoleType OCShareRoleTypeContributor; +extern OCShareRoleType OCShareRoleTypeManager; +extern OCShareRoleType OCShareRoleTypeCustom; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Share/Roles/OCShareRole.m b/ownCloudSDK/Share/Roles/OCShareRole.m new file mode 100644 index 00000000..f8d956f1 --- /dev/null +++ b/ownCloudSDK/Share/Roles/OCShareRole.m @@ -0,0 +1,51 @@ +// +// OCShareRole.m +// ownCloudSDK +// +// Created by Felix Schwarz on 13.10.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 "OCShareRole.h" + +@implementation OCShareRole + +- (instancetype)initWithType:(OCShareRoleType)type shareTypes:(OCShareTypesMask)shareTypes permissions:(OCSharePermissionsMask)permissions customizablePermissions:(OCSharePermissionsMask)customizablePermissions locations:(OCLocationType)locations symbolName:(OCSymbolName)symbolName localizedName:(NSString *)localizedName localizedDescription:(NSString *)localizedDescription +{ + if ((self = [super init]) != nil) + { + _type = type; + _shareTypes = shareTypes; + + _permissions = permissions; + _customizablePermissions = customizablePermissions; + + _locations = locations; + + _symbolName = symbolName; + _localizedName = localizedName; + _localizedDescription = localizedDescription; + } + + return (self); +} + +@end + +OCShareRoleType OCShareRoleTypeInternal = @"internal"; +OCShareRoleType OCShareRoleTypeViewer = @"viewer"; +OCShareRoleType OCShareRoleTypeUploader = @"uploader"; +OCShareRoleType OCShareRoleTypeEditor = @"editor"; +OCShareRoleType OCShareRoleTypeContributor = @"contributor"; +OCShareRoleType OCShareRoleTypeManager = @"manager"; +OCShareRoleType OCShareRoleTypeCustom = @"custom"; diff --git a/ownCloudSDK/Statistics/OCStatistic.h b/ownCloudSDK/Statistics/OCStatistic.h new file mode 100644 index 00000000..32f7a506 --- /dev/null +++ b/ownCloudSDK/Statistics/OCStatistic.h @@ -0,0 +1,42 @@ +// +// OCStatistic.h +// ownCloudSDK +// +// Created by Felix Schwarz on 14.10.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 + +typedef NSString* OCStatisticUUID; + +@interface OCStatistic : NSObject + +@property(readonly,strong) OCStatisticUUID uuid; //!< UUID of the statistic, random + +@property(nullable,strong) NSString *label; //!< Label describing the statistic + +@property(nullable,strong) NSNumber *itemCount; //!< Number of items + +@property(nullable,strong) NSNumber *fileCount; //!< Number of files +@property(nullable,strong) NSNumber *folderCount; //!< Number of folders + +@property(nullable,strong) NSNumber *sizeInBytes; //!< Size in bytes +@property(nullable,readonly,nonatomic) NSString *localizedSize; //!< Localized string built from .sizeInBytes + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Statistics/OCStatistic.m b/ownCloudSDK/Statistics/OCStatistic.m new file mode 100644 index 00000000..d03a9237 --- /dev/null +++ b/ownCloudSDK/Statistics/OCStatistic.m @@ -0,0 +1,58 @@ +// +// OCStatistic.m +// ownCloudSDK +// +// Created by Felix Schwarz on 14.10.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 "OCStatistic.h" + +@implementation OCStatistic + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _uuid = NSUUID.UUID.UUIDString; + } + + return (self); +} + +- (NSString *)localizedSize +{ + if (_sizeInBytes != nil) + { + return ([NSByteCountFormatter stringFromByteCount:_sizeInBytes.longLongValue countStyle:NSByteCountFormatterCountStyleFile]); + } + + return (nil); +} + +- (OCDataItemType)dataItemType +{ + return (OCDataItemTypeStatistic); +} + +- (OCDataItemReference)dataItemReference +{ + return (_uuid); +} + +- (OCDataItemVersion)dataItemVersion +{ + return (@((_fileCount.unsignedIntegerValue * _folderCount.unsignedIntegerValue) + _fileCount.unsignedIntegerValue - _folderCount.unsignedIntegerValue + _sizeInBytes.unsignedIntegerValue)); +} + +@end diff --git a/ownCloudSDK/TUS/OCTUSJob.h b/ownCloudSDK/TUS/OCTUSJob.h index 66de041d..4b1f186f 100644 --- a/ownCloudSDK/TUS/OCTUSJob.h +++ b/ownCloudSDK/TUS/OCTUSJob.h @@ -20,6 +20,7 @@ #import "OCTUSHeader.h" #import "OCChecksum.h" #import "OCEventTarget.h" +#import "OCDrive.h" NS_ASSUME_NONNULL_BEGIN @@ -46,6 +47,8 @@ NS_ASSUME_NONNULL_BEGIN @property(strong,nullable) NSDate *fileModDate; @property(strong,nullable) OCChecksum *fileChecksum; +@property(strong,nullable) OCDriveID fileDriveID; + @property(strong,nullable) OCEventTarget *eventTarget; - (instancetype)initWithHeader:(OCTUSHeader *)header segmentFolderURL:(NSURL *)segmentFolder fileURL:(NSURL *)fileURL creationURL:(NSURL *)creationURL; diff --git a/ownCloudSDK/TUS/OCTUSJob.m b/ownCloudSDK/TUS/OCTUSJob.m index 0bcc2eb7..13dd3bad 100644 --- a/ownCloudSDK/TUS/OCTUSJob.m +++ b/ownCloudSDK/TUS/OCTUSJob.m @@ -272,6 +272,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder _fileSize = [coder decodeObjectOfClass:NSNumber.class forKey:@"fileSize"]; _fileModDate = [coder decodeObjectOfClass:NSDate.class forKey:@"fileModDate"]; _fileChecksum = [coder decodeObjectOfClass:OCChecksum.class forKey:@"fileChecksum"]; + _fileDriveID = [coder decodeObjectOfClass:NSString.class forKey:@"fileDriveID"]; _eventTarget = [coder decodeObjectOfClass:OCEventTarget.class forKey:@"eventTarget"]; @@ -301,6 +302,7 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_fileSize forKey:@"fileSize"]; [coder encodeObject:_fileModDate forKey:@"fileModDate"]; [coder encodeObject:_fileChecksum forKey:@"fileChecksum"]; + [coder encodeObject:_fileDriveID forKey:@"fileDriveID"]; [coder encodeObject:_eventTarget forKey:@"eventTarget"]; diff --git a/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.h b/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.h index 90c67d93..d9733df5 100644 --- a/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.h +++ b/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.h @@ -27,6 +27,8 @@ typedef void(^OCAsyncSequentialQueueExecutor)(OCAsyncSequentialQueueJob job, dis @property(copy) OCAsyncSequentialQueueExecutor executor; +- (instancetype)initWithQueue:(dispatch_queue_t)dispatchQueue; + - (void)async:(OCAsyncSequentialQueueJob)job; @end diff --git a/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.m b/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.m index ebe91c8e..713405e2 100644 --- a/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.m +++ b/ownCloudSDK/Toolkit/OCAsyncSequentialQueue.m @@ -35,11 +35,16 @@ @implementation OCAsyncSequentialQueue #pragma mark - Init - (instancetype)init +{ + return ([self initWithQueue:dispatch_get_main_queue()]); +} + +- (instancetype)initWithQueue:(dispatch_queue_t)dispatchQueue { if ((self = [super init]) != nil) { self.executor = ^(OCAsyncSequentialQueueJob _Nonnull job, dispatch_block_t _Nonnull completionHandler) { - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_async(dispatchQueue, ^{ job(completionHandler); }); }; diff --git a/ownCloudSDK/VFS/CONCEPT.md b/ownCloudSDK/VFS/CONCEPT.md new file mode 100644 index 00000000..9088ff07 --- /dev/null +++ b/ownCloudSDK/VFS/CONCEPT.md @@ -0,0 +1,38 @@ +# Virtual File System concept + +## Goals +- allow mixing virtual folder structure with actual content + - represent drives as virtual folders + - allow placing virtual folders amidst actual content +- satisfy FileProvider requirements: + - conversion: FPItemURL -> PersistentItemID (sync) + - conversion: PersistentItemID -> FPItemURL (sync) + - conversion: ItemID -> Item (sync) + - ItemID -> Enumerator (sync) + - Item needs to comply to NSFileProviderItem (sync) + - required properties (as per header): + - itemIdentifier + - parentItemIdentifier + - filename + - more required properties (as per doc): + - contentType (-> folder - or file type as UTI) + - capabilities (-> permissions) + - ItemID -> Thumbnail (async) + +## Implementation +- OCVault / OCVaultLocation + - performs necessary mappings as required by FP + - including mapping of virtual nodes/virtual node IDs from/to FPItemURLs +- VFSNode + - represents a file or folder + - builds a tree +- VFSCore + - method to retrieve contents of a virtual node as VFSContent, providing: + - contained VFSNode child nodes + - OCQuery + OCCore (where applicable) + - automatic return of a requested OCCore via OCCoreManager upon deallocation + [- method that, for a node, returns an object that conforms to a `VFSItem` protocol describing the item (this can of course also be the node or item itself, but gives a hook for future customizations)] +- VFSItemID string addressing scheme + - using "\" as separator, not ":", because that might also be used in ocis identifiers + - Real items: I\[bookmarkUUID]\[driveID]\[localID][\[fileName]] + - Virtual items: V\[bookmarkUUID]\[driveID] or V\[vfsNodeID] diff --git a/ownCloudSDK/VFS/OCItem+OCVFSItem.h b/ownCloudSDK/VFS/OCItem+OCVFSItem.h new file mode 100644 index 00000000..2977829a --- /dev/null +++ b/ownCloudSDK/VFS/OCItem+OCVFSItem.h @@ -0,0 +1,27 @@ +// +// OCItem+OCVFSItem.h +// ownCloudSDK +// +// Created by Felix Schwarz on 20.05.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCItem (VFSItem) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/VFS/OCItem+OCVFSItem.m b/ownCloudSDK/VFS/OCItem+OCVFSItem.m new file mode 100644 index 00000000..ae9e21e3 --- /dev/null +++ b/ownCloudSDK/VFS/OCItem+OCVFSItem.m @@ -0,0 +1,49 @@ +// +// OCItem+OCVFSItem.m +// ownCloudSDK +// +// Created by Felix Schwarz on 20.05.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+OCVFSItem.h" + +@implementation OCItem (VFSItem) + +#pragma mark - OCVFSItem +- (OCVFSItemID)vfsItemID +{ + if (self.isRoot) + { + return ([OCVFSNode rootFolderItemIDForBookmarkUUID:self.bookmarkUUID driveID:self.driveID]); + } + + return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.bookmarkUUID driveID:self.driveID localID:self.localID]); +} + +- (OCVFSItemID)vfsParentItemID +{ + if (self.path.parentPath.isRootPath) + { + return ([OCVFSNode rootFolderItemIDForBookmarkUUID:self.bookmarkUUID driveID:self.driveID]); + } + + return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:self.bookmarkUUID driveID:self.driveID localID:self.parentLocalID]); +} + +- (NSString *)vfsItemName +{ + return (self.name); +} + +@end diff --git a/ownCloudSDK/VFS/OCVFSContent.h b/ownCloudSDK/VFS/OCVFSContent.h new file mode 100644 index 00000000..8ad49756 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSContent.h @@ -0,0 +1,41 @@ +// +// OCVFSContent.h +// ownCloudSDK +// +// Created by Felix Schwarz on 04.05.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 "OCVFSNode.h" + +@class OCCore; +@class OCQuery; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVFSContent : NSObject + +@property(strong,nullable) OCBookmark *bookmark; +@property(weak,nullable) OCCore *core; + +@property(strong,nullable) OCVFSNode *containerNode; + +@property(strong,nullable) OCQuery *query; +@property(strong,nullable) NSArray *vfsChildNodes; + +@property(assign) BOOL isSnapshot; //!< If YES, content is not self-refreshing and needs to be re-requested to get the latest version + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/VFS/OCVFSContent.m b/ownCloudSDK/VFS/OCVFSContent.m new file mode 100644 index 00000000..5f453701 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSContent.m @@ -0,0 +1,32 @@ +// +// OCVFSContent.m +// ownCloudSDK +// +// Created by Felix Schwarz on 04.05.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 "OCVFSContent.h" +#import "OCCoreManager.h" + +@implementation OCVFSContent + +- (void)dealloc +{ + if ((_bookmark != nil) && (_core != nil)) + { + [OCCoreManager.sharedCoreManager returnCoreForBookmark:_bookmark completionHandler:nil]; + } +} + +@end diff --git a/ownCloudSDK/VFS/OCVFSCore.h b/ownCloudSDK/VFS/OCVFSCore.h new file mode 100644 index 00000000..d5f66394 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSCore.h @@ -0,0 +1,71 @@ +// +// OCVFSCore.h +// ownCloudSDK +// +// Created by Felix Schwarz on 28.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 "OCVFSTypes.h" +#import "OCVFSNode.h" +#import "OCQuery.h" +#import "OCVFSContent.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCCore; +@class OCVaultLocation; + +@protocol OCVFSCoreDelegate + +- (void)acquireCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void(^)(NSError * _Nullable error, OCCore * _Nullable core))completionHandler; //!< Provide a core for the bookmark (can be called more than once, count requests) +- (void)relinquishCoreForBookmark:(OCBookmark *)bookmark completionHandler:(void(^)(NSError * _Nullable error))completionHandler; //!< Returns a core for the bookmark (can be called more than once, count requests) + +@end + +@interface OCVFSCore : NSObject + +@property(weak,nullable) id delegate; + +#pragma mark - Public interface +- (void)addNodes:(NSArray *)nodes; //!< Adds nodes +- (void)removeNodes:(NSArray *)nodes; //!< Removes nodes + +- (void)setNodes:(NSArray *)nodes; //!< Replaces all existing nodes with nodes + +- (nullable NSURL *)urlForItemIdentifier:(OCVFSItemID)identifier; +- (nullable OCVFSItemID)itemIdentifierForURL:(NSURL *)url; +- (nullable id)itemForIdentifier:(OCVFSItemID)identifier error:(NSError * _Nullable * _Nullable)outError; +- (nullable OCItem *)itemForLocation:(OCLocation *)location error:(NSError * _Nullable * _Nullable)outError; + +@property(readonly,nonatomic,nullable) OCVFSNode *rootNode; + +- (void)provideContentForContainerItemID:(nullable OCVFSItemID)containerItemID changesFromSyncAnchor:(nullable OCSyncAnchor)sinceSyncAnchor completionHandler:(void(^)(NSError * _Nullable error, OCVFSContent * _Nullable content))completionHandler; + ++ (nullable OCVFSItemID)composeVFSItemIDForOCItemWithBookmarkUUID:(OCBookmarkUUIDString)bookmarkUUIDString driveID:(OCDriveID)driveID localID:(OCLocalID)localID; + +#pragma mark - Internals +- (OCVFSNode *)nodeAtPath:(OCPath)vfsPath; +- (OCVFSNode *)driveRootNodeForLocation:(OCLocation *)location; + +- (nullable OCCore *)_acquireCoreForVaultLocation:(OCVaultLocation *)location error:(NSError **)outError; +- (void)_relinquishCore:(OCCore *)core; + +//- (nullable OCVFSNode *)nodeForID:(OCVFSNodeID)nodeID; +//- (nullable OCVFSNode *)retrieveNodeAt:(OCLocation *)location; //!< returns the VFS node for the given location. For virtual paths, use just OCLocation.path, for server-based paths, use OCLocation.bookmarkUUID, OCLocation.driveID (where applicable) and OCLocation.path +//- (nullable NSArray *)retrieveChildNodesAt:(OCLocation *)location; //!< returns the VFS childnodes at the given location. For virtual paths, use just OCLocation.path, for server-based paths, use OCLocation.bookmarkUUID, OCLocation.driveID (where applicable) and OCLocation.path + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/VFS/OCVFSCore.m b/ownCloudSDK/VFS/OCVFSCore.m new file mode 100644 index 00000000..0aa3f77c --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSCore.m @@ -0,0 +1,465 @@ +// +// OCVFSCore.m +// ownCloudSDK +// +// Created by Felix Schwarz on 28.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 "OCVFSCore.h" +#import "OCVault.h" +#import "OCVaultLocation.h" +#import "OCBookmarkManager.h" +#import "OCCoreManager.h" +#import "NSArray+OCFiltering.h" +#import "NSString+OCPath.h" +#import "OCMacros.h" +#import "OCCore+FileProvider.h" + +@interface OCVFSCore () +{ + NSMutableArray *_nodes; + NSMapTable *_nodesByID; +} +@end + +@implementation OCVFSCore + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _nodes = [NSMutableArray new]; + _nodesByID = [NSMapTable weakToWeakObjectsMapTable]; + } + + return (self); +} + +- (void)addNodes:(NSArray *)nodes +{ + @synchronized(_nodes) + { + [_nodes addObjectsFromArray:nodes]; + + for (OCVFSNode *node in nodes) + { + node.vfsCore = self; + [_nodesByID setObject:node forKey:node.identifier]; + } + } + + [self _recreateVirtualFillNodes]; +} + +- (void)removeNodes:(NSArray *)nodes +{ + @synchronized(_nodes) + { + for (OCVFSNode *node in nodes) + { + [_nodesByID removeObjectForKey:node.identifier]; + node.vfsCore = nil; + } + [_nodes removeObjectsInArray:nodes]; + } + [self _recreateVirtualFillNodes]; +} + +- (void)setNodes:(NSArray *)nodes +{ + @synchronized(_nodes) + { + [self removeNodes:_nodes]; + } + [self addNodes:nodes]; +} + +- (void)_recreateVirtualFillNodes +{ + // Create virtual "fill" nodes to fill the gaps between + // virtual paths +} + +- (nullable NSURL *)urlForItemIdentifier:(OCVFSItemID)identifier +{ + OCVaultLocation *location; + + if ((location = [[OCVaultLocation alloc] initWithVFSItemID:identifier]) != nil) + { + if (!location.isVirtual) + { + OCItem *item; + + if ((item = (OCItem *)[self itemForIdentifier:identifier error:NULL]) != nil) + { + if (item.name != nil) + { + location.additionalPathElements = @[ item.name ]; + } + } + } + + return ([OCVault urlForLocation:location]); + } + + return (nil); +} + +- (nullable OCVFSItemID)itemIdentifierForURL:(NSURL *)url +{ + return ([OCVault locationForURL:url].vfsItemID); +} + +- (OCCore *)_acquireCoreForVaultLocation:(OCVaultLocation *)location error:(NSError **)outError +{ + __block OCCore *returnCore = nil; + __block NSError *returnError = nil; + + if (location.bookmarkUUID != nil) + { + OCBookmark *bookmark; + + if ((bookmark = [OCBookmarkManager.sharedBookmarkManager bookmarkForUUID:location.bookmarkUUID]) != nil) + { + if (_delegate != nil) + { + OCSyncExec(acquireCore, { + [_delegate acquireCoreForBookmark:bookmark completionHandler:^(NSError * _Nullable error, OCCore * _Nullable core) { + returnCore = core; + returnError = error; + + OCSyncExecDone(acquireCore); + }]; + }); + } + } + } + + if (outError != NULL) + { + *outError = returnError; + } + + return (returnCore); +} + +- (void)_relinquishCore:(OCCore *)core +{ + if (_delegate != nil) + { + [_delegate relinquishCoreForBookmark:core.bookmark completionHandler:^(NSError * _Nullable error) { + if (error != nil) + { + OCLogError(@"Error returning core: %@", error); + } + }]; + } +} + +- (nullable id)itemForIdentifier:(OCVFSItemID)identifier error:(NSError * _Nullable * _Nullable)outError +{ + OCVaultLocation *location; + __block NSError *returnError = nil; + __block id item = nil; + + if ([identifier isEqual:OCVFSItemIDRoot]) + { + return (self.rootNode); + } + + if ((location = [[OCVaultLocation alloc] initWithVFSItemID:identifier]) != nil) + { + if (location.vfsNodeID != nil) + { + // Virtual item + @synchronized(_nodes) + { + item = (id)[_nodesByID objectForKey:location.vfsNodeID]; + } + } + else if (location.isVirtual) + { + // Virtual item not identified by node ID + if ((location.driveID != nil) && (location.bookmarkUUID != nil)) + { + item = (id)[self driveRootNodeForLocation:[[OCLocation alloc] initWithBookmarkUUID:location.bookmarkUUID driveID:location.driveID path:@"/"]]; + } + } + else + { + // Other item + if ((location.bookmarkUUID != nil) && (location.localID != nil)) + { + NSError *coreError = nil; + OCCore *core = [self _acquireCoreForVaultLocation:location error:&coreError]; + + if (core != nil) + { + if (coreError != nil) + { + returnError = coreError; + } + else + { + OCSyncExec(itemRetrieval, { + [core retrieveItemFromDatabaseForLocalID:location.localID completionHandler:^(NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { + itemFromDatabase.bookmarkUUID = location.bookmarkUUID.UUIDString; + item = (id)itemFromDatabase; + returnError = error; + + OCSyncExecDone(itemRetrieval); + }]; + }); + + [self _relinquishCore:core]; + } + + } + } + } + } + + if (outError != NULL) + { + *outError = returnError; + } + + return (item); +} + +- (nullable OCItem *)itemForLocation:(OCLocation *)location error:(NSError * _Nullable * _Nullable)outError +{ + NSError *returnError = nil; + OCItem *item = nil; + + if (location != nil) + { + // Other item + if ((location.bookmarkUUID != nil) && (location.path != nil)) + { + NSError *coreError = nil; + OCVaultLocation *vaultLocation = [[OCVaultLocation alloc] init]; + OCCore *core; + + vaultLocation.bookmarkUUID = location.bookmarkUUID; + vaultLocation.driveID = location.driveID; + + if ((core = [self _acquireCoreForVaultLocation:vaultLocation error:&coreError]) != nil) + { + if (coreError != nil) + { + returnError = coreError; + } + else + { + item = [core cachedItemAtLocation:location error:&returnError]; + + [self _relinquishCore:core]; + } + } + } + } + + if (outError != NULL) + { + *outError = returnError; + } + + return (item); +} + +- (void)provideContentForContainerItemID:(nullable OCVFSItemID)containerItemID changesFromSyncAnchor:(nullable OCSyncAnchor)sinceSyncAnchor completionHandler:(void(^)(NSError * _Nullable error, OCVFSContent * _Nullable content))completionHandler +{ + OCVFSNode *containerNode = nil; + OCQuery *query = nil; + NSArray *vfsChildNodes = nil; + OCPath vfsContainerPath = nil; + OCLocation *queryLocation = nil; + + if ((containerItemID == nil) || [containerItemID isEqual:OCVFSItemIDRoot]) + { + vfsContainerPath = @"/"; + containerNode = self.rootNode; + + if (containerNode.location != nil) + { + queryLocation = containerNode.location; + } + } + else + { + OCVaultLocation *vaultLocation; + + if ((vaultLocation = [[OCVaultLocation alloc] initWithVFSItemID:containerItemID]) != nil) + { + if (vaultLocation.isVirtual) + { + if (vaultLocation.vfsNodeID != nil) + { + containerNode = [self nodeForID:vaultLocation.vfsNodeID]; + } + else if ((vaultLocation.bookmarkUUID != nil) && (vaultLocation.driveID != nil)) + { + containerNode = [self driveRootNodeForLocation:[[OCLocation alloc] initWithBookmarkUUID:vaultLocation.bookmarkUUID driveID:vaultLocation.driveID path:@"/"]]; + } + + if (containerNode != nil) + { + queryLocation = containerNode.location; + vfsContainerPath = containerNode.path; + } + } + else + { + NSError *error = nil; + OCItem *item = (OCItem *)[self itemForIdentifier:containerItemID error:&error]; + + if ([item isKindOfClass:OCItem.class]) + { + queryLocation = [[OCLocation alloc] init]; + + queryLocation.bookmarkUUID = vaultLocation.bookmarkUUID; + queryLocation.driveID = vaultLocation.driveID; + queryLocation.path = item.path; + } + } + } + } + + if (vfsContainerPath != nil) + { + vfsChildNodes = [self childNodesOf:vfsContainerPath]; + } + + if (queryLocation != nil) + { + if (sinceSyncAnchor != nil) + { + query = [OCQuery queryForChangesSinceSyncAnchor:sinceSyncAnchor]; + } + else + { + query = [OCQuery queryForLocation:queryLocation]; + } + + OCBookmarkUUID bookmarkUUID; + OCBookmark *bookmark = nil; + + if ((bookmarkUUID = queryLocation.bookmarkUUID) != nil) + { + bookmark = [OCBookmarkManager.sharedBookmarkManager bookmarkForUUID:bookmarkUUID]; + } + + if (bookmark != nil) + { + [OCCoreManager.sharedCoreManager requestCoreForBookmark:bookmark setup:nil completionHandler:^(OCCore * _Nullable core, NSError * _Nullable error) { + if (error != nil) + { + completionHandler(error, nil); + } + else + { + OCVFSContent *content = [[OCVFSContent alloc] init]; + + content.bookmark = bookmark; + content.core = core; + + content.vfsChildNodes = vfsChildNodes; + content.isSnapshot = (vfsChildNodes.count > 0); + content.containerNode = containerNode; + content.query = query; + + completionHandler(nil, content); + } + }]; + + return; + } + } + + OCVFSContent *content = [[OCVFSContent alloc] init]; + + content.vfsChildNodes = vfsChildNodes; + content.isSnapshot = (vfsChildNodes.count > 0); + content.containerNode = containerNode; + content.query = query; + + completionHandler(nil, content); +} + +- (OCVFSNode *)rootNode +{ + return ([self nodeAtPath:@"/"]); +} + +- (OCVFSNode *)driveRootNodeForLocation:(OCLocation *)location +{ + if (location == nil) { return (nil); } + + @synchronized(_nodes) + { + return ([_nodes firstObjectMatching:^BOOL(OCVFSNode * _Nonnull node) { + return ([node.location.bookmarkUUID isEqual:location.bookmarkUUID] && OCNAIsEqual(node.location.driveID, location.driveID) && node.location.path.isRootPath); + }]); + } +} + +- (OCVFSNode *)nodeAtPath:(OCPath)vfsPath +{ + if (vfsPath == nil) { return (nil); } + + @synchronized(_nodes) + { + return ([_nodes firstObjectMatching:^BOOL(OCVFSNode * _Nonnull node) { + return ([node.path isEqual:vfsPath]); + }]); + } +} + +- (NSArray *)childNodesOf:(OCPath)path +{ + @synchronized(_nodes) + { + return ([_nodes filteredArrayUsingBlock:^BOOL(OCVFSNode * _Nonnull node, BOOL * _Nonnull stop) { + return ([node.path.parentPath isEqual:path] && ![node.path isEqual:path]); + }]); + } +} + +- (nullable OCVFSNode *)nodeForID:(OCVFSNodeID)nodeID +{ + @synchronized(_nodes) + { + return ([_nodesByID objectForKey:nodeID]); + } +} + ++ (OCVFSItemID)composeVFSItemIDForOCItemWithBookmarkUUID:(OCBookmarkUUIDString)bookmarkUUIDString driveID:(OCDriveID)driveID localID:(OCLocalID)localID +{ + if ((bookmarkUUIDString == nil) || (localID == nil)) + { + return (nil); + } + + if (driveID != nil) + { + return ([[NSString alloc] initWithFormat:@"I\\%@\\%@\\%@", bookmarkUUIDString, driveID, localID]); + } + + return ([[NSString alloc] initWithFormat:@"I\\%@\\%@", bookmarkUUIDString, localID]); +} + +@end + +OCVFSItemID OCVFSItemIDRoot = @"R"; diff --git a/ownCloudSDK/VFS/OCVFSNode.h b/ownCloudSDK/VFS/OCVFSNode.h new file mode 100644 index 00000000..681c9490 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSNode.h @@ -0,0 +1,55 @@ +// +// OCVFSNode.h +// ownCloudSDK +// +// Created by Felix Schwarz on 28.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 "OCVFSTypes.h" +#import "OCItem.h" + +@class OCVFSCore; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVFSNode : NSObject + +@property(weak,nullable,nonatomic) OCVFSCore *vfsCore; +@property(weak,nullable,readonly,nonatomic) OCVFSNode *parentNode; + +@property(strong,nonatomic) OCVFSNodeID identifier; //!< Internal ID of virtual nodes. +@property(assign) OCVFSNodeType type; //!< The type of VFS node. +@property(assign) BOOL autogeneratedFillNode; //!< YES if this node has been autogenerated to fill a gap in the tree, i.e. if /some/folder is added and /some doesn't exist, the VFS core autogenerates a /some "fill node". Needed primarily for internal housekeeping. + +@property(strong,readonly,nonatomic) OCVFSItemID itemID; //!< Computed item ID +//@property(strong,nullable) OCVFSItemID aliasItemID; //!< Alias item ID. When set, is used to locate the VFSNode for a "real" item. + +@property(strong,nonatomic) NSString *name; //!< The name of the VFS node. +@property(strong,nonatomic) OCPath path; //!< The virtual path of the VFS node. + +@property(strong, nullable) OCLocation *location; //!< The real location of a folder on a server +@property(strong, nullable, nonatomic) OCItem *locationItem; //!< The item at the location + +@property(readonly,nonatomic) BOOL isRootNode; //!< Returns YES if this node is at the root of the VFS + ++ (OCVFSNode *)virtualFolderAtPath:(OCPath)path location:(nullable OCLocation *)location; + ++ (OCVFSNode *)virtualFolderInPath:(OCPath)path withName:(NSString *)name location:(nullable OCLocation *)location; + ++ (OCVFSItemID)rootFolderItemIDForBookmarkUUID:(OCBookmarkUUIDString)bookmarkUUIDString driveID:(nullable OCDriveID)driveID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/VFS/OCVFSNode.m b/ownCloudSDK/VFS/OCVFSNode.m new file mode 100644 index 00000000..61e19078 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSNode.m @@ -0,0 +1,179 @@ +// +// OCVFSNode.m +// ownCloudSDK +// +// Created by Felix Schwarz on 28.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 "OCVFSNode.h" +#import "NSData+OCHash.h" +#import "NSString+OCPath.h" +#import "OCVFSCore.h" +#import "OCLocation.h" + +@interface OCVFSNode () +{ + OCVFSNodeID _identifier; + OCVFSItemID _itemID; +} +@end + +@implementation OCVFSNode + +- (OCVFSNode *)parentNode +{ + if (_path.isRootPath) + { + return (nil); + } + + return ([_vfsCore nodeAtPath:_path.parentPath]); +} + +- (void)setVfsCore:(OCVFSCore *)vfsCore +{ + _vfsCore = vfsCore; + +// [self locationItem]; +} + +- (OCVFSNodeID)identifier +{ + if (_identifier == nil) + { + NSString *hashBasis = ((_name != nil) ? [_path stringByAppendingPathComponent:_name] : _path); + + if (hashBasis != nil) + { + _identifier = [[[hashBasis dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] asHexStringWithSeparator:nil]; + } + } + + return (_identifier); +} + +- (OCVFSItemID)itemID +{ + if (self.isRootNode) + { + return (OCVFSItemIDRoot); + } + + if (_itemID == nil) + { + if ((_location != nil) && (_location.bookmarkUUID != nil) && (_location.driveID != nil) && (_location.path.isRootPath)) + { + _itemID = [OCVFSNode rootFolderItemIDForBookmarkUUID:_location.bookmarkUUID.UUIDString driveID:_location.driveID]; + } + else + { + _itemID = [@"V\\" stringByAppendingString:self.identifier]; + } + } + + return (_itemID); +} + +- (OCItem *)locationItem +{ + if ((_location != nil) && (_locationItem == nil)) + { + _locationItem = [_vfsCore itemForLocation:_location error:NULL]; + } + + return (_locationItem); +} + +- (void)setPath:(OCPath)path +{ + _path = path; + + _identifier = nil; + _itemID = nil; +} + +- (void)setName:(NSString *)name +{ + _name = name; + _identifier = nil; + _itemID = nil; +} + +- (BOOL)isRootNode +{ + return ([_path isEqual:@"/"]); +} + ++ (OCVFSNode *)virtualFolderAtPath:(OCPath)path location:(nullable OCLocation *)location +{ + OCVFSNode *node = [self new]; + + node.type = (location != nil) ? OCVFSNodeTypeLocation : OCVFSNodeTypeVirtualFolder; + + node.path = path; + node.name = path.lastPathComponent; + + node.location = location; + + return (node); +} + ++ (OCVFSNode *)virtualFolderInPath:(OCPath)path withName:(NSString *)name location:(OCLocation *)location; +{ + OCVFSNode *node = [self new]; + + node.type = (location != nil) ? OCVFSNodeTypeLocation : OCVFSNodeTypeVirtualFolder; + + node.path = [path stringByAppendingPathComponent:name].normalizedDirectoryPath; + node.name = name; + + node.location = location; + + return (node); +} + ++ (OCVFSItemID)rootFolderItemIDForBookmarkUUID:(OCBookmarkUUIDString)bookmarkUUIDString driveID:(nullable OCDriveID)driveID +{ + if (driveID != nil) + { + return ([[NSString alloc] initWithFormat:@"V\\%@\\%@", bookmarkUUIDString, driveID]); + } + + return (OCVFSItemIDRoot); +} + +#pragma mark - OCVFSItem +- (OCVFSItemID)vfsItemID +{ + OCVFSItemID vfsItemID = self.itemID; + + if ([vfsItemID isEqual:OCVFSItemIDRoot]) + { + return (NSFileProviderRootContainerItemIdentifier); + } + + return (vfsItemID); +} + +- (OCVFSItemID)vfsParentItemID +{ + return (self.parentNode.vfsItemID); +} + +- (NSString *)vfsItemName +{ + return (self.name); +} + +@end diff --git a/ownCloudSDK/VFS/OCVFSTypes.h b/ownCloudSDK/VFS/OCVFSTypes.h new file mode 100644 index 00000000..5c0e7404 --- /dev/null +++ b/ownCloudSDK/VFS/OCVFSTypes.h @@ -0,0 +1,60 @@ +// +// OCVFSTypes.h +// ownCloudSDK +// +// Created by Felix Schwarz on 02.05.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 "OCLocation.h" +#import "OCTypes.h" + +typedef NS_ENUM(NSInteger, OCVFSNodeType) +{ + OCVFSNodeTypeVirtualFolder, //!< Virtual folder node hosting child nodes + OCVFSNodeTypeLocation, //!< Virtual node whose content is provided by a location + OCVFSNodeTypeQuery //!< Virtual node whose content is provided by a query +}; + +//typedef id OCVFSOpaqueItem; +typedef NSString* OCVFSNodeID; + +typedef NSString* OCVFSItemID NS_TYPED_EXTENSIBLE_ENUM; // The Virtual File System Item ID, used as NSFileProviderItemIdentifier (with OCVFSItemIDRoot being mapped to NSFileProviderRootContainerItemIdentifier) +/** + Supported OCVFSItemID formats: + - Root node: + R + + - OCItem: + I\[bookmarkUUID]\[driveID]\[localID] // used for drive-based accounts + I\[bookmarkUUID]\[localID] // used for OC10 accounts + + - OCVFSNode: + V\[nodeID] + V\[bookmarkUUID]\[driveID] + */ + +NS_ASSUME_NONNULL_BEGIN + +@protocol OCVFSItem + +@property(readonly,nonatomic,nullable) OCVFSItemID vfsItemID; +@property(readonly,nonatomic,nullable) OCVFSItemID vfsParentItemID; +@property(readonly,nonatomic,nullable) NSString *vfsItemName; + +@end + +extern OCVFSItemID OCVFSItemIDRoot; + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.h b/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.h index 35241040..bf3ba3d2 100644 --- a/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.h +++ b/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.h @@ -30,6 +30,7 @@ extern OCDatabaseTableName OCDatabaseTableNameSyncJournal; extern OCDatabaseTableName OCDatabaseTableNameSyncLanes; extern OCDatabaseTableName OCDatabaseTableNameUpdateJobs; extern OCDatabaseTableName OCDatabaseTableNameThumbnails; +extern OCDatabaseTableName OCDatabaseTableNameResources; extern OCDatabaseTableName OCDatabaseTableNameCounters; extern OCDatabaseTableName OCDatabaseTableNameEvents; extern OCDatabaseTableName OCDatabaseTableNameItemPolicies; diff --git a/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.m b/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.m index d13493c9..76f9172b 100644 --- a/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.m +++ b/ownCloudSDK/Vaults/Database/OCDatabase+Schemas.m @@ -21,6 +21,7 @@ #import "OCSQLiteTransaction.h" #import "OCSyncLane.h" #import "OCMacros.h" +#import "OCItem+OCTypeAlias.h" #define INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER \ __block NSError *transactionError = nil; \ @@ -41,6 +42,7 @@ - (void)addSchemas [self addOrUpdateMetaDataSchema]; [self addOrUpdateThumbnailsSchema]; + [self addOrUpdateResourceSchema]; [self addOrUpdateSyncLanesSchema]; [self addOrUpdateSyncJournalSchema]; @@ -48,7 +50,7 @@ - (void)addSchemas [self addOrUpdateItemPoliciesSchema]; - [self addOrUpdateUpdateScanPaths]; + [self addOrUpdateUpdateJobs]; } - (void)addOrUpdateMetaDataSchema @@ -896,6 +898,317 @@ - (void)addOrUpdateMetaDataSchema }]]; }] ]; + + // Version 15 + /* + Add driveID column + index + */ + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameMetaData + version:15 + creationQueries:@[ + /* + mdID : INTEGER - unique ID used to uniquely identify and efficiently update a row + type : INTEGER - OCItemType value to indicate if this is a file or a collection/folder + syncAnchor: INTEGER - sync anchor, a number that increases its value with every change to an entry. For files, higher sync anchor values indicate the file changed (incl. creation, content or meta data changes). For collections/folders, higher sync anchor values indicate the list of items in the collection/folder changed in a way not covered by file entries (i.e. rename, deletion, but not creation of files). + removed : INTEGER - value indicating if this file or folder has been removed: 1 if it was, 0 if not (default). Removed entries are kept around until their delta to the latest syncAnchor value exceeds -[OCDatabase removedItemRetentionLength]. + mdTimestamp: INTEGER - NSDate.timeIntervalSinceReferenceDate value of creation or last update of this record + locallyModified: INTEGER - value indicating if this is a file that's been created or modified locally + localRelativePath: TEXT - path of the local copy of the item, relative to the rootURL of the vault that stores it + path : TEXT - full path of the item (e.g. "/example/file.txt") + parentPath : TEXT - parent path of the item. (e.g. "/example" for an item at "/example/file.txt") + name : TEXT - name of the item (e.g. "file.txt" for an item at "/example/file.txt") + mimeType : TEXT - MIME type of the item + size : INTEGER - size of the item + favorite : INTEGER - BOOL indicating if the item is favorite (OCItem.isFavorite) + cloudStatus : INTEGER - Cloud status of the item (OCItem.cloudStatus) + downloadTrigger : TEXT - What triggered the download of the item (OCItemDownloadTriggerID) + hasLocalAttributes : INTEGER - BOOL indicating an item with local attributes (OCItem.hasLocalAttributes) + lastUsedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastUsed + lastModifiedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastModified + syncActivity : INTEGER - OCSyncActivity mask indicating which sync activity the item has (0 for none) (OCItem.syncActivity) + ownerUserName : TEXT - User name of the owner of this item (OCItem.user.userName) + driveID : TEXT - OCDriveID identifying the drive the item is located on + fileID : TEXT - OCFileID identifying the item + localID : TEXT - OCLocalID identifying the item + itemData : BLOB - data of the serialized OCItem + */ + @"CREATE TABLE metaData (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)", + + // Create indexes over path and parentPath + @"CREATE INDEX idx_metaData_path ON metaData (path)", + @"CREATE INDEX idx_metaData_parentPath ON metaData (parentPath)", + @"CREATE INDEX idx_metaData_synchAnchor ON metaData (syncAnchor)", + @"CREATE INDEX idx_metaData_localID ON metaData (localID)", + @"CREATE INDEX idx_metaData_driveID ON metaData (driveID)", + @"CREATE INDEX idx_metaData_fileID ON metaData (fileID)", + @"CREATE INDEX idx_metaData_removed ON metaData (removed)", + ] + openStatements:@[ + // Create trigger to delete thumbnails alongside metadata entries + @"CREATE TEMPORARY TRIGGER temp_delete_associated_thumbnails AFTER DELETE ON metaData BEGIN DELETE FROM thumb.thumbnails WHERE fileID = OLD.fileID; END" // relatedTo:OCDatabaseTableNameThumbnails + ] + upgradeMigrator:^(OCSQLiteDB *db, OCSQLiteTableSchema *schema, void (^completionHandler)(NSError *error)) { + // Migrate to version 15 + [db executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError *(OCSQLiteDB *db, OCSQLiteTransaction *transaction) { + INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER + + // Create new table + [db executeQuery:[OCSQLiteQuery query: + @"CREATE TABLE metaData_new (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Migrate data to new table + [db executeQuery:[OCSQLiteQuery query:@"INSERT INTO metaData_new (mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, fileID, localID, itemData) SELECT mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, fileID, localID, itemData FROM metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Drop old table + [db executeQuery:[OCSQLiteQuery query:@"DROP TABLE metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Rename new table + [db executeQuery:[OCSQLiteQuery query:@"ALTER TABLE metaData_new RENAME TO metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + return (transactionError); + } type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + completionHandler(error); + }]]; + }] + ]; + + // Version 16 + /* + Add typeAlias column + index + */ + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameMetaData + version:16 + creationQueries:@[ + /* + mdID : INTEGER - unique ID used to uniquely identify and efficiently update a row + type : INTEGER - OCItemType value to indicate if this is a file or a collection/folder + syncAnchor: INTEGER - sync anchor, a number that increases its value with every change to an entry. For files, higher sync anchor values indicate the file changed (incl. creation, content or meta data changes). For collections/folders, higher sync anchor values indicate the list of items in the collection/folder changed in a way not covered by file entries (i.e. rename, deletion, but not creation of files). + removed : INTEGER - value indicating if this file or folder has been removed: 1 if it was, 0 if not (default). Removed entries are kept around until their delta to the latest syncAnchor value exceeds -[OCDatabase removedItemRetentionLength]. + mdTimestamp: INTEGER - NSDate.timeIntervalSinceReferenceDate value of creation or last update of this record + locallyModified: INTEGER - value indicating if this is a file that's been created or modified locally + localRelativePath: TEXT - path of the local copy of the item, relative to the rootURL of the vault that stores it + path : TEXT - full path of the item (e.g. "/example/file.txt") + parentPath : TEXT - parent path of the item. (e.g. "/example" for an item at "/example/file.txt") + name : TEXT - name of the item (e.g. "file.txt" for an item at "/example/file.txt") + mimeType : TEXT - MIME type of the item (OCMIMEType) + typeAlias : TEXT - Type alias of the item (OCTypeAlias) + size : INTEGER - size of the item + favorite : INTEGER - BOOL indicating if the item is favorite (OCItem.isFavorite) + cloudStatus : INTEGER - Cloud status of the item (OCItem.cloudStatus) + downloadTrigger : TEXT - What triggered the download of the item (OCItemDownloadTriggerID) + hasLocalAttributes : INTEGER - BOOL indicating an item with local attributes (OCItem.hasLocalAttributes) + lastUsedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastUsed + lastModifiedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastModified + syncActivity : INTEGER - OCSyncActivity mask indicating which sync activity the item has (0 for none) (OCItem.syncActivity) + ownerUserName : TEXT - User name of the owner of this item (OCItem.user.userName) + driveID : TEXT - OCDriveID identifying the drive the item is located on + fileID : TEXT - OCFileID identifying the item + localID : TEXT - OCLocalID identifying the item + itemData : BLOB - data of the serialized OCItem + */ + @"CREATE TABLE metaData (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, typeAlias TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)", + + // Create indexes over path and parentPath + @"CREATE INDEX idx_metaData_path ON metaData (path)", + @"CREATE INDEX idx_metaData_parentPath ON metaData (parentPath)", + @"CREATE INDEX idx_metaData_synchAnchor ON metaData (syncAnchor)", + @"CREATE INDEX idx_metaData_localID ON metaData (localID)", + @"CREATE INDEX idx_metaData_driveID ON metaData (driveID)", + @"CREATE INDEX idx_metaData_fileID ON metaData (fileID)", + @"CREATE INDEX idx_metaData_typeAlias ON metaData (typeAlias)", + @"CREATE INDEX idx_metaData_removed ON metaData (removed)", + ] + openStatements:@[ + // Create trigger to delete thumbnails alongside metadata entries + @"CREATE TEMPORARY TRIGGER temp_delete_associated_thumbnails AFTER DELETE ON metaData BEGIN DELETE FROM thumb.thumbnails WHERE fileID = OLD.fileID; END" // relatedTo:OCDatabaseTableNameThumbnails + ] + upgradeMigrator:^(OCSQLiteDB *db, OCSQLiteTableSchema *schema, void (^completionHandler)(NSError *error)) { + // Migrate to version 16 + [db executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError *(OCSQLiteDB *db, OCSQLiteTransaction *transaction) { + INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER + + // Create new table + [db executeQuery:[OCSQLiteQuery query: + @"CREATE TABLE metaData_new (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, typeAlias TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Migrate data to new table + [db executeQuery:[OCSQLiteQuery query:@"INSERT INTO metaData_new (mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, driveID, fileID, localID, itemData) SELECT mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, driveID, fileID, localID, itemData FROM metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Set up progress reporting + __block NSUInteger rowCount = 0; + __block NSUInteger processedRows = 0; + __block NSProgress *migrationProgress = nil; + + if ((migrationProgress = schema.migrationProgress) != nil) + { + [db executeQuery:[OCSQLiteQuery query:@"SELECT COUNT(*) AS cnt FROM metaData_new" resultHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error, OCSQLiteTransaction * _Nullable transaction, OCSQLiteResultSet * _Nullable resultSet) { + OCSQLiteRowDictionary resultDict; + + if ((resultDict = [resultSet nextRowDictionaryWithError:NULL]) != nil) + { + if ((rowCount = [OCTypedCast(resultDict[@"cnt"], NSNumber) unsignedIntValue]) > 0) + { + migrationProgress.totalUnitCount = rowCount; + } + else + { + migrationProgress = nil; + } + } + + resultHandler(db, error, transaction, resultSet); + }]]; + if (transactionError != nil) { return(transactionError); } + } + + // Fill new typeAlias columns with real data + [db executeQuery:[OCSQLiteQuery querySelectingColumns:@[@"mdID", @"mimeType"] fromTable:@"metaData_new" where:nil resultHandler:^(OCSQLiteDB *db, NSError *error, OCSQLiteTransaction *transaction, OCSQLiteResultSet *resultSet) { + // Derive typeAlias from mimeType + [resultSet iterateUsing:^(OCSQLiteResultSet *resultSet, NSUInteger line, NSDictionary *rowDictionary, BOOL *stop) { + OCMIMEType mimeType; + if ((rowDictionary[@"mdID"] != nil) && ((mimeType = rowDictionary[@"mimeType"]) != nil) && ((mimeType = OCNullResolved(mimeType)) != nil)) + { + OCTypeAlias typeAlias; + + if ((typeAlias = [OCItem typeAliasForMIMEType:mimeType]) != nil) + { + [db executeQuery:[OCSQLiteQuery queryUpdatingRowWithID:rowDictionary[@"mdID"] + inTable:@"metaData_new" + withRowValues:@{ + @"typeAlias" : typeAlias, + } + completionHandler:^(OCSQLiteDB *db, NSError *error) { + if (error != nil) + { + transactionError = error; + } + } + ] + ]; + } + } + + processedRows++; + + if (migrationProgress != nil) + { + if ((processedRows % 100) == 0) + { + migrationProgress.completedUnitCount = processedRows; + } + } + } error:&transactionError]; + }]]; + if (transactionError != nil) { return(transactionError); } + + // Drop old table + [db executeQuery:[OCSQLiteQuery query:@"DROP TABLE metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Rename new table + [db executeQuery:[OCSQLiteQuery query:@"ALTER TABLE metaData_new RENAME TO metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + return (transactionError); + } type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + completionHandler(error); + }]]; + }] + ]; + + // Version 17 + /* + Add typeAlias column + index + */ + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameMetaData + version:17 + creationQueries:@[ + /* + mdID : INTEGER - unique ID used to uniquely identify and efficiently update a row + type : INTEGER - OCItemType value to indicate if this is a file or a collection/folder + syncAnchor: INTEGER - sync anchor, a number that increases its value with every change to an entry. For files, higher sync anchor values indicate the file changed (incl. creation, content or meta data changes). For collections/folders, higher sync anchor values indicate the list of items in the collection/folder changed in a way not covered by file entries (i.e. rename, deletion, but not creation of files). + removed : INTEGER - value indicating if this file or folder has been removed: 1 if it was, 0 if not (default). Removed entries are kept around until their delta to the latest syncAnchor value exceeds -[OCDatabase removedItemRetentionLength]. + mdTimestamp: INTEGER - NSDate.timeIntervalSinceReferenceDate value of creation or last update of this record + locallyModified: INTEGER - value indicating if this is a file that's been created or modified locally + localRelativePath: TEXT - path of the local copy of the item, relative to the rootURL of the vault that stores it + locationString : TEXT - OCLocation.string, built from driveID + path, can be used to find all items inside a folder on a drive + path : TEXT - full path of the item (e.g. "/example/file.txt") + parentPath : TEXT - parent path of the item. (e.g. "/example" for an item at "/example/file.txt") + name : TEXT - name of the item (e.g. "file.txt" for an item at "/example/file.txt") + mimeType : TEXT - MIME type of the item (OCMIMEType) + typeAlias : TEXT - Type alias of the item (OCTypeAlias) + size : INTEGER - size of the item + favorite : INTEGER - BOOL indicating if the item is favorite (OCItem.isFavorite) + cloudStatus : INTEGER - Cloud status of the item (OCItem.cloudStatus) + downloadTrigger : TEXT - What triggered the download of the item (OCItemDownloadTriggerID) + hasLocalAttributes : INTEGER - BOOL indicating an item with local attributes (OCItem.hasLocalAttributes) + lastUsedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastUsed + lastModifiedDate : REAL - NSDate.timeIntervalSince1970 value of OCItem.lastModified + syncActivity : INTEGER - OCSyncActivity mask indicating which sync activity the item has (0 for none) (OCItem.syncActivity) + ownerUserName : TEXT - User name of the owner of this item (OCItem.user.userName) + driveID : TEXT - OCDriveID identifying the drive the item is located on + fileID : TEXT - OCFileID identifying the item + localID : TEXT - OCLocalID identifying the item + itemData : BLOB - data of the serialized OCItem + */ + @"CREATE TABLE metaData (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, locationString TEXT NOT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, typeAlias TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)", + + // Create indexes over path and parentPath + @"CREATE INDEX idx_metaData_locationString ON metaData (locationString)", + @"CREATE INDEX idx_metaData_path ON metaData (path)", + @"CREATE INDEX idx_metaData_parentPath ON metaData (parentPath)", + @"CREATE INDEX idx_metaData_synchAnchor ON metaData (syncAnchor)", + @"CREATE INDEX idx_metaData_localID ON metaData (localID)", + @"CREATE INDEX idx_metaData_driveID ON metaData (driveID)", + @"CREATE INDEX idx_metaData_fileID ON metaData (fileID)", + @"CREATE INDEX idx_metaData_typeAlias ON metaData (typeAlias)", + @"CREATE INDEX idx_metaData_removed ON metaData (removed)", + ] + openStatements:@[ + // Create trigger to delete thumbnails alongside metadata entries + @"CREATE TEMPORARY TRIGGER temp_delete_associated_thumbnails AFTER DELETE ON metaData BEGIN DELETE FROM thumb.thumbnails WHERE fileID = OLD.fileID; END" // relatedTo:OCDatabaseTableNameThumbnails + ] + upgradeMigrator:^(OCSQLiteDB *db, OCSQLiteTableSchema *schema, void (^completionHandler)(NSError *error)) { + // Migrate to version 16 + [db executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError *(OCSQLiteDB *db, OCSQLiteTransaction *transaction) { + INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER + + // Create new table + [db executeQuery:[OCSQLiteQuery query: + @"CREATE TABLE metaData_new (mdID INTEGER PRIMARY KEY AUTOINCREMENT, type INTEGER NOT NULL, syncAnchor INTEGER NOT NULL, removed INTEGER NOT NULL, mdTimestamp INTEGER NOT NULL, locallyModified INTEGER NOT NULL, localRelativePath TEXT NULL, locationString TEXT NOT NULL, path TEXT NOT NULL, parentPath TEXT NOT NULL, name TEXT NOT NULL COLLATE OCLOCALIZED, mimeType TEXT NULL, typeAlias TEXT NULL, size INTEGER NOT NULL, favorite INTEGER NOT NULL, cloudStatus INTEGER NOT NULL, downloadTrigger TEXT NULL, hasLocalAttributes INTEGER NOT NULL, lastUsedDate REAL NULL, lastModifiedDate REAL NULL, syncActivity INTEGER NULL, ownerUserName TEXT, driveID TEXT, fileID TEXT, localID TEXT, itemData BLOB NOT NULL)" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Migrate data to new table + [db executeQuery:[OCSQLiteQuery query:@"INSERT INTO metaData_new (mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, locationString, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, driveID, fileID, localID, itemData) SELECT mdID, type, syncAnchor, removed, mdTimestamp, locallyModified, localRelativePath, ';' || COALESCE(driveID,'') || ':' || path, path, parentPath, name, mimeType, size, favorite, cloudStatus, downloadTrigger, hasLocalAttributes, lastUsedDate, lastModifiedDate, syncActivity, ownerUserName, driveID, fileID, localID, itemData FROM metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Drop old table + [db executeQuery:[OCSQLiteQuery query:@"DROP TABLE metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Rename new table + [db executeQuery:[OCSQLiteQuery query:@"ALTER TABLE metaData_new RENAME TO metaData" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + // Create "locationString" index + [db executeQuery:[OCSQLiteQuery query:@"CREATE INDEX idx_metaData_locationString ON metaData (locationString)" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + return (transactionError); + } type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + completionHandler(error); + }]]; + }] + ]; } - (void)addOrUpdateSyncLanesSchema @@ -1151,7 +1464,7 @@ - (void)addOrUpdateSyncJournalSchema ]; } -- (void)addOrUpdateUpdateScanPaths +- (void)addOrUpdateUpdateJobs { /*** Update Scan Paths ***/ @@ -1169,6 +1482,42 @@ - (void)addOrUpdateUpdateScanPaths openStatements:nil upgradeMigrator:nil] ]; + + // Version 2 + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameUpdateJobs + version:2 + creationQueries:@[ + /* + jobID : INTEGER - unique ID used to uniquely identify and efficiently update a row + driveID : TEXT - driveID of the drive on which path is located + path : TEXT - path to scan as part of an update + */ + @"CREATE TABLE updateJobs (jobID INTEGER PRIMARY KEY AUTOINCREMENT, driveID TEXT, path TEXT NOT NULL)", // relatedTo:OCDatabaseTableNameUpdateJobs + + // Create index over path + @"CREATE INDEX idx_updateJobs_path ON updateJobs (path)" // relatedTo:OCDatabaseTableNameUpdateJobs + ] + openStatements:nil + upgradeMigrator:^(OCSQLiteDB *db, OCSQLiteTableSchema *schema, void (^completionHandler)(NSError *error)) { + // Migrate to version 5 + [db executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError *(OCSQLiteDB *sqlDB, OCSQLiteTransaction *transaction) { + INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER + + // Add revision column + [sqlDB executeQuery:[OCSQLiteQuery query:@"ALTER TABLE updateJobs ADD COLUMN driveID TEXT" resultHandler:resultHandler]]; // relatedTo:OCDatabaseTableNameUpdateJobs + if (transactionError != nil) { return(transactionError); } + + // Add index over path + [sqlDB executeQuery:[OCSQLiteQuery query:@"CREATE INDEX idx_updateJobs_path ON updateJobs (path)" resultHandler:resultHandler]]; // relatedTo:OCDatabaseTableNameUpdateJobs + if (transactionError != nil) { return(transactionError); } + + return (transactionError); + } type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + completionHandler(error); + }]]; + }] + ]; } - (void)addOrUpdateEvents @@ -1386,6 +1735,73 @@ - (void)addOrUpdateThumbnailsSchema }]]; }] ]; + + // Version 3 (retire) + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameThumbnails + version:3 + creationQueries:@[ + /* + tnID : INTEGER - unique ID used to uniquely identify and efficiently update a row + fileID : TEXT - OCFileID of the item to which this thumbnail belongs + eTag : TEXT - OCFileETag of the item to which this thumbnail belongs + specID : TEXT - a string consisting of other attributes affecting thumbnail creation, like f.ex. the MIME Type (which can change after a rename) + maxWidth : INTEGER - maximum width of the item when retrieving the thumbnail from the server + maxHeight : INTEGER - maximum height of the item when retrieving the thumbnail from the server + mimeType : TEXT - MIME Type of imageData + imageData : BLOB - image data of the thumbnail + */ + @"CREATE TABLE thumb.thumbnails (tnID INTEGER PRIMARY KEY AUTOINCREMENT, fileID TEXT NOT NULL, eTag TEXT NOT NULL, specID TEXT NOT NULL, maxWidth INTEGER NOT NULL, maxHeight INTEGER NOT NULL, mimeType TEXT NOT NULL, imageData BLOB NOT NULL)", // relatedTo:OCDatabaseTableNameThumbnails + + // Create index over fileID + @"CREATE INDEX thumb.idx_thumbnails_fileID ON thumbnails (fileID)" // relatedTo:OCDatabaseTableNameThumbnails + ] + openStatements:nil + upgradeMigrator:^(OCSQLiteDB *db, OCSQLiteTableSchema *schema, void (^completionHandler)(NSError *error)) { + // Migrate to version 3 + [db executeTransaction:[OCSQLiteTransaction transactionWithBlock:^NSError *(OCSQLiteDB *db, OCSQLiteTransaction *transaction) { + INSTALL_TRANSACTION_ERROR_COLLECTION_RESULT_HANDLER + + // Remove all entries + [db executeQuery:[OCSQLiteQuery query:@"DELETE FROM thumb.thumbnails" resultHandler:resultHandler]]; + if (transactionError != nil) { return(transactionError); } + + return (transactionError); + } type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { + completionHandler(error); + }]]; + }] + ]; +} + +- (void)addOrUpdateResourceSchema +{ + /*** Resources ***/ + + // Version 1 + [self.sqlDB addTableSchema:[OCSQLiteTableSchema + schemaWithTableName:OCDatabaseTableNameResources + version:1 + creationQueries:@[ + /* + rowID : INTEGER - unique ID used to uniquely identify and efficiently update a row + type : TEXT - OCResourceType, type of resource, f.ex. thumbnail or avatar + identifier : TEXT - OCResourceIdentifier, identifier that identifies the resource, f.ex. the file ID or user name + version : TEXT - OCResourceVersion, string that can be used to distinguish versions (throug equality comparison), f.ex. ETags or checksums (optional) + structDesc : TEXT - 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) (optional) + maxWidth : INTEGER - maximum width of resource (optional) + maxHeight : INTEGER - maximum height of the resource (optional) + metaData : TEXT - resource type specific meta data describing resData (optional) + data : BLOB - resource data of the thumbnail + */ + @"CREATE TABLE thumb.resources (rowID INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, identifier TEXT NOT NULL, version TEXT, structDesc TEXT, maxWidth INTEGER, maxHeight INTEGER, metaData TEXT, data BLOB NOT NULL)", // relatedTo:OCDatabaseTableNameResources + + // Create index over identifier + @"CREATE INDEX thumb.idx_resources_identifier ON resources (identifier)" // relatedTo:OCDatabaseTableNameResources + ] + openStatements:nil + upgradeMigrator:nil + ]]; } - (void)addOrUpdateCountersSchema @@ -1417,6 +1833,7 @@ - (void)addOrUpdateCountersSchema OCDatabaseTableName OCDatabaseTableNameSyncJournal = @"syncJournal"; OCDatabaseTableName OCDatabaseTableNameUpdateJobs = @"updateJobs"; OCDatabaseTableName OCDatabaseTableNameThumbnails = @"thumb.thumbnails"; // Places that need to be changed as well if this is changed are annotated with relatedTo:OCDatabaseTableNameThumbnails +OCDatabaseTableName OCDatabaseTableNameResources = @"thumb.resources"; // Places that need to be changed as well if this is changed are annotated with relatedTo:OCDatabaseTableNameThumbnails or relatedTo:OCDatabaseTableNameResources OCDatabaseTableName OCDatabaseTableNameEvents = @"events"; OCDatabaseTableName OCDatabaseTableNameCounters = @"counters"; OCDatabaseTableName OCDatabaseTableNameItemPolicies = @"itemPolicies"; diff --git a/ownCloudSDK/Vaults/Database/OCDatabase+Versions.h b/ownCloudSDK/Vaults/Database/OCDatabase+Versions.h index eefc4e2d..8ed49823 100644 --- a/ownCloudSDK/Vaults/Database/OCDatabase+Versions.h +++ b/ownCloudSDK/Vaults/Database/OCDatabase+Versions.h @@ -6,12 +6,23 @@ // Copyright © 2021 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 typedef NS_ENUM(NSUInteger, OCDatabaseVersion) //!< Integer value indicating the version of the database schema used. Increased for every database update. { OCDatabaseVersionUnknown, OCDatabaseVersion_11_6, + OCDatabaseVersion_12_0, - OCDatabaseVersionLatest = OCDatabaseVersion_11_6 + OCDatabaseVersionLatest = OCDatabaseVersion_12_0 }; diff --git a/ownCloudSDK/Vaults/Database/OCDatabase.h b/ownCloudSDK/Vaults/Database/OCDatabase.h index eca785f1..7680a4da 100644 --- a/ownCloudSDK/Vaults/Database/OCDatabase.h +++ b/ownCloudSDK/Vaults/Database/OCDatabase.h @@ -38,6 +38,7 @@ @class OCEvent; @class OCCoreDirectoryUpdateJob; @class OCItemPolicy; +@class OCDrive; typedef void(^OCDatabaseCompletionHandler)(OCDatabase *db, NSError *error); typedef void(^OCDatabaseRetrieveCompletionHandler)(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items); @@ -64,8 +65,6 @@ typedef NSString* OCDatabaseCounterIdentifier; { NSURL *_databaseURL; - NSMutableArray *_tableSchemas; - NSMutableDictionary *_eventsByDatabaseID; NSString *_selectItemRowsSQLQueryPrefix; @@ -105,7 +104,10 @@ typedef NSString* OCDatabaseCounterIdentifier; - (void)addCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)syncAnchor completionHandler:(OCDatabaseCompletionHandler)completionHandler; - (void)updateCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)syncAnchor completionHandler:(OCDatabaseCompletionHandler)completionHandler; - (void)removeCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)syncAnchor completionHandler:(OCDatabaseCompletionHandler)completionHandler; +- (void)removeCacheItemsWithDriveID:(OCDriveID)driveID syncAnchor:(OCSyncAnchor)syncAnchor completionHandler:(OCDatabaseCompletionHandler)completionHandler; + - (void)purgeCacheItemsWithDatabaseIDs:(NSArray *)databaseIDs completionHandler:(OCDatabaseCompletionHandler)completionHandler; +- (void)purgeCacheItemsWithDriveID:(OCDriveID)driveID completionHandler:(OCDatabaseCompletionHandler)completionHandler; - (void)retrieveCacheItemForLocalID:(OCLocalID)localID completionHandler:(OCDatabaseRetrieveItemCompletionHandler)completionHandler; @@ -114,10 +116,10 @@ typedef NSString* OCDatabaseCounterIdentifier; - (void)retrieveCacheItemForFileIDUniquePrefix:(OCFileIDUniquePrefix)fileIDUniquePrefix includingRemoved:(BOOL)includingRemoved completionHandler:(OCDatabaseRetrieveItemCompletionHandler)completionHandler; -- (void)retrieveCacheItemsAtPath:(OCPath)path itemOnly:(BOOL)itemOnly completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler; -- (NSArray *)retrieveCacheItemsSyncAtPath:(OCPath)path itemOnly:(BOOL)itemOnly error:(NSError * __autoreleasing *)outError syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor; +- (void)retrieveCacheItemsAtLocation:(OCLocation *)location itemOnly:(BOOL)itemOnly completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler; +- (NSArray *)retrieveCacheItemsSyncAtLocation:(OCLocation *)location itemOnly:(BOOL)itemOnly error:(NSError * __autoreleasing *)outError syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor; -- (void)retrieveCacheItemsRecursivelyBelowPath:(OCPath)path includingPathItself:(BOOL)includingPathItself includingRemoved:(BOOL)includingRemoved completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler; +- (void)retrieveCacheItemsRecursivelyBelowLocation:(OCLocation *)location includingPathItself:(BOOL)includingPathItself includingRemoved:(BOOL)includingRemoved completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler; - (void)retrieveCacheItemsUpdatedSinceSyncAnchor:(OCSyncAnchor)synchAnchor foldersOnly:(BOOL)foldersOnly completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler; @@ -126,18 +128,9 @@ typedef NSString* OCDatabaseCounterIdentifier; - (void)iterateCacheItemsWithIterator:(OCDatabaseItemIterator)iterator; //!< Iterates through all cache items using the passed iterator block. The last invocation of the iterator will be with nil values for syncAnchor, item; NULL for stop. - (void)iterateCacheItemsForQueryCondition:(OCQueryCondition *)queryCondition excludeRemoved:(BOOL)excludeRemoved withIterator:(OCDatabaseItemIterator)iterator; //!< Iterates through matching cache items using the passed iterator block. The last invocation of the iterator will be with nil values for syncAnchor, item; NULL for stop. -#pragma mark - Thumbnail interface -- (void)retrieveThumbnailDataForItemVersion:(OCItemVersionIdentifier *)itemVersion specID:(NSString *)specID maximumSizeInPixels:(CGSize)maximumSizeInPixels completionHandler:(OCDatabaseRetrieveThumbnailCompletionHandler)completionHandler; -- (void)storeThumbnailData:(NSData *)thumbnailData withMIMEType:(NSString *)mimeType specID:(NSString *)specID forItemVersion:(OCItemVersionIdentifier *)itemVersion maximumSizeInPixels:(CGSize)maximumSizeInPixels completionHandler:(OCDatabaseCompletionHandler)completionHandler; - -#pragma mark - File interface -//- (void)addFiles:(NSArray *)files completionHandler:(OCDatabaseCompletionHandler)completionHandler; -//- (void)updateFiles:(NSArray *)files completionHandler:(OCDatabaseCompletionHandler)completionHandler; -//- (void)removeFiles:(NSArray *)files completionHandler:(OCDatabaseCompletionHandler)completionHandler; - #pragma mark - Update Scan interface - (void)addDirectoryUpdateJob:(OCCoreDirectoryUpdateJob *)updateScanPath completionHandler:(OCDatabaseDirectoryUpdateJobCompletionHandler)completionHandler; -- (void)retrieveDirectoryUpdateJobsAfter:(OCCoreDirectoryUpdateJobID)jobID forPath:(OCPath)path maximumJobs:(NSUInteger)maximumJobs completionHandler:(OCDatabaseRetrieveDirectoryUpdateJobsCompletionHandler)completionHandler; +- (void)retrieveDirectoryUpdateJobsAfter:(OCCoreDirectoryUpdateJobID)jobID forLocation:(OCLocation *)location maximumJobs:(NSUInteger)maximumJobs completionHandler:(OCDatabaseRetrieveDirectoryUpdateJobsCompletionHandler)completionHandler; - (void)removeDirectoryUpdateJobWithID:(OCCoreDirectoryUpdateJobID)jobID completionHandler:(OCDatabaseCompletionHandler)completionHandler; #pragma mark - Sync Lane interface diff --git a/ownCloudSDK/Vaults/Database/OCDatabase.m b/ownCloudSDK/Vaults/Database/OCDatabase.m index f0104b51..2230b3da 100644 --- a/ownCloudSDK/Vaults/Database/OCDatabase.m +++ b/ownCloudSDK/Vaults/Database/OCDatabase.m @@ -22,6 +22,7 @@ #import "OCSQLiteTransaction.h" #import "OCSQLiteQueryCondition.h" #import "OCItem.h" +#import "OCItem+OCTypeAlias.h" #import "OCItemVersionIdentifier.h" #import "OCSyncRecord.h" #import "NSString+OCPath.h" @@ -29,6 +30,7 @@ #import "OCMacros.h" #import "OCSyncAction.h" #import "OCSyncLane.h" +#import "OCDrive.h" #import "OCProcessManager.h" #import "OCQueryCondition+SQLBuilder.h" #import "OCAsyncSequentialQueue.h" @@ -139,6 +141,8 @@ - (void)openWithCompletionHandler:(OCDatabaseCompletionHandler)completionHandler { [self.sqlDB executeQueryString:@"PRAGMA journal_mode"]; + [self.sqlDB dropTableSchemas]; //!< Table schemas no longer needed, save memory + if (completionHandler!=nil) { completionHandler(self, error); @@ -292,10 +296,12 @@ - (void)addCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)syncA @"locallyModified" : @(item.locallyModified), @"localRelativePath" : OCSQLiteNullProtect(item.localRelativePath), @"downloadTrigger" : OCSQLiteNullProtect(item.downloadTriggerIdentifier), + @"locationString" : item.locationString, @"path" : item.path, @"parentPath" : [item.path parentPath], @"name" : [item.path lastPathComponent], @"mimeType" : OCSQLiteNullProtect(item.mimeType), + @"typeAlias" : OCSQLiteNullProtect(item.typeAlias), @"size" : @(item.size), @"favorite" : @(item.isFavorite.boolValue), @"cloudStatus" : @(item.cloudStatus), @@ -303,6 +309,7 @@ - (void)addCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)syncA @"syncActivity" : @(item.syncActivity), @"lastUsedDate" : OCSQLiteNullProtect(item.lastUsed), @"lastModifiedDate" : OCSQLiteNullProtect(item.lastModified), + @"driveID" : OCSQLiteNullProtect(item.driveID), @"fileID" : OCSQLiteNullProtect(item.fileID), @"localID" : OCSQLiteNullProtect(item.localID), @"ownerUserName" : OCSQLiteNullProtect(item.ownerUserName), @@ -363,10 +370,12 @@ - (void)updateCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)sy @"locallyModified" : @(item.locallyModified), @"localRelativePath" : OCSQLiteNullProtect(item.localRelativePath), @"downloadTrigger" : OCSQLiteNullProtect(item.downloadTriggerIdentifier), + @"locationString" : item.locationString, @"path" : item.path, @"parentPath" : [item.path parentPath], @"name" : [item.path lastPathComponent], @"mimeType" : OCSQLiteNullProtect(item.mimeType), + @"typeAlias" : OCSQLiteNullProtect(item.typeAlias), @"size" : @(item.size), @"favorite" : @(item.isFavorite.boolValue), @"cloudStatus" : @(item.cloudStatus), @@ -374,6 +383,7 @@ - (void)updateCacheItems:(NSArray *)items syncAnchor:(OCSyncAnchor)sy @"syncActivity" : @(item.syncActivity), @"lastUsedDate" : OCSQLiteNullProtect(item.lastUsed), @"lastModifiedDate" : OCSQLiteNullProtect(item.lastModified), + @"driveID" : OCSQLiteNullProtect(item.driveID), @"fileID" : OCSQLiteNullProtect(item.fileID), @"localID" : OCSQLiteNullProtect(item.localID), @"ownerUserName" : OCSQLiteNullProtect(item.ownerUserName), @@ -455,6 +465,58 @@ - (void)purgeCacheItemsWithDatabaseIDs:(NSArray *)databaseIDs com } } +- (void)removeCacheItemsWithDriveID:(OCDriveID)driveID syncAnchor:(OCSyncAnchor)syncAnchor completionHandler:(OCDatabaseCompletionHandler)completionHandler; +{ + if ((driveID == nil) || (OCTypedCast(driveID, NSString).length == 0)) + { + if (completionHandler != nil) + { + completionHandler(self, nil); + } + } + else + { + OCDatabaseTimestamp mdTimestamp = [self _timestampForSyncAnchor:syncAnchor]; + + OCSQLiteQuery *query = [OCSQLiteQuery queryUpdatingRowsWhere:@{ + @"driveID" : driveID, + } inTable:OCDatabaseTableNameMetaData withRowValues:@{ + @"removed" : @(YES), + @"syncAnchor" : syncAnchor, + @"mdTimestamp" : mdTimestamp + } completionHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error) { + if (completionHandler != nil) + { + completionHandler(self, error); + } + }]; + + [self.sqlDB executeQuery:query]; + } +} + +- (void)purgeCacheItemsWithDriveID:(OCDriveID)driveID completionHandler:(OCDatabaseCompletionHandler)completionHandler +{ + if ((driveID == nil) || (OCTypedCast(driveID, NSString).length == 0)) + { + if (completionHandler != nil) + { + completionHandler(self, nil); + } + } + else + { + OCSQLiteQuery *query = [OCSQLiteQuery queryDeletingRowsWhere:@{ @"driveID" : driveID } fromTable:OCDatabaseTableNameMetaData completionHandler:^(OCSQLiteDB * _Nonnull db, NSError * _Nullable error) { + if (completionHandler != nil) + { + completionHandler(self, error); + } + }]; + + [self.sqlDB executeQuery:query]; + } +} + - (OCItem *)_itemFromResultDict:(NSDictionary> *)resultDict { NSData *itemData; @@ -648,11 +710,11 @@ - (void)retrieveCacheItemForFileIDUniquePrefix:(OCFileIDUniquePrefix)fileIDUniqu } -- (void)retrieveCacheItemsRecursivelyBelowPath:(OCPath)path includingPathItself:(BOOL)includingPathItself includingRemoved:(BOOL)includingRemoved completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler +- (void)retrieveCacheItemsRecursivelyBelowLocation:(OCLocation *)location includingPathItself:(BOOL)includingPathItself includingRemoved:(BOOL)includingRemoved completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler { NSMutableArray *parameters = [NSMutableArray new]; - if (path.length == 0) + if (location.path.length == 0) { OCLogError(@"Retrieval below zero-length/nil path failed"); @@ -660,10 +722,13 @@ - (void)retrieveCacheItemsRecursivelyBelowPath:(OCPath)path includingPathItself: return; } - [parameters addObject:[[path stringBySQLLikeEscaping] stringByAppendingString:@"%"]]; + [parameters addObject:[[location.path stringBySQLLikeEscaping] stringByAppendingString:@"%"]]; NSString *sqlStatement = [_selectItemRowsSQLQueryPrefix stringByAppendingString:@", removed FROM metaData WHERE path LIKE ?"]; + sqlStatement = [sqlStatement stringByAppendingString:@" AND driveID=?"]; + [parameters addObject:OCSQLiteNullProtect(location.driveID)]; + if (includingRemoved) { sqlStatement = [sqlStatement stringByAppendingString:@" AND removed=0"]; @@ -672,18 +737,18 @@ - (void)retrieveCacheItemsRecursivelyBelowPath:(OCPath)path includingPathItself: if (!includingPathItself) { sqlStatement = [sqlStatement stringByAppendingString:@" AND path!=?"]; - [parameters addObject:path]; + [parameters addObject:location.path]; } [self _retrieveCacheItemsForSQLQuery:sqlStatement parameters:parameters cancelAction:nil completionHandler:completionHandler]; } -- (void)retrieveCacheItemsAtPath:(OCPath)path itemOnly:(BOOL)itemOnly completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler +- (void)retrieveCacheItemsAtLocation:(OCLocation *)location itemOnly:(BOOL)itemOnly completionHandler:(OCDatabaseRetrieveCompletionHandler)completionHandler { NSString *sqlQueryString = nil; NSArray *parameters = nil; - if (path == nil) + if (location.path == nil) { completionHandler(self, OCError(OCErrorInsufficientParameters), nil, nil); return; @@ -692,23 +757,33 @@ - (void)retrieveCacheItemsAtPath:(OCPath)path itemOnly:(BOOL)itemOnly completion if (itemOnly) { sqlQueryString = [_selectItemRowsSQLQueryPrefix stringByAppendingString:@" FROM metaData WHERE path=? AND removed=0"]; - parameters = @[path]; + parameters = @[location.path]; } else { sqlQueryString = [_selectItemRowsSQLQueryPrefix stringByAppendingString:@" FROM metaData WHERE (parentPath=? OR path=?) AND removed=0"]; - parameters = @[path, path]; + parameters = @[location.path, location.path]; + } + + if (location.driveID == nil) + { + sqlQueryString = [sqlQueryString stringByAppendingString:@" AND driveID IS NULL"]; + } + else + { + sqlQueryString = [sqlQueryString stringByAppendingString:@" AND driveID=?"]; + parameters = [parameters arrayByAddingObject:location.driveID]; } [self _retrieveCacheItemsForSQLQuery:sqlQueryString parameters:parameters cancelAction:nil completionHandler:completionHandler]; } -- (NSArray *)retrieveCacheItemsSyncAtPath:(OCPath)path itemOnly:(BOOL)itemOnly error:(NSError * __autoreleasing *)outError syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor +- (NSArray *)retrieveCacheItemsSyncAtLocation:(OCLocation *)location itemOnly:(BOOL)itemOnly error:(NSError * __autoreleasing *)outError syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor { __block NSArray *items = nil; OCSyncExec(cacheItemsRetrieval, { - [self retrieveCacheItemsAtPath:path itemOnly:itemOnly completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *dbItems) { + [self retrieveCacheItemsAtLocation:location itemOnly:itemOnly completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *dbItems) { items = dbItems; if (outError != NULL) { *outError = error; } @@ -746,12 +821,16 @@ - (void)retrieveCacheItemsUpdatedSinceSyncAnchor:(OCSyncAnchor)synchAnchor folde OCItemPropertyNameFileID : @"fileID", OCItemPropertyNameName : @"name", + OCItemPropertyNameDriveID : @"driveID", OCItemPropertyNamePath : @"path", + OCItemPropertyNameParentPath : @"parentPath", + OCItemPropertyNameLocationString : @"locationString", OCItemPropertyNameLocalRelativePath : @"localRelativePath", OCItemPropertyNameLocallyModified : @"locallyModified", OCItemPropertyNameMIMEType : @"mimeType", + OCItemPropertyNameTypeAlias : @"typeAlias", OCItemPropertyNameSize : @"size", OCItemPropertyNameIsFavorite : @"favorite", OCItemPropertyNameCloudStatus : @"cloudStatus", @@ -853,149 +932,14 @@ - (void)iterateCacheItemsForQueryCondition:(nullable OCQueryCondition *)queryCon }]]; } -#pragma mark - Thumbnail interface -- (void)storeThumbnailData:(NSData *)thumbnailData withMIMEType:(NSString *)mimeType specID:(NSString *)specID forItemVersion:(OCItemVersionIdentifier *)itemVersion maximumSizeInPixels:(CGSize)maximumSizeInPixels completionHandler:(OCDatabaseCompletionHandler)completionHandler -{ - if ((itemVersion.fileID == nil) || (itemVersion.eTag == nil)) - { - OCLogError(@"Error storing thumbnail for itemVersion %@ because it lacks fileID or eTag.", OCLogPrivate(itemVersion)); - return; - } - - if (specID == nil) - { - OCLogError(@"Error storing thumbnail for itemVersion %@ because it lacks a specID.", OCLogPrivate(itemVersion)); - return; - } - - if (thumbnailData == nil) - { - OCLogError(@"Error storing thumbnail data for itemVersion %@ because it lacks data.", OCLogPrivate(itemVersion)); - return; - } - - [self.sqlDB executeTransaction:[OCSQLiteTransaction transactionWithQueries:@[ - // Remove outdated versions and smaller thumbnail sizes - [OCSQLiteQuery query:@"DELETE FROM thumb.thumbnails WHERE fileID = :fileID AND ((eTag != :eTag) OR (maxWidth < :maxWidth AND maxHeight < :maxHeight) OR (specID != :specID))" // relatedTo:OCDatabaseTableNameThumbnails - withNamedParameters:@{ - @"fileID" : itemVersion.fileID, - @"eTag" : itemVersion.eTag, - @"specID" : specID, - @"maxWidth" : @(maximumSizeInPixels.width), - @"maxHeight" : @(maximumSizeInPixels.height), - } resultHandler:nil], - - // Insert new thumbnail - [OCSQLiteQuery queryInsertingIntoTable:OCDatabaseTableNameThumbnails - rowValues:@{ - @"fileID" : itemVersion.fileID, - @"eTag" : itemVersion.eTag, - @"specID" : specID, - @"maxWidth" : @(maximumSizeInPixels.width), - @"maxHeight" : @(maximumSizeInPixels.height), - @"mimeType" : mimeType, - @"imageData" : thumbnailData - } resultHandler:nil] - ] type:OCSQLiteTransactionTypeDeferred completionHandler:^(OCSQLiteDB *db, OCSQLiteTransaction *transaction, NSError *error) { - if (completionHandler != nil) - { - completionHandler(self, error); - } - }]]; -} - -- (void)retrieveThumbnailDataForItemVersion:(OCItemVersionIdentifier *)itemVersion specID:(NSString *)specID maximumSizeInPixels:(CGSize)maximumSizeInPixels completionHandler:(OCDatabaseRetrieveThumbnailCompletionHandler)completionHandler -{ - /* - // This is a bit more complex SQL statement. Here's how it was tested and what it is meant to achieve: - - // Table creation and test data set - CREATE TABLE thumb.thumbnails (tnID INTEGER PRIMARY KEY, maxWidth INTEGER NOT NULL, maxHeight INTEGER NOT NULL); - INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (10,10); - INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (15,15); - INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (20,20); - INSERT INTO thumbnails (maxWidth, maxHeight) VALUES (25,25); - - SELECT * FROM thumbnails ORDER BY (maxWidth = 8 AND maxHeight = 8) DESC, (maxWidth >= 8 AND maxHeight >= 8) DESC, (((maxWidth < 8 AND maxHeight < 8) * -1000 + 1) * ((maxWidth * maxHeight) - ( 8* 8))) ASC LIMIT 0,1; - // Returns (10,10) (smaller than smallest => next bigger) - - SELECT * FROM thumbnails ORDER BY (maxWidth = 14 AND maxHeight = 14) DESC, (maxWidth >= 14 AND maxHeight >= 14) DESC, (((maxWidth < 14 AND maxHeight < 14) * -1000 + 1) * ((maxWidth * maxHeight) - (14*14))) ASC LIMIT 0,1; - // Returns (15,15) (smaller than biggest, but smaller ones also there, no exact match => next bigger) - - SELECT * FROM thumbnails ORDER BY (maxWidth = 15 AND maxHeight = 15) DESC, (maxWidth >= 15 AND maxHeight >= 15) DESC, (((maxWidth < 15 AND maxHeight < 15) * -1000 + 1) * ((maxWidth * maxHeight) - (15*15))) ASC LIMIT 0,1; - // Returns (15,15) (=> exact match) - - SELECT * FROM thumbnails ORDER BY (maxWidth = 16 AND maxHeight = 16) DESC, (maxWidth >= 16 AND maxHeight >= 16) DESC, (((maxWidth < 16 AND maxHeight < 16) * -1000 + 1) * ((maxWidth * maxHeight) - (16*16))) ASC LIMIT 0,1; - // Returns (20,20) (smaller than biggest, but smaller ones also there, no exact match => next bigger) - - SELECT * FROM thumbnails ORDER BY (maxWidth = 30 AND maxHeight = 30) DESC, (maxWidth >= 30 AND maxHeight >= 30) DESC, (((maxWidth < 30 AND maxHeight < 30) * -1000 + 1) * ((maxWidth * maxHeight) - (30*30))) ASC LIMIT 0,1; - // Returns (25,25) (bigger than biggest => return biggest) - - Explaining the ORDER part, where the magic takes place: - - (maxWidth = 30 AND maxHeight = 30) DESC, // prefer exact match - (maxWidth >= 30 AND maxHeight >= 30) DESC, // if no exact match, prefer bigger ones - - (((maxWidth < 30 AND maxHeight < 30) * -1000 + 1) * // make sure those smaller than needed score the largest negative values and move to the end of the list - ((maxWidth * maxHeight) - (30*30))) ASC // the closer the size is to the one needed, the higher it should rank - - Wouldn't this filtering and sorting be easier in ObjC code going through the results? - - Yes, BUT by performing this in SQLite we save the overhead/memory of loading irrelevant data, and since the WHERE is very specific, the set that SQLite needs to sort - this way will be tiny and shouldn't have any measurable performance impact. - */ - - if ((itemVersion.fileID==nil) || (itemVersion.eTag==nil) || (specID == nil)) - { - if (completionHandler!=nil) - { - completionHandler(self, OCError(OCErrorInsufficientParameters), CGSizeZero, nil, nil); - } - return; - } - - [self.sqlDB executeQuery:[OCSQLiteQuery query:@"SELECT maxWidth, maxHeight, mimeType, imageData FROM thumbnails WHERE fileID = :fileID AND eTag = :eTag AND specID = :specID ORDER BY (maxWidth = :maxWidth AND maxHeight = :maxHeight) DESC, (maxWidth >= :maxWidth AND maxHeight >= :maxHeight) DESC, (((maxWidth < :maxWidth AND maxHeight < :maxHeight) * -1000 + 1) * ((maxWidth * maxHeight) - (:maxWidth * :maxHeight))) ASC LIMIT 0,1" withNamedParameters:@{ - @"fileID" : itemVersion.fileID, - @"eTag" : itemVersion.eTag, - @"specID" : specID, - @"maxWidth" : @(maximumSizeInPixels.width), - @"maxHeight" : @(maximumSizeInPixels.height), - } resultHandler:^(OCSQLiteDB *db, NSError *error, OCSQLiteTransaction *transaction, OCSQLiteResultSet *resultSet) { - NSError *returnError = error; - __block BOOL calledCompletionHandler = NO; - - if (returnError == nil) - { - [resultSet iterateUsing:^(OCSQLiteResultSet *resultSet, NSUInteger line, NSDictionary *rowDictionary, BOOL *stop) { - NSNumber *maxWidthNumber = nil, *maxHeightNumber = nil; - NSString *mimeType = nil; - NSData *imageData = nil; - - if (((maxWidthNumber = rowDictionary[@"maxWidth"])!=nil) && - ((maxHeightNumber = rowDictionary[@"maxHeight"])!=nil) && - ((mimeType = rowDictionary[@"mimeType"])!=nil) && - ((imageData = rowDictionary[@"imageData"])!=nil)) - { - completionHandler(self, nil, CGSizeMake((CGFloat)maxWidthNumber.integerValue, (CGFloat)maxHeightNumber.integerValue), mimeType, imageData); - calledCompletionHandler = YES; - } - } error:&returnError]; - } - - if (!calledCompletionHandler) - { - completionHandler(self, returnError, CGSizeZero, nil, nil); - } - }]]; -} - #pragma mark - Directory Update Job interface - (void)addDirectoryUpdateJob:(OCCoreDirectoryUpdateJob *)updateJob completionHandler:(OCDatabaseDirectoryUpdateJobCompletionHandler)completionHandler { - if ((updateJob != nil) && (updateJob.path != nil)) + if ((updateJob != nil) && (updateJob.location.path != nil)) { [self.sqlDB executeQuery:[OCSQLiteQuery queryInsertingIntoTable:OCDatabaseTableNameUpdateJobs rowValues:@{ - @"path" : updateJob.path + @"driveID" : OCSQLiteNullProtect(updateJob.location.driveID), + @"path" : updateJob.location.path } resultHandler:^(OCSQLiteDB *db, NSError *error, NSNumber *rowID) { updateJob.identifier = rowID; @@ -1007,16 +951,17 @@ - (void)addDirectoryUpdateJob:(OCCoreDirectoryUpdateJob *)updateJob completionHa } else { - OCLogError(@"updateScanPath=%@, updateScanPath.path=%@ => could not be stored in database", updateJob, updateJob.path); + OCLogError(@"updateJob=%@, updateJob.location=%@ => could not be stored in database", updateJob, updateJob.location); completionHandler(self, OCError(OCErrorInsufficientParameters), nil); } } -- (void)retrieveDirectoryUpdateJobsAfter:(OCCoreDirectoryUpdateJobID)jobID forPath:(OCPath)path maximumJobs:(NSUInteger)maximumJobs completionHandler:(OCDatabaseRetrieveDirectoryUpdateJobsCompletionHandler)completionHandler +- (void)retrieveDirectoryUpdateJobsAfter:(OCCoreDirectoryUpdateJobID)jobID forLocation:(OCLocation *)location maximumJobs:(NSUInteger)maximumJobs completionHandler:(OCDatabaseRetrieveDirectoryUpdateJobsCompletionHandler)completionHandler { [self.sqlDB executeQuery:[OCSQLiteQuery querySelectingColumns:nil fromTable:OCDatabaseTableNameUpdateJobs where:@{ @"jobID" : [OCSQLiteQueryCondition queryConditionWithOperator:@">=" value:jobID apply:(jobID!=nil)], - @"path" : [OCSQLiteQueryCondition queryConditionWithOperator:@"=" value:path apply:(path!=nil)] + @"driveID" : [OCSQLiteQueryCondition queryConditionWithOperator:@"=" value:location.driveID apply:(location.driveID!=nil)], + @"path" : [OCSQLiteQueryCondition queryConditionWithOperator:@"=" value:location.path apply:(location.path!=nil)] } orderBy:@"jobID ASC" limit:((maximumJobs == 0) ? nil : [NSString stringWithFormat:@"0,%ld",maximumJobs]) resultHandler:^(OCSQLiteDB *db, NSError *error, OCSQLiteTransaction *transaction, OCSQLiteResultSet *resultSet) { __block NSMutableArray *updateJobs = nil; NSError *iterationError = error; @@ -1031,7 +976,7 @@ - (void)retrieveDirectoryUpdateJobsAfter:(OCCoreDirectoryUpdateJobID)jobID forPa if ((updateJob = [OCCoreDirectoryUpdateJob new]) != nil) { updateJob.identifier = (OCCoreDirectoryUpdateJobID)rowDictionary[@"jobID"]; - updateJob.path = (OCPath)rowDictionary[@"path"]; + updateJob.location = [[OCLocation alloc] initWithDriveID:(OCDriveID)rowDictionary[@"driveID"] path:(OCPath)rowDictionary[@"path"]]; if (updateJobs == nil) { updateJobs = [NSMutableArray new]; } @@ -1305,27 +1250,29 @@ - (void)addSyncRecords:(NSArray *)syncRecords completionHandler @synchronized(db) { - if (syncRecord.recordID != nil) + OCSyncRecordID recordID = syncRecord.recordID; + + if (recordID != nil) { if (self->_syncRecordsByID != nil) { // Add to cache - self->_syncRecordsByID[syncRecord.recordID] = syncRecord; + self->_syncRecordsByID[recordID] = syncRecord; } if (syncRecord.progress != nil) { - self->_progressBySyncRecordID[syncRecord.recordID] = syncRecord.progress.progress; + self->_progressBySyncRecordID[recordID] = syncRecord.progress.progress; } if (syncRecord.resultHandler != nil) { - self->_resultHandlersBySyncRecordID[syncRecord.recordID] = syncRecord.resultHandler; + self->_resultHandlersBySyncRecordID[recordID] = syncRecord.resultHandler; } if (syncRecord.action.ephermalParameters != nil) { - self->_ephermalParametersBySyncRecordID[syncRecord.recordID] = syncRecord.action.ephermalParameters; + self->_ephermalParametersBySyncRecordID[recordID] = syncRecord.action.ephermalParameters; } } } @@ -1347,7 +1294,7 @@ - (void)updateSyncRecords:(NSArray *)syncRecords completionHand for (OCSyncRecord *syncRecord in syncRecords) { - if (syncRecord.recordID != nil) + if ((syncRecord.recordID != nil) && !syncRecord.removed) { // Increment revision of record syncRecord.revision = @(syncRecord.revision.longLongValue + 1); @@ -1410,17 +1357,23 @@ - (void)removeSyncRecords:(NSArray *)syncRecords completionHand for (OCSyncRecord *syncRecord in syncRecords) { + if (syncRecord.removed) + { + OCLogError(@"Sync record with recordID=%@ already deleted: %@", syncRecord.recordID, syncRecord); + continue; + } + if (syncRecord.recordID != nil) { [queries addObject:[OCSQLiteQuery queryDeletingRowWithID:syncRecord.recordID fromTable:OCDatabaseTableNameSyncJournal completionHandler:^(OCSQLiteDB *db, NSError *error) { OCSyncRecordID syncRecordID; - if ((syncRecordID = syncRecord.recordID) != nil) + if (((syncRecordID = syncRecord.recordID) != nil) && !syncRecord.removed) { - syncRecord.recordID = nil; - @synchronized(db) { + syncRecord.removed = YES; + [self->_progressBySyncRecordID removeObjectForKey:syncRecordID]; [self->_resultHandlersBySyncRecordID removeObjectForKey:syncRecordID]; [self->_ephermalParametersBySyncRecordID removeObjectForKey:syncRecordID]; @@ -1461,7 +1414,7 @@ - (void)numberOfSyncRecordsOnSyncLaneID:(OCSyncLaneID)laneID completionHandler:( }]]; } -- (OCSyncRecord *)_syncRecordFromRowDictionary:(NSDictionary> *)rowDictionary +- (OCSyncRecord *)_syncRecordFromRowDictionary:(NSDictionary> *)rowDictionary cache:(BOOL)cache { OCSyncRecord *syncRecord = nil; OCSyncRecordID recordID; @@ -1477,8 +1430,19 @@ - (OCSyncRecord *)_syncRecordFromRowDictionary:(NSDictionary> *rowDictionary, BOOL *stop) { - syncRecord = [self _syncRecordFromRowDictionary:rowDictionary]; + syncRecord = [self _syncRecordFromRowDictionary:rowDictionary cache:NO]; *stop = YES; } error:&iterationError]; } @@ -1587,7 +1554,7 @@ - (void)retrieveSyncRecordsForPath:(OCPath)path action:(OCSyncActionIdentifier)a [resultSet iterateUsing:^(OCSQLiteResultSet *resultSet, NSUInteger line, NSDictionary> *rowDictionary, BOOL *stop) { OCSyncRecord *syncRecord; - if ((syncRecord = [self _syncRecordFromRowDictionary:rowDictionary]) != nil) + if ((syncRecord = [self _syncRecordFromRowDictionary:rowDictionary cache:NO]) != nil) { [syncRecords addObject:syncRecord]; } @@ -1610,7 +1577,7 @@ - (void)retrieveSyncRecordAfterID:(OCSyncRecordID)recordID onLaneID:(OCSyncLaneI NSError *iterationError = error; [resultSet iterateUsing:^(OCSQLiteResultSet *resultSet, NSUInteger line, NSDictionary> *rowDictionary, BOOL *stop) { - syncRecord = [self _syncRecordFromRowDictionary:rowDictionary]; + syncRecord = [self _syncRecordFromRowDictionary:rowDictionary cache:YES]; *stop = YES; } error:&iterationError]; @@ -1856,7 +1823,7 @@ - (void)addItemPolicy:(OCItemPolicy *)itemPolicy completionHandler:(OCDatabaseCo { [self.sqlDB executeQuery:[OCSQLiteQuery queryInsertingIntoTable:OCDatabaseTableNameItemPolicies rowValues:@{ @"identifier" : OCSQLiteNullProtect(itemPolicy.identifier), - @"path" : OCSQLiteNullProtect(itemPolicy.path), + @"path" : OCSQLiteNullProtect(itemPolicy.location.path), @"localID" : OCSQLiteNullProtect(itemPolicy.localID), @"kind" : itemPolicy.kind, @"policyData" : itemPolicyData, @@ -1881,7 +1848,7 @@ - (void)updateItemPolicy:(OCItemPolicy *)itemPolicy completionHandler:(OCDatabas { [self.sqlDB executeQuery:[OCSQLiteQuery queryUpdatingRowWithID:itemPolicy.databaseID inTable:OCDatabaseTableNameItemPolicies withRowValues:@{ @"identifier" : OCSQLiteNullProtect(itemPolicy.identifier), - @"path" : OCSQLiteNullProtect(itemPolicy.path), + @"path" : OCSQLiteNullProtect(itemPolicy.location.path), @"localID" : OCSQLiteNullProtect(itemPolicy.localID), @"kind" : itemPolicy.kind, @"policyData" : itemPolicyData, diff --git a/ownCloudSDK/Vaults/Database/SQLite/Collations/OCSQLiteCollationLocalized.h b/ownCloudSDK/Vaults/Database/SQLite/Collations/OCSQLiteCollationLocalized.h index 07e1cd80..3ffbfb01 100644 --- a/ownCloudSDK/Vaults/Database/SQLite/Collations/OCSQLiteCollationLocalized.h +++ b/ownCloudSDK/Vaults/Database/SQLite/Collations/OCSQLiteCollationLocalized.h @@ -16,7 +16,7 @@ * */ -#import +#import "OCSQLiteCollation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.h b/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.h index e07c08d1..b8544e45 100644 --- a/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.h +++ b/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.h @@ -123,6 +123,7 @@ typedef void(^OCSQLiteDBBusyStatusHandler)(NSProgress * _Nullable progress); //! #pragma mark - Table Schemas - (void)addTableSchema:(OCSQLiteTableSchema *)schema; //!< Adds a table schema to the database. All schemas must be added prior to calling -applyTableSchemasWithCompletionHandler: the database. - (void)applyTableSchemasWithCompletionHandler:(nullable OCSQLiteDBCompletionHandler)completionHandler; //!< Applies the table schemas: creates tables that don't yet exist, applies all available upgrades for existing tables +- (void)dropTableSchemas; //!< Drops all table schemas. Useful to save memory after migrations and migration checks have run. #pragma mark - Execute - (void)executeQuery:(OCSQLiteQuery *)query; //!< Executes a query. Usually async, but synchronous if called from with in a OCSQLiteTransactionBlock. diff --git a/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.m b/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.m index 3d106b6e..2d428464 100644 --- a/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.m +++ b/ownCloudSDK/Vaults/Database/SQLite/OCSQLiteDB.m @@ -531,6 +531,12 @@ - (void)applyTableSchemasWithCompletionHandler:(OCSQLiteDBCompletionHandler)comp }]]; } +- (void)dropTableSchemas +{ + [self queueBlock:^{ + [self->_tableSchemas removeAllObjects]; + }]; +} #pragma mark - Queries (public) - (void)executeQuery:(OCSQLiteQuery *)query @@ -1032,7 +1038,10 @@ - (OCSQLiteStatement *)_cachedStatementForSQLQuery:(OCSQLiteQueryString)sqlQuery statement = [OCSQLiteStatement statementFromQuery:sqlQuery database:self error:error]; // Insert statement at the top of the array - [_cachedStatements insertObject:statement atIndex:0]; + if (statement != nil) + { + [_cachedStatements insertObject:statement atIndex:0]; + } // if (cutOffIdx != NSNotFound) // { diff --git a/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.h b/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.h index cc0e620c..cc8ede1f 100644 --- a/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.h +++ b/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - INSERT query builder + (nullable instancetype)queryInsertingIntoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(nullable OCSQLiteDBInsertionHandler)resultHandler; ++ (nullable instancetype)queryInsertingOrReplacingIntoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(OCSQLiteDBInsertionHandler)resultHandler; #pragma mark - UPDATE query builder + (nullable instancetype)queryUpdatingRowsWhere:(NSDictionary > *)matchValues inTable:(NSString *)tableName withRowValues:(NSDictionary > *)rowValues completionHandler:(nullable OCSQLiteDBCompletionHandler)completionHandler; diff --git a/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.m b/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.m index 0c139595..fc33c878 100644 --- a/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.m +++ b/ownCloudSDK/Vaults/Database/SQLite/Queries/OCSQLiteQuery.m @@ -93,7 +93,7 @@ + (instancetype)querySelectingColumns:(NSArray *)columnNames fromTab } #pragma mark - INSERT query builder -+ (instancetype)queryInsertingIntoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(OCSQLiteDBInsertionHandler)resultHandler ++ (instancetype)_queryWith:(NSString *)statement intoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(OCSQLiteDBInsertionHandler)resultHandler { OCSQLiteQuery *query = nil; NSUInteger rowValuesCount; @@ -128,7 +128,7 @@ + (instancetype)queryInsertingIntoTable:(NSString *)tableName rowValues:(NSDicti i++; }]; - sqlQuery = [NSString stringWithFormat:@"INSERT INTO %@ (%@) VALUES (%@)", tableName, [columnNames componentsJoinedByString:@","], placeholdersString]; + sqlQuery = [NSString stringWithFormat:@"%@ INTO %@ (%@) VALUES (%@)", statement, tableName, [columnNames componentsJoinedByString:@","], placeholdersString]; query = [self new]; query.sqlQuery = sqlQuery; @@ -141,6 +141,16 @@ + (instancetype)queryInsertingIntoTable:(NSString *)tableName rowValues:(NSDicti return (query); } ++ (instancetype)queryInsertingIntoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(OCSQLiteDBInsertionHandler)resultHandler +{ + return ([self _queryWith:@"INSERT" intoTable:tableName rowValues:rowValues resultHandler:resultHandler]); +} + ++ (instancetype)queryInsertingOrReplacingIntoTable:(NSString *)tableName rowValues:(NSDictionary > *)rowValues resultHandler:(OCSQLiteDBInsertionHandler)resultHandler +{ + return ([self _queryWith:@"INSERT OR REPLACE" intoTable:tableName rowValues:rowValues resultHandler:resultHandler]); +} + #pragma mark - UPDATE query builder + (instancetype)queryUpdatingRowsWhere:(NSDictionary > *)matchValues inTable:(NSString *)tableName withRowValues:(NSDictionary > *)rowValues completionHandler:(OCSQLiteDBCompletionHandler)completionHandler { @@ -247,6 +257,8 @@ + (NSString *)_buildWhereStringForMatchPairs:(NSDictionary _Nonnull obj, BOOL * _Nonnull stop) { NSString *sqlOperator = @"="; + NSString *placeholderString = @"?"; + BOOL addObject = YES; if ([obj isKindOfClass:[OCSQLiteQueryCondition class]]) { @@ -261,15 +273,37 @@ + (NSString *)_buildWhereStringForMatchPairs:(NSDictionary 0) { - [whereString appendFormat:@" AND %@%@?", columnName, sqlOperator]; + [whereString appendFormat:@" AND %@%@%@", columnName, sqlOperator, placeholderString]; } else { - [whereString appendFormat:@" WHERE %@%@?", columnName, sqlOperator]; + [whereString appendFormat:@" WHERE %@%@%@", columnName, sqlOperator, placeholderString]; } addedConditions++; diff --git a/ownCloudSDK/Vaults/OCVault+Internal.h b/ownCloudSDK/Vaults/OCVault+Internal.h index 71cdb97d..dc1f8e8a 100644 --- a/ownCloudSDK/Vaults/OCVault+Internal.h +++ b/ownCloudSDK/Vaults/OCVault+Internal.h @@ -27,12 +27,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)compactInContext:(nullable void(^)(void(^blockToRunInContext)(OCSyncAnchor syncAnchor, void(^updateHandler)(NSSet *updateDirectoryLocalIDs))))runInContext withSelector:(OCVaultCompactSelector)selector completionHandler:(nullable OCCompletionHandler)completionHandler; //!< Compacts the vault's contents using the selector to determine which items' files to delete. If the vault is used by an online core, make sure to pass a block for runInContext that runs the passed block inside -[OCCore performProtectedSyncBlock:..] to guarantee integrity. #pragma mark - File Provider +- (void)signalDriveChangesWithAdditions:(NSArray *)addedDrives updates:(NSArray *)updates removals:(NSArray *)removedDrives; - (void)signalChangesForItems:(NSArray *)changedItems; -- (void)signalChangesInDirectoriesWithLocalIDs:(NSSet *)changedDirectoriesLocalIDs; + +- (void)signalChangesInDirectoriesWithVFSItemIDs:(NSSet *)changedDirectoriesVFSItemIDs; #if OC_FEATURE_AVAILABLE_FILEPROVIDER -- (void)signalEnumeratorForContainerItemIdentifier:(NSFileProviderItemIdentifier)changedDirectoryLocalID; +- (void)signalEnumeratorForContainerItemIdentifier:(OCVFSItemID)changedDirectoryLocalID; #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ @end +extern NSNotificationName OCVaultDetachedDrivesListChanged; //!< Notification sent when an OCVault's .detachedDrives list has changed. Sent from the thread that modified the list in KVO. The object is the OCVault. +extern NSNotificationName OCVaultSubscribedDrivesListChanged; //!< Notification sent when an OCVault's .subscribedDrivesList has changed. Sent from the thread that modified the list in KVO. The object is the OCVault. + NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Vaults/OCVault+Internal.m b/ownCloudSDK/Vaults/OCVault+Internal.m index 4a897536..c539d562 100644 --- a/ownCloudSDK/Vaults/OCVault+Internal.m +++ b/ownCloudSDK/Vaults/OCVault+Internal.m @@ -59,7 +59,7 @@ - (void)compactInContext:(nullable void(^)(void(^blockToRunInContext)(OCSyncAnch [OCIPNotificationCenter.sharedNotificationCenter postNotificationForName:self.bookmark.coreUpdateNotificationName ignoreSelf:NO]; // Post File Provider change notification - [self signalChangesInDirectoriesWithLocalIDs:updateDirectoryLocalIDs]; + [self signalChangesInDirectoriesWithVFSItemIDs:updateDirectoryLocalIDs]; }); return (nil); @@ -259,83 +259,102 @@ - (BOOL)fileManager:(NSFileManager *)fileManager shouldRemoveItemAtURL:(NSURL *) } #pragma mark - File Provider -- (void)signalChangesForItems:(NSArray *)changedItems +- (nullable NSArray *)translatedParentIDsForItem:(OCItem *)item suggestedRootFolderID:(nullable OCLocalID)suggestedRootFolderID { - NSMutableSet *changedDirectoriesLocalIDs = [NSMutableSet new]; - OCLocalID rootDirectoryLocalID = nil; - BOOL addRoot = NO; + // Translation without VFS + NSArray* translatedIDs = nil; - // Coalesce IDs - for (OCItem *item in changedItems) + switch (item.type) { - switch (item.type) - { - case OCItemTypeFile: + case OCItemTypeFile: + if (item.path.parentPath.isRootPath) + { + #if OC_FEATURE_AVAILABLE_FILEPROVIDER + translatedIDs = @[ ((suggestedRootFolderID != nil) ? suggestedRootFolderID : item.parentLocalID) ]; + #endif + } + else if (item.parentLocalID != nil) + { + translatedIDs = @[ item.parentLocalID ]; + } + break; + + case OCItemTypeCollection: + if (item.path.isRootPath) + { + #if OC_FEATURE_AVAILABLE_FILEPROVIDER + translatedIDs = @[ ((suggestedRootFolderID != nil) ? suggestedRootFolderID : item.localID) ]; + #endif + } + else + { if (item.parentLocalID != nil) { - [changedDirectoriesLocalIDs addObject:item.parentLocalID]; - } - else if ([item.path.parentPath isEqual:@"/"]) - { - addRoot = YES; - } - break; + OCLocalID parentLocalID = item.path.parentPath.isRootPath ? ((suggestedRootFolderID != nil) ? suggestedRootFolderID : item.parentLocalID) : item.parentLocalID; - case OCItemTypeCollection: - if ([item.path isEqual:@"/"]) - { - rootDirectoryLocalID = item.localID; - addRoot = YES; - } - else - { - if (item.parentLocalID != nil) + if (item.localID != nil) { - [changedDirectoriesLocalIDs addObject:item.parentLocalID]; + translatedIDs = @[ parentLocalID, item.localID ]; } - -// if ((item.localID != nil) && !item.removed) - if (item.localID != nil) + else { - [changedDirectoriesLocalIDs addObject:item.localID]; + translatedIDs = @[ parentLocalID ]; } } + } + break; + } - break; - } + return (translatedIDs); +} - if ((rootDirectoryLocalID==nil) && item.path.parentPath.isRootPath && (item.parentLocalID!=nil)) - { - rootDirectoryLocalID = item.parentLocalID; - } +- (void)signalDriveChangesWithAdditions:(NSArray *)addedDrives updates:(NSArray *)updates removals:(NSArray *)removedDrives +{ + NSSet *changedDirectoriesVFSItemIDs = nil; + + if ([self conformsToProtocol:@protocol(OCVaultVFSTranslation)]) + { + changedDirectoriesVFSItemIDs = [(id)self vfsRefreshIDsForDriveChangesWithAdditions:addedDrives updates:updates removals:removedDrives]; } + else + { + changedDirectoriesVFSItemIDs = [NSSet setWithObject:OCVFSItemIDRoot]; + } + + // Signal NSFileProviderManager + [self signalChangesInDirectoriesWithVFSItemIDs:changedDirectoriesVFSItemIDs]; +} - // Remove root directory localID - if (rootDirectoryLocalID != nil) +- (void)signalChangesForItems:(NSArray *)changedItems +{ + NSMutableSet *changedDirectoriesVFSItemIDs = [NSMutableSet new]; + + // Check for translation availability + if (![self conformsToProtocol:@protocol(OCVaultVFSTranslation)]) { - if ([changedDirectoriesLocalIDs containsObject:rootDirectoryLocalID]) - { - [changedDirectoriesLocalIDs removeObject:rootDirectoryLocalID]; - addRoot = YES; - } + OCLogError(@"Signalling changes for %@ failed: OCVault lacks OCVaultVFSTranslation conformance", changedItems); + return; } - // Add root directory localID - if (addRoot) + // Coalesce IDs +// OCLocalID rootDirectoryLocalID = nil; +// BOOL addRoot = NO; + + for (OCItem *item in changedItems) { - #if OC_FEATURE_AVAILABLE_FILEPROVIDER - if (OCVault.hostHasFileProvider) + NSArray *translatedItemIDs; + + if ((translatedItemIDs = [(id)self vfsRefreshParentIDsForItem:item]) != nil) { - [changedDirectoriesLocalIDs addObject:NSFileProviderRootContainerItemIdentifier]; + [changedDirectoriesVFSItemIDs addObjectsFromArray:translatedItemIDs]; } - #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ } // Signal NSFileProviderManager - [self signalChangesInDirectoriesWithLocalIDs:changedDirectoriesLocalIDs]; + [self signalChangesInDirectoriesWithVFSItemIDs:changedDirectoriesVFSItemIDs]; } -- (void)signalChangesInDirectoriesWithLocalIDs:(NSSet *)changedDirectoriesLocalIDs +- (void)signalChangesInDirectoriesWithVFSItemIDs:(NSSet *)changedDirectoriesVFSIDs { #if OC_FEATURE_AVAILABLE_FILEPROVIDER if (OCVault.hostHasFileProvider) @@ -343,11 +362,11 @@ - (void)signalChangesInDirectoriesWithLocalIDs:(NSSet *)changedDirec dispatch_async(dispatch_get_main_queue(), ^{ NSFileProviderManager *fileProviderManager = [self fileProviderManager]; - for (OCLocalID changedDirectoryLocalID in changedDirectoriesLocalIDs) + for (OCLocalID changedDirectoryVFSID in changedDirectoriesVFSIDs) { - OCLogDebug(@"Signaling changes to file provider manager %@ for item localID=%@", fileProviderManager, OCLogPrivate(changedDirectoryLocalID)); + OCLogDebug(@"Signaling changes to file provider manager %@ for container with vfsItemID=%@", fileProviderManager, OCLogPrivate(changedDirectoryVFSID)); - [self signalEnumeratorForContainerItemIdentifier:changedDirectoryLocalID]; + [self signalEnumeratorForContainerItemIdentifier:changedDirectoryVFSID]; } }); } @@ -355,37 +374,42 @@ - (void)signalChangesInDirectoriesWithLocalIDs:(NSSet *)changedDirec } #if OC_FEATURE_AVAILABLE_FILEPROVIDER -- (void)signalEnumeratorForContainerItemIdentifier:(NSFileProviderItemIdentifier)changedDirectoryLocalID +- (void)signalEnumeratorForContainerItemIdentifier:(OCVFSItemID)changedDirectoryVFSItemID { if (!OCVault.hostHasFileProvider) { return; } + if ([changedDirectoryVFSItemID isEqual:OCVFSItemIDRoot]) + { + changedDirectoryVFSItemID = NSFileProviderRootContainerItemIdentifier; + } + @synchronized(_fileProviderSignalCountByContainerItemIdentifiersLock) { - NSNumber *currentSignalCount = _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID]; + NSNumber *currentSignalCount = _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID]; if (currentSignalCount == nil) { // The only/first signal for this right now => schedule right away - _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID] = @(1); + _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID] = @(1); - [self _scheduleSignalForContainerItemIdentifier:changedDirectoryLocalID]; + [self _scheduleSignalForContainerItemIdentifier:changedDirectoryVFSItemID]; } else { // Another signal hasn't completed yet, so increase the counter and wait for the scheduled signal to complete // (at which point, another signal will be triggered) - OCLogDebug(@"Skipped signaling %@ for changes as another signal hasn't completed yet", changedDirectoryLocalID); + OCLogDebug(@"Skipped signaling %@ for changes as another signal hasn't completed yet", changedDirectoryVFSItemID); - _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID] = @(_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID].integerValue + 1); + _fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID] = @(_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID].integerValue + 1); } } } -- (void)_scheduleSignalForContainerItemIdentifier:(NSFileProviderItemIdentifier)changedDirectoryLocalID +- (void)_scheduleSignalForContainerItemIdentifier:(OCVFSItemID)changedDirectoryVFSItemID { NSTimeInterval minimumSignalInterval = 0.2; // effectively throttle FP container update notifications to at most once per [minimumSignalInterval] @@ -401,30 +425,30 @@ - (void)_scheduleSignalForContainerItemIdentifier:(NSFileProviderItemIdentifier) { @synchronized(self->_fileProviderSignalCountByContainerItemIdentifiersLock) { - NSInteger signalCountAtStart = self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID].integerValue; + NSInteger signalCountAtStart = self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID].integerValue; - OCLogDebug(@"Signaling %@ for changes..", changedDirectoryLocalID); + OCLogDebug(@"Signaling %@ for changes..", changedDirectoryVFSItemID); - [fileProviderManager signalEnumeratorForContainerItemIdentifier:changedDirectoryLocalID completionHandler:^(NSError * _Nullable error) { - OCLogDebug(@"Signaling %@ for changes ended with error %@", changedDirectoryLocalID, error); + [fileProviderManager signalEnumeratorForContainerItemIdentifier:changedDirectoryVFSItemID completionHandler:^(NSError * _Nullable error) { + OCLogDebug(@"Signaling %@ for changes ended with error %@", changedDirectoryVFSItemID, error); dispatch_async(dispatch_get_main_queue(), ^{ @synchronized(self->_fileProviderSignalCountByContainerItemIdentifiersLock) { - NSInteger signalCountAtEnd = self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID].integerValue; + NSInteger signalCountAtEnd = self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID].integerValue; NSInteger remainingSignalCount = signalCountAtEnd - signalCountAtStart; if (remainingSignalCount > 0) { // There were signals after initiating the last signal => schedule another signal - self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryLocalID] = @(remainingSignalCount); + self->_fileProviderSignalCountByContainerItemIdentifiers[changedDirectoryVFSItemID] = @(remainingSignalCount); - [self _scheduleSignalForContainerItemIdentifier:changedDirectoryLocalID]; + [self _scheduleSignalForContainerItemIdentifier:changedDirectoryVFSItemID]; } else { // The last signal was sent after the last signal was requested => remove from dict - [self->_fileProviderSignalCountByContainerItemIdentifiers removeObjectForKey:changedDirectoryLocalID]; + [self->_fileProviderSignalCountByContainerItemIdentifiers removeObjectForKey:changedDirectoryVFSItemID]; } } }); @@ -433,10 +457,13 @@ - (void)_scheduleSignalForContainerItemIdentifier:(NSFileProviderItemIdentifier) } else { - OCLogDebug(@"Signaling %@ for changes failed because the file provider manager couldn't be found.", changedDirectoryLocalID); + OCLogDebug(@"Signaling %@ for changes failed because the file provider manager couldn't be found.", changedDirectoryVFSItemID); } }); } #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ @end + +NSNotificationName OCVaultDetachedDrivesListChanged = @"OCVaultDetachedDrivesListChanged"; +NSNotificationName OCVaultSubscribedDrivesListChanged = @"OCVaultSubscribedDrivesListChanged"; diff --git a/ownCloudSDK/Vaults/OCVault.h b/ownCloudSDK/Vaults/OCVault.h index cba06c97..c659cefe 100644 --- a/ownCloudSDK/Vaults/OCVault.h +++ b/ownCloudSDK/Vaults/OCVault.h @@ -20,6 +20,10 @@ #import "OCFeatureAvailability.h" #import "OCBookmark.h" #import "OCKeyValueStore.h" +#import "OCLocation.h" +#import "OCVaultLocation.h" +#import "OCDrive.h" +#import "OCVFSCore.h" #if OC_FEATURE_AVAILABLE_FILEPROVIDER #import @@ -27,6 +31,62 @@ @class OCDatabase; @class OCItem; +@class OCResourceManager; + +/* + # Filesystem layout # API + + [App Group Container]/ - OCAppIdentity.appGroupContainerURL + "Vaults"/ - OCVaultPathVaults + [Bookmark UUID]/ - OCVault.rootURL + [Bookmark UUID].db - OCVault.databaseURL + [Bookmark UUID].tdb + (thumbnail part of .database) + + [Bookmark UUID].ockvs - OCVault.keyValueStoreURL + + "Erasure"/ - OCVault.wipeContainerRootURL (folder whose contents should be erased) + + "HTTPPipeline"/ - OCVault.httpPipelineRootURL + backend.sqlite - OCHTTPPipelineManager.backendRootURL + tmp/ - OCHTTPPipelineBackend.backendTemporaryFilesRootURL + + "TemporaryDownloads"/ - OCVault.temporaryDownloadURL + [Random UUID] (temporary files for downloads) + + "messageQueue.dat" - OCMessageQueue.globalQueue KVS + + "postBuildSettings.plist" - OCClassSettingsFlatSourcePostBuild storage + + + [NSFileProviderManager documentStorageURL]/ + "VFS"/ - OCVault.vfsStorageRootURLForBookmarkUUID:nil + [VFS Node ID] + … + + [Bookmark UUID]/ - OCVault.filesRootURL + "org.owncloud.fp-services:[Bookmark UUID]" - OCVault.fpServicesURL (file) + + "VFS"/ - OCVault.vfsStorageRootURLForBookmarkUUID:bookmarkUUID + [VFS Node ID] + … + + "TUS"/ - Storage for TUS segments (OCSyncActionUpload) + [Random UUID] + + "Erasure"/ - OCVault.wipeContainerFilesRootURL (folder whose contents should be erased) + + *OC10/without drives* + [Local ID]/ - OCVault.localFolderURLForItem + [filename.xyz] - OCVault.localURLForItem + + + *Drive-based* + "Drives"/ - OCVault.drivesRootURL + [Drive ID]/ - OCVault.localDriveRootURLForDriveID --> !! Virtual !! + [Local ID]/ - OCVault.localFolderURLForItem + [filename.xyz] - OCVault.localURLForItem + + */ NS_ASSUME_NONNULL_BEGIN @@ -38,17 +98,29 @@ typedef BOOL(^OCVaultCompactSelector)(OCSyncAnchor _Nullable syncAnchor, OCItem NSURL *_rootURL; NSURL *_databaseURL; + NSURL *_drivesRootURL; NSURL *_filesRootURL; NSURL *_httpPipelineRootURL; NSURL *_temporaryDownloadURL; + NSURL *_wipeContainerRootURL; + NSURL *_wipeContainerFilesRootURL; #if OC_FEATURE_AVAILABLE_FILEPROVIDER NSFileProviderDomain *_fileProviderDomain; NSFileProviderManager *_fileProviderManager; - NSMutableDictionary *_fileProviderSignalCountByContainerItemIdentifiers; + NSMutableDictionary *_fileProviderSignalCountByContainerItemIdentifiers; NSString *_fileProviderSignalCountByContainerItemIdentifiersLock; #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ + NSArray *_activeDrives; + NSArray *_subscribedDrives; + NSArray *_detachedDrives; + NSDictionary *_activeDrivesByID; + NSDictionary *_detachedDrivesByID; + BOOL _observingDrivesUpdates; + + OCVFSCore *_vfsCore; + OCDatabase *_database; } @@ -58,15 +130,25 @@ typedef BOOL(^OCVaultCompactSelector)(OCSyncAnchor _Nullable syncAnchor, OCItem @property(strong) OCBookmark *bookmark; @property(nullable,readonly,nonatomic) OCDatabase *database; //!< The vault's database. +@property(nullable,readonly,nonatomic) OCResourceManager *resourceManager; //!< The vault's resource manager. @property(nullable,readonly,nonatomic) OCKeyValueStore *keyValueStore; //!< Key-value store (lazily allocated), available independant of opening/closing the vault @property(nullable,readonly,nonatomic) NSURL *rootURL; //!< The vault's root directory @property(nullable,readonly,nonatomic) NSURL *databaseURL; //!< The vault's SQLite database location @property(nullable,readonly,nonatomic) NSURL *keyValueStoreURL; //!< The vault's key value store location @property(nullable,readonly,nonatomic) NSURL *filesRootURL; //!< The vault's root URL for file storage +@property(nullable,readonly,nonatomic) NSURL *drivesRootURL; //!< The vault's root URL for drive folders @property(nullable,readonly,nonatomic) NSURL *httpPipelineRootURL; //!< The vault's root URL for HTTP pipeline data @property(nullable,readonly,nonatomic) NSURL *temporaryDownloadURL; //!< The vault's root URL for temporarily downloaded files. +@property(nullable,readonly,nonatomic) NSURL *wipeContainerRootURL; //!< The vault's rootURL subfolder for items to erase. +@property(nullable,readonly,nonatomic) NSURL *wipeContainerFilesRootURL; //!< The vault's filesRootURL subfolder for items to erase. + +@property(nullable,readonly,class,nonatomic) NSURL *storageRootURL; //!< The root URL for file storage for file providers + ++ (NSURL *)storageRootURLForBookmarkUUID:(nullable OCBookmarkUUID)bookmarkUUID; //!< The root URL for file storage: globally for bookmarkUUID==nil, per-account if a bookmarkUUID is passed ++ (NSURL *)vfsStorageRootURLForBookmarkUUID:(nullable OCBookmarkUUID)bookmarkUUID; //!< The root URL for VFS virtual node storage: globally for bookmarkUUID==nil, per-account if a bookmarkUUID is passed + #if OC_FEATURE_AVAILABLE_FILEPROVIDER @property(nullable,readonly,nonatomic) NSFileProviderDomain *fileProviderDomain; //!< File provider domain matching the bookmark's UUID @property(nullable,readonly,nonatomic) NSFileProviderManager *fileProviderManager; //!< File provider manager for .fileProviderDomain @@ -86,7 +168,30 @@ typedef BOOL(^OCVaultCompactSelector)(OCSyncAnchor _Nullable syncAnchor, OCItem - (void)compactWithSelector:(nullable OCVaultCompactSelector)selector completionHandler:(nullable OCCompletionHandler)completionHandler; //!< Compacts the vaults contents, disposing of unneeded files. If a selector is provided, only files for which it ALSO returns YES are disposed off. - (void)eraseWithCompletionHandler:(nullable OCCompletionHandler)completionHandler; //!< Completely erases the vaults contents. +#pragma mark - Wiping +- (void)wipeItemAtURL:(NSURL *)itemURL returnAfterMove:(BOOL)returnAfterMove withCompletionHandler:(nullable OCCompletionHandler)inCompletionHandler; //!< Moves the filesystem item (file/folder) into a special to-be-wiped folder, then deletes it there. If the app is terminated while at it, the filesystem item to wipe will be deleted at the next call of -emptyWipeFoldersWithCompletionHandler: +- (void)emptyWipeFoldersWithCompletionHandler:(void(^)(NSError * _Nullable error))completionHandler; //!< Deletes the contents of the vault's wipe folders. + +#pragma mark - Drives +@property(strong,readonly,nonatomic) NSArray *activeDrives; //!< All drives returned by the server +@property(strong,readonly,nonatomic) NSArray *subscribedDrives; //!< All drives the user is subscribed to +@property(strong,readonly,nonatomic) NSArray *detachedDrives; //!< Drives that no longer exist on the server but (may) have unsynced user data in them. Check OCDrive.detachedState. + +- (void)updateWithRemoteDrives:(NSArray *)newDrives; //!< Uses newDrives as new list of drives available on the server to update .activeDrives, .subscribedDrives and .detachedDrives. + +- (void)subscribeToDrives:(NSArray *)drives; //!< Adds the drives to the list of subscribed drives +- (void)unsubscribeFromDrives:(NSArray *)drives; //!< Removes the drives from the list of subscribed drives + +- (void)changeDetachedState:(OCDriveDetachedState)detachedState forDriveID:(OCDriveID)detachedDriveID; //!< Changes the detached state for an already detached drive. +- (nullable OCDrive *)driveWithIdentifier:(OCDriveID)driveID; + +- (void)startDriveUpdates; //!< Initializes the drive properties and starts observing for changes. This is usually called by -openWithCompletionHandler:. Calling this without opening a vault allows getting access to the drives list and be notified of changes. Useful mostly for VFS implementations. +- (void)stopDriveUpdates; //!< Stops observing for changes. This is usually called by -closeWithCompletionHandler:. + +- (void)eraseDrive:(OCDriveID)driveID withCompletionHandler:(nullable OCCompletionHandler)completionHandler; //!< Completely erases the contents of a drive. + #pragma mark - URL and path builders +- (nullable NSURL *)localDriveRootURLForDriveID:(nullable OCDriveID)driveID; //!< Returns the root folder for the drive with ID driveID - (nullable NSURL *)localURLForItem:(OCItem *)item; //!< Builds the URL to where an item should be stored. Follows // pattern. - (nullable NSURL *)localFolderURLForItem:(OCItem *)item; //!< Builds the URL to where an item's folder should be stored. Follows // pattern. - (nullable NSString *)relativePathForItem:(OCItem *)item; @@ -97,10 +202,39 @@ typedef BOOL(^OCVaultCompactSelector)(OCSyncAnchor _Nullable syncAnchor, OCItem @property(nullable,readonly,nonatomic,class) NSURL *httpPipelineRootURL; +#pragma mark - URL/path parser ++ (nullable OCVaultLocation *)locationForURL:(NSURL *)url; ++ (nullable NSURL *)urlForLocation:(OCVaultLocation *)location; + +@end + +#pragma mark - VFS +@interface OCVault (VFSExtras) + +@property(strong,nullable,readonly,nonatomic) OCVFSCore *vfs; //!< Returns the VFS for the vault, if any. Requires implementation of the OCVaultVFSProvider protocol in an OCVault category. +- (nullable NSArray *)translatedParentIDsForItem:(OCItem *)item suggestedRootFolderID:(nullable OCLocalID)suggestedRootFolderID; + +@end + +@class OCVFSCore; + +@protocol OCVaultVFSTranslation +- (nullable NSSet *)vfsRefreshIDsForDriveChangesWithAdditions:(nullable NSArray *)addedDrives updates:(nullable NSArray *)updatedDrives removals:(nullable NSArray *)removedDrives; +- (nullable NSArray *)vfsRefreshParentIDsForItem:(OCItem *)item; +@end + +@protocol OCVaultVFSProvider +- (nullable OCVFSCore *)provideVFS; @end extern NSString *OCVaultPathVaults; extern NSString *OCVaultPathHTTPPipeline; +extern NSString *OCVaultPathDrives; +extern NSString *OCVaultPathVFS; + +extern OCKeyValueStoreKey OCKeyValueStoreKeyVaultDriveList; + +extern NSNotificationName OCVaultDriveListChanged; //!< Notification sent when an OCVault's drive list has changed. The object is the OCVault. NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Vaults/OCVault.m b/ownCloudSDK/Vaults/OCVault.m index abeaf042..b29f091a 100644 --- a/ownCloudSDK/Vaults/OCVault.m +++ b/ownCloudSDK/Vaults/OCVault.m @@ -33,6 +33,14 @@ #import "OCHTTPPolicyManager.h" #import "OCBookmark+DBMigration.h" #import "OCDatabase+Schemas.h" +#import "OCResourceManager.h" +#import "OCDatabase+ResourceStorage.h" +#import "NSString+OCPath.h" +#import "OCVaultDriveList.h" +#import "NSArray+OCMapping.h" +#import "NSArray+OCFiltering.h" +#import "GADrive.h" +#import "GADriveItem.h" @implementation OCVault @@ -45,6 +53,7 @@ @implementation OCVault @synthesize database = _database; @synthesize keyValueStore = _keyValueStore; +@synthesize resourceManager = _resourceManager; + (BOOL)vaultInitializedForBookmark:(OCBookmark *)bookmark { @@ -135,6 +144,62 @@ - (NSURL *)keyValueStoreURL return (_keyValueStoreURL); } +- (NSURL *)drivesRootURL +{ + if (_drivesRootURL == nil) + { + _drivesRootURL = [self.filesRootURL URLByAppendingPathComponent:OCVaultPathDrives isDirectory:YES]; + } + + return (_drivesRootURL); +} + ++ (NSURL *)storageRootURL +{ + #if OC_FEATURE_AVAILABLE_FILEPROVIDER + static dispatch_once_t onceToken; + static NSURL *storageRootURL; + + if (OCVault.hostHasFileProvider) + { + dispatch_once(&onceToken, ^{ + storageRootURL = NSFileProviderManager.defaultManager.documentStorageURL.URLByStandardizingPath; + }); + + return (storageRootURL); + } + #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ + + return (nil); +} + ++ (NSURL *)storageRootURLForBookmarkUUID:(OCBookmarkUUID)bookmarkUUID +{ + if (bookmarkUUID == nil) + { + return ([self storageRootURL]); + } + + return ([[self storageRootURL] URLByAppendingPathComponent:bookmarkUUID.UUIDString isDirectory:YES]); +} + ++ (NSURL *)vfsStorageRootURLForBookmarkUUID:(OCBookmarkUUID)bookmarkUUID +{ + static dispatch_once_t onceToken; + static NSURL *vsfStorageRootURL; + + dispatch_once(&onceToken, ^{ + vsfStorageRootURL = [[self storageRootURL] URLByAppendingPathComponent:@"VFS" isDirectory:YES]; + }); + + if (bookmarkUUID == nil) + { + return (vsfStorageRootURL); + } + + return ([[self storageRootURL] URLByAppendingPathComponent:[bookmarkUUID.UUIDString stringByAppendingPathComponent:@"VFS"] isDirectory:YES]); +} + - (NSURL *)filesRootURL { if (_filesRootURL == nil) @@ -142,12 +207,12 @@ - (NSURL *)filesRootURL #if OC_FEATURE_AVAILABLE_FILEPROVIDER if (OCVault.hostHasFileProvider) { - _filesRootURL = [[NSFileProviderManager defaultManager].documentStorageURL URLByAppendingPathComponent:[_uuid UUIDString]]; + _filesRootURL = [OCVault.storageRootURL URLByAppendingPathComponent:_uuid.UUIDString isDirectory:YES]; } else #endif /* OC_FEATURE_AVAILABLE_FILEPROVIDER */ { - _filesRootURL = [self.rootURL URLByAppendingPathComponent:@"Files"]; + _filesRootURL = [self.rootURL URLByAppendingPathComponent:@"Files" isDirectory:YES]; } } @@ -214,6 +279,26 @@ - (NSURL *)temporaryDownloadURL return (_temporaryDownloadURL); } +- (NSURL *)wipeContainerRootURL +{ + if (_wipeContainerRootURL == nil) + { + _wipeContainerRootURL = [self.rootURL URLByAppendingPathComponent:@"Erasure"]; + } + + return (_wipeContainerRootURL); +} + +- (NSURL *)wipeContainerFilesRootURL +{ + if (_wipeContainerFilesRootURL == nil) + { + _wipeContainerFilesRootURL = [self.filesRootURL URLByAppendingPathComponent:@"Erasure"]; + } + + return (_wipeContainerFilesRootURL); +} + - (OCDatabase *)database { if (_database == nil) @@ -231,11 +316,443 @@ - (OCKeyValueStore *)keyValueStore _keyValueStore = [OCKeyValueStore sharedWithURL:self.keyValueStoreURL identifier:self.uuid.UUIDString owner:nil]; [_keyValueStore registerClass:OCEventQueue.class forKey:OCKeyValueStoreKeyOCCoreSyncEventsQueue]; [_keyValueStore registerClass:OCCoreUpdateScheduleRecord.class forKey:OCKeyValueStoreKeyCoreUpdateScheduleRecord]; + [_keyValueStore registerClass:OCVaultDriveList.class forKey:OCKeyValueStoreKeyVaultDriveList]; } return (_keyValueStore); } +- (OCResourceManager *)resourceManager +{ + if (_resourceManager == nil) + { + OCDatabase *database; + + if ((database = self.database) != nil) + { + _resourceManager = [[OCResourceManager alloc] initWithStorage:database]; + } + } + + return (_resourceManager); +} + +#pragma mark - VFS +- (OCVFSCore *)vfs +{ + if (_vfsCore == nil) + { + if ([self conformsToProtocol:@protocol(OCVaultVFSProvider)]) + { + _vfsCore = [(id)self provideVFS]; + } + } + + return (_vfsCore); +} + +#pragma mark - Drives +- (NSArray *)activeDrives +{ + @synchronized(self) + { + return (_activeDrives); + } +} + +- (void)updateWithRemoteDrives:(NSArray *)newRemoteDrives +{ + // Sort _drives by type and name + newRemoteDrives = [newRemoteDrives sortedArrayUsingComparator:^NSComparisonResult(OCDrive *drive1, OCDrive *drive2) { + NSComparisonResult result; + + if ((result = [drive1.type localizedCompare:drive2.type]) != NSOrderedSame) + { + return (result); + } + + return ([drive1.name localizedCompare:drive2.name]); + }]; + + NSMutableSet *newActiveDrivesIDs = [newRemoteDrives setUsingMapper:^OCDriveID(OCDrive *drive) { + if (drive.isDeactivated) + { + // Do not include deactivated drives in newDrivesIDs + return (nil); + } + return (drive.identifier); + }]; + NSMutableSet *newDrivesIDs = [newRemoteDrives setUsingMapper:^OCDriveID(OCDrive *drive) { + return (drive.identifier); + }]; + + __block NSMutableArray *remotelyAddedDrives = [NSMutableArray new]; + __block NSMutableArray *remotelyRemovedDrives = [NSMutableArray new]; + __block NSMutableArray *remotelyUpdatedDrives = [NSMutableArray new]; + __block BOOL changedDetachedDrives = NO; + + [self _modifyDriveListWith:^OCVaultDriveList *(OCVaultDriveList *driveList, BOOL *outDidModify) { + BOOL detachedDrivesChanged = NO; + + NSMutableSet *existingDrivesIDs = [driveList.drives setUsingMapper:^OCDriveID(OCDrive *drive) { + return (drive.identifier); + }]; + + // Find no longer detached drives + NSMutableArray *detachedDrives = [driveList.detachedDrives mutableCopy]; + if (detachedDrives == nil) { detachedDrives = [NSMutableArray new]; } + + for (OCDrive *detachedDrive in driveList.detachedDrives) + { + if ([newActiveDrivesIDs containsObject:detachedDrive.identifier]) + { + OCTLogDebug(@[@"Drives"], @"Re-attached detached drive: %@", detachedDrive); + + [detachedDrives removeObject:detachedDrive]; + detachedDrivesChanged = YES; + } + } + + // Find new detached drives + for (OCDrive *drive in driveList.drives) + { + if (![newDrivesIDs containsObject:drive.identifier]) + { + OCTLogDebug(@[@"Drives"], @"Newly detached drive: %@", drive); + + drive.detachedState = OCDriveDetachedStateNew; + drive.detachedSinceDate = [NSDate new]; + + [detachedDrives addObject:drive]; + [remotelyRemovedDrives addObject:drive]; + + detachedDrivesChanged = YES; + } + } + + // Find added drives + for (OCDrive *newRemoteDrive in newRemoteDrives) + { + if (![existingDrivesIDs containsObject:newRemoteDrive.identifier] && !newRemoteDrive.isDeactivated) + { + [remotelyAddedDrives addObject:newRemoteDrive]; + } + } + + // Find updated drives + NSMutableDictionary *existingDrivesByDriveID = [driveList.drives dictionaryUsingMapper:^OCDriveID(OCDrive *drive) { + return (drive.identifier); + }]; + + for (OCDrive *newRemoteDrive in newRemoteDrives) + { + if (newRemoteDrive.identifier != nil) + { + OCDrive *existingDrive; + + if ((existingDrive = existingDrivesByDriveID[newRemoteDrive.identifier]) != nil) + { + if ([newRemoteDrive isSubstantiallyDifferentFrom:existingDrive]) + { +// OCLogDebug(@"1: %d", OCNANotEqual(newRemoteDrive.identifier, existingDrive.identifier)); +// OCLogDebug(@"2: %d", OCNANotEqual(newRemoteDrive.type, existingDrive.type)); +// OCLogDebug(@"3: %d", OCNANotEqual(newRemoteDrive.name, existingDrive.name)); +// OCLogDebug(@"4: %d", OCNANotEqual(newRemoteDrive.desc, existingDrive.desc)); +// OCLogDebug(@"5: %d", OCNANotEqual(newRemoteDrive.davRootURL, existingDrive.davRootURL)); +// OCLogDebug(@"6: %d", OCNANotEqual([newRemoteDrive.gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag, [existingDrive.gaDrive specialDriveItemFor:GASpecialFolderNameImage].eTag)); +// OCLogDebug(@"7: %d", OCNANotEqual([newRemoteDrive.gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag, [existingDrive.gaDrive specialDriveItemFor:GASpecialFolderNameReadme].eTag)); +// OCLogDebug(@"8: %d", OCNANotEqual(newRemoteDrive.detachedSinceDate, existingDrive.detachedSinceDate)); +// OCLogDebug(@"9: %d", (newRemoteDrive.detachedState != existingDrive.detachedState)); +// OCLogDebug(@"10: %d", OCNANotEqual(newRemoteDrive.rootETag, existingDrive.rootETag)); + + [remotelyUpdatedDrives addObject:newRemoteDrive]; + } + } + } + } + + // Find deactivated drives + for (OCDrive *newRemoteDrive in newRemoteDrives) + { + if ((newRemoteDrive.identifier != nil) && newRemoteDrive.isDeactivated) + { + OCDrive *existingDetachedDrive = [detachedDrives firstObjectMatching:^BOOL(OCDrive * _Nonnull detachedDrive) { + return ([detachedDrive.identifier isEqual:newRemoteDrive.identifier]); + }]; + + if (existingDetachedDrive != nil) + { + // Update existing detached drive with latest drive info + existingDetachedDrive.gaDrive = newRemoteDrive.gaDrive; + } + else + { + // Add as detached drive + newRemoteDrive.detachedState = OCDriveDetachedStateNew; + newRemoteDrive.detachedSinceDate = [NSDate new]; + + [detachedDrives addObject:newRemoteDrive]; + + detachedDrivesChanged = YES; + } + } + } + + // Update drives with new remote drives + if ((remotelyAddedDrives.count > 0) || (remotelyUpdatedDrives.count > 0) || (remotelyRemovedDrives.count > 0)) + { + if (remotelyAddedDrives.count > 0) + { + OCTLogDebug(@[@"Drives"], @"Remotely added drives: %@", remotelyAddedDrives); + } + + if (remotelyUpdatedDrives.count > 0) + { + OCTLogDebug(@[@"Drives"], @"Remotely updated drives: %@", remotelyUpdatedDrives); + } + + if (remotelyRemovedDrives.count > 0) + { + OCTLogDebug(@[@"Drives"], @"Remotely removed drives: %@", remotelyRemovedDrives); + } + + for (OCDrive *addedDrive in remotelyAddedDrives) + { + // Subscribe to new drives by default + // (when implementing a policy here in the future, consider always subscribing to the personal space regardless of policy) + // (also handle the return of detached drives and don't automatically re-add them as subscribed drives if they weren't before) + [driveList.subscribedDriveIDs addObject:addedDrive.identifier]; + } + + driveList.drives = newRemoteDrives; + *outDidModify = YES; + } + + // Update detached drives + if (detachedDrivesChanged) + { + changedDetachedDrives = detachedDrivesChanged; + + driveList.detachedDrives = detachedDrives; + *outDidModify = YES; + } + + return (driveList); + }]; + + // Signal drive changes to File Provider + if ((remotelyAddedDrives.count > 0) || (remotelyUpdatedDrives.count > 0) || (remotelyRemovedDrives.count > 0)) + { + [self signalDriveChangesWithAdditions:remotelyAddedDrives updates:remotelyUpdatedDrives removals:remotelyRemovedDrives]; + } + + // Signal detached drive changes + if (changedDetachedDrives) + { + [NSNotificationCenter.defaultCenter postNotificationName:OCVaultDetachedDrivesListChanged object:self]; + } +} + +- (NSArray *)subscribedDrives +{ + @synchronized(self) + { + return (_subscribedDrives); + } +} + +- (NSArray *)detachedDrives +{ + @synchronized(self) + { + return (_detachedDrives); + } +} + +- (void)subscribeToDrives:(NSArray *)drives +{ + [self _modifyDriveListWith:^OCVaultDriveList *(OCVaultDriveList *driveList, BOOL *outDidModify) { + NSUInteger count = driveList.subscribedDriveIDs.count; + + for (OCDrive *drive in drives) + { + [driveList.subscribedDriveIDs addObject:drive.identifier]; + } + + if (count != driveList.subscribedDriveIDs.count) + { + OCTLogDebug(@[@"Drives"], @"Subscribed to drives: %@", drives); + *outDidModify = YES; + } + + return (driveList); + }]; +} + +- (void)unsubscribeFromDrives:(NSArray *)drives +{ + [self _modifyDriveListWith:^OCVaultDriveList *(OCVaultDriveList *driveList, BOOL *outDidModify) { + NSUInteger count = driveList.subscribedDriveIDs.count; + + for (OCDrive *drive in drives) + { + [driveList.subscribedDriveIDs removeObject:drive.identifier]; + } + + if (count != driveList.subscribedDriveIDs.count) + { + OCTLogDebug(@[@"Drives"], @"Unsubscribed from drives: %@", drives); + *outDidModify = YES; + } + + return (driveList); + }]; +} + +- (void)changeDetachedState:(OCDriveDetachedState)detachedState forDriveID:(OCDriveID)detachedDriveID +{ + [self _modifyDriveListWith:^OCVaultDriveList *(OCVaultDriveList *driveList, BOOL *outDidModify) { + + OCDrive *modifyDrive = [driveList.detachedDrives firstObjectMatching:^BOOL(OCDrive * _Nonnull drive) { + return ([drive.identifier isEqual:detachedDriveID]); + }]; + + if (modifyDrive != nil) + { + OCTLogDebug(@[@"Drives"], @"Changed detachedState to %ld for drive: %@", (long)detachedState, modifyDrive); + + modifyDrive.detachedState = detachedState; + *outDidModify = YES; + } + + return (driveList); + }]; +} + +- (nullable OCDrive *)driveWithIdentifier:(OCDriveID)driveID +{ + OCDrive *drive; + + @synchronized(self) + { + if ((drive = _activeDrivesByID[driveID]) == nil) + { + drive = _detachedDrivesByID[driveID]; + } + } + + return (drive); +} + +- (void)startDriveUpdates +{ + if (!_observingDrivesUpdates) + { + __weak OCVault *weakSelf = self; + __block BOOL isInitial = YES; + + _observingDrivesUpdates = YES; + + OCTLogDebug(@[@"Drives"], @"Starting drive update observation for bookmark: %@", _bookmark); + + [self.keyValueStore addObserver:^(OCKeyValueStore * _Nonnull store, id _Nullable owner, OCKeyValueStoreKey _Nonnull key, OCVaultDriveList * _Nullable driveList) { + [weakSelf _updateFromDriveList:driveList]; + + if (isInitial) + { + isInitial = NO; + } + else + { + dispatch_async(dispatch_get_main_queue(), ^{ + [NSNotificationCenter.defaultCenter postNotificationName:OCVaultDriveListChanged object:weakSelf]; + }); + } + } forKey:OCKeyValueStoreKeyVaultDriveList withOwner:self initial:YES]; + } +} + +- (void)stopDriveUpdates +{ + if (_observingDrivesUpdates) + { + _observingDrivesUpdates = NO; + [self.keyValueStore removeObserverForOwner:self forKey:OCKeyValueStoreKeyVaultDriveList]; + + OCTLogDebug(@[@"Drives"], @"Stopped drive update observation for bookmark: %@", _bookmark); + } +} + +- (void)_modifyDriveListWith:(OCVaultDriveList *(^)(OCVaultDriveList *driveList, BOOL *outDidModify))driveListModifier +{ + __block OCVaultDriveList *updateFromDriveList = nil; + + [self.keyValueStore updateObjectForKey:OCKeyValueStoreKeyVaultDriveList usingModifier:^id _Nullable(OCVaultDriveList *driveList, BOOL * _Nonnull outDidModify) { + if (driveList == nil) + { + driveList = [OCVaultDriveList new]; + } + + BOOL didModify = NO; + OCVaultDriveList *newDriveList = driveListModifier(driveList, &didModify); + + if (didModify) + { + updateFromDriveList = newDriveList; + } + + *outDidModify = didModify; + return (newDriveList); + }]; + + if (updateFromDriveList != nil) + { + [self _updateFromDriveList:updateFromDriveList]; + } +} + +- (void)_updateFromDriveList:(OCVaultDriveList *)driveList +{ + NSMutableSet *oldSubscribedDriveIDs = [_subscribedDrives setUsingMapper:^id _Nullable(OCDrive *drive) { + return (drive.identifier); + }]; + + [self willChangeValueForKey:@"activeDrives"]; + [self willChangeValueForKey:@"subscribedDrives"]; + [self willChangeValueForKey:@"detachedDrives"]; + + @synchronized(self) + { + _activeDrives = [driveList.drives copy]; + _activeDrivesByID = [_activeDrives dictionaryUsingMapper:^OCDriveID(OCDrive *drive) { + return (drive.identifier); + }]; + + _subscribedDrives = [driveList.drives filteredArrayUsingBlock:^BOOL(OCDrive * _Nonnull drive, BOOL * _Nonnull stop) { + return ([driveList.subscribedDriveIDs containsObject:drive.identifier] && !drive.isDeactivated); + }]; + + _detachedDrives = [driveList.detachedDrives copy]; + _detachedDrivesByID = [_detachedDrives dictionaryUsingMapper:^OCDriveID(OCDrive *drive) { + return (drive.identifier); + }]; + } + + [self didChangeValueForKey:@"detachedDrives"]; + [self didChangeValueForKey:@"subscribedDrives"]; + [self didChangeValueForKey:@"activeDrives"]; + + // Signal subscribed drive changes + NSMutableSet *newSubscribedDriveIDs = [_subscribedDrives setUsingMapper:^id _Nullable(OCDrive *drive) { + return (drive.identifier); + }]; + + if ((oldSubscribedDriveIDs != newSubscribedDriveIDs) && ![oldSubscribedDriveIDs isEqual:newSubscribedDriveIDs]) + { + [NSNotificationCenter.defaultCenter postNotificationName:OCVaultSubscribedDrivesListChanged object:self]; + } +} + #pragma mark - Operations - (void)openWithCompletionHandler:(OCCompletionHandler)completionHandler { @@ -251,7 +768,12 @@ - (void)openWithCompletionHandler:(OCCompletionHandler)completionHandler self.bookmark.databaseVersion = OCDatabaseVersionLatest; [[NSNotificationCenter defaultCenter] postNotificationName:OCBookmarkUpdatedNotification object:self.bookmark]; } + + self->_resourceManager = [[OCResourceManager alloc] initWithStorage:db]; } + + [self startDriveUpdates]; + completionHandler(db, error); }]; } @@ -266,6 +788,8 @@ - (void)openWithCompletionHandler:(OCCompletionHandler)completionHandler - (void)closeWithCompletionHandler:(OCCompletionHandler)completionHandler { + [self stopDriveUpdates]; + [self.database closeWithCompletionHandler:completionHandler]; } @@ -276,6 +800,69 @@ - (void)compactWithSelector:(nullable OCVaultCompactSelector)selector completion } completionHandler:completionHandler]; } +- (void)eraseDrive:(OCDriveID)driveID withCompletionHandler:(nullable OCCompletionHandler)completionHandler +{ + if ((self.rootURL != nil) && (driveID != nil)) + { + void(^WipeDriveRootFolder)(OCCompletionHandler completionHandler) = ^(OCCompletionHandler completionHandler){ + NSURL *driveRootURL; + + if ((driveRootURL = [self localDriveRootURLForDriveID:driveID]) != nil) + { + [self wipeItemAtURL:driveRootURL returnAfterMove:YES withCompletionHandler:completionHandler]; + } + else + { + completionHandler(self, OCError(OCErrorInvalidParameter)); + } + }; + + if (self.database.isOpened) + { + [self.database purgeCacheItemsWithDriveID:driveID completionHandler:^(OCDatabase *db, NSError *error) { + if (error != nil) + { + if (completionHandler != nil) + { + completionHandler(self, error); + } + + return; + } + + WipeDriveRootFolder(completionHandler); + }]; + } + else + { + [self.database openWithCompletionHandler:^(OCDatabase *db, NSError *error) { + if (error != nil) + { + if (completionHandler != nil) + { + completionHandler(self, error); + } + + return; + } + + WipeDriveRootFolder(^(id sender, NSError *wipeError){ + [self.database closeWithCompletionHandler:^(OCDatabase *db, NSError *error) { + if (completionHandler != nil) + { + completionHandler(self, wipeError); + } + }]; + }); + }]; + } + } + else + { + completionHandler(self, OCError(OCErrorInsufficientParameters)); + } +} + - (void)eraseWithCompletionHandler:(OCCompletionHandler)completionHandler { if (self.rootURL != nil) @@ -322,6 +909,93 @@ - (void)eraseWithCompletionHandler:(OCCompletionHandler)completionHandler OCFileOpLog(@"rm", error, @"Removed files root at %@", self.filesRootURL.path); } + if (completionHandler != nil) + { + completionHandler(self, error); + } + }); + } + else + { + completionHandler(self, OCError(OCErrorInsufficientParameters)); + } +} + +#pragma mark - Wiping +- (void)wipeItemAtURL:(NSURL *)itemURL returnAfterMove:(BOOL)returnAfterMove withCompletionHandler:(nullable OCCompletionHandler)inCompletionHandler +{ + NSURL *temporaryErasureURL = nil; + NSURL *wipeRootURL = nil; + NSError *createFolderError = nil; + + if ([itemURL.path hasPrefix:self.filesRootURL.path]) + { + // File/Folder to remove is inside filesRootURL -> use .wipeContainerFilesRootURL + UUID + wipeRootURL = self.wipeContainerFilesRootURL; + } + else + { + // File/Folder to remove is outside filesRootURL -> use .wipeContainerRootURL + UUID for everything else + wipeRootURL = self.wipeContainerRootURL; + } + + temporaryErasureURL = [wipeRootURL URLByAppendingPathComponent:NSUUID.UUID.UUIDString]; + + if (![[NSFileManager defaultManager] createDirectoryAtURL:wipeRootURL withIntermediateDirectories:YES attributes:@{ NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication } error:&createFolderError]) + { + if (createFolderError == nil) + { + // Directory creation failed but no error was returned + createFolderError = OCError(OCErrorInternal); + } + + if (inCompletionHandler != nil) + { + inCompletionHandler(self, createFolderError); + } + + return; + } + + if ((itemURL != nil) && (temporaryErasureURL != nil)) + { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ + NSError *error = nil; + OCCompletionHandler completionHandler = inCompletionHandler; + NSFileManager *fileManager = [NSFileManager new]; + + fileManager.delegate = self; + + if ([fileManager fileExistsAtPath:itemURL.path]) + { + NSURL *removeURL = itemURL; + + // Move to erasureTargetURL + if ([fileManager moveItemAtURL:itemURL toURL:temporaryErasureURL error:&error]) + { + removeURL = temporaryErasureURL; + + if (returnAfterMove && (completionHandler != nil)) + { + completionHandler(self, nil); + completionHandler = nil; + } + } + + OCFileOpLog(@"mv", error, @"Moved erasure content from %@ to %@", itemURL.path, removeURL.path); + + // Remove actualEraseURL (which is either inURL or temporaryErasureURL) + if (![fileManager removeItemAtURL:removeURL error:&error]) + { + if (error == nil) + { + error = OCError(OCErrorInternal); + } + } + + OCFileOpLog(@"rm", error, @"Removing erasure content at %@ (previously moved from %@)", removeURL.path, itemURL.path); + } + if (completionHandler != nil) { completionHandler(self, error); @@ -330,17 +1004,85 @@ - (void)eraseWithCompletionHandler:(OCCompletionHandler)completionHandler } } +- (void)emptyWipeFoldersWithCompletionHandler:(void(^)(NSError * _Nullable error))completionHandler +{ + NSMutableArray *wipeURLs = [NSMutableArray new]; + NSError *error = nil; + + NSFileManager *fileManager = [NSFileManager new]; + fileManager.delegate = self; + + OCWaitInit(wipeWait); + + if ([fileManager fileExistsAtPath:self.wipeContainerRootURL.path]) + { + NSArray *contentURLs; + + if ((contentURLs = [fileManager contentsOfDirectoryAtURL:self.wipeContainerRootURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants error:&error]) != nil) + { + [wipeURLs addObjectsFromArray:contentURLs]; + } + } + + if ([fileManager fileExistsAtPath:self.wipeContainerFilesRootURL.path]) + { + NSArray *contentURLs; + + if ((contentURLs = [fileManager contentsOfDirectoryAtURL:self.wipeContainerFilesRootURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants error:&error]) != nil) + { + [wipeURLs addObjectsFromArray:contentURLs]; + } + } + + dispatch_queue_t asyncWipeQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); + + for (NSURL *wipeURL in wipeURLs) + { + OCWaitWillStartTask(wipeWait); + + dispatch_async(asyncWipeQueue, ^{ + NSFileManager *fileManager = [NSFileManager new]; + fileManager.delegate = self; + + [fileManager removeItemAtURL:wipeURL error:nil]; + + OCWaitDidFinishTask(wipeWait); + }); + } + + OCWaitOnCompletion(wipeWait, asyncWipeQueue, ^{ + if (completionHandler != nil) + { + completionHandler(nil); + } + }); +} + + #pragma mark - URL and path builders +- (NSURL *)localDriveRootURLForDriveID:(nullable OCDriveID)driveID +{ + // Returns the root folder for the drive with ID driveID + if (driveID == nil) + { + // oC10 items have no drive ID, so just return the filesRootURL + return (self.filesRootURL); + } + + // Otherwise return + return ([self.drivesRootURL URLByAppendingPathComponent:driveID isDirectory:YES]); +} + - (NSURL *)localURLForItem:(OCItem *)item { // Build the URL to where an item should be stored. Follow // pattern. - return ([self.filesRootURL URLByAppendingPathComponent:[self relativePathForItem:item] isDirectory:NO]); + return ([[self localDriveRootURLForDriveID:item.driveID] URLByAppendingPathComponent:[self relativePathForItem:item] isDirectory:NO]); } - (NSURL *)localFolderURLForItem:(OCItem *)item { // Build the URL to where an item's folder should be stored. Follows // pattern. - return ([self.filesRootURL URLByAppendingPathComponent:item.localID isDirectory:YES]); + return ([[self localDriveRootURLForDriveID:item.driveID] URLByAppendingPathComponent:item.localID isDirectory:YES]); } - (NSString *)relativePathForItem:(OCItem *)item @@ -369,8 +1111,149 @@ + (NSURL *)httpPipelineRootURL return [[[OCAppIdentity sharedAppIdentity] appGroupContainerURL] URLByAppendingPathComponent:OCVaultPathHTTPPipeline]; } +#pragma mark - URL/path parser ++ (nullable OCVaultLocation *)locationForURL:(NSURL *)url +{ + OCVaultLocation *location = nil; + NSString *urlPath = url.URLByStandardizingPath.path; + NSString *storageRootPath = OCVault.storageRootURL.path.normalizedDirectoryPath; + + if (![urlPath hasPrefix:storageRootPath]) + { + // URL not in file provider's storage root path + return (nil); + } + + NSString *relativePath = [urlPath substringFromIndex:storageRootPath.length]; + NSArray *pathComponents = relativePath.pathComponents; + + if (pathComponents.count > 0) + { + if ([pathComponents.firstObject isEqual:OCVaultPathVFS]) + { + // Possible layouts: + } + else + { + // Possible layouts: + // [Bookmark UUID]/[Local ID]/[filename.xyz] + // [Bookmark UUID]/Drives/[Drive ID]/[Local ID]/[filename.xyz] + const NSUInteger uuidStringLength = 36; + NSUInteger parsedElements = 0; + + if (pathComponents[0].length == uuidStringLength) // [Bookmark UUID]/… + { + location = [OCVaultLocation new]; + + location.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:pathComponents[0]]; + parsedElements = 1; + + if (pathComponents.count > 1) + { + if ([pathComponents[1] isEqual:OCVaultPathVFS]) // [Bookmark UUID]/VFS/… + { + parsedElements = 2; + if (pathComponents.count > 2) + { + location.vfsNodeID = pathComponents[2]; // [Bookmark UUID]/VFS/[VFS Node ID]/… + parsedElements = 3; + + if (pathComponents.count > 3) // && (pathComponents[3].length == uuidStringLength)) // [Bookmark UUID]/Drives/[Drive ID]/[Local ID]/… + { + location.localID = pathComponents[3]; + parsedElements = 4; + } + } + } + else if ([pathComponents[1] isEqual:OCVaultPathDrives]) // [Bookmark UUID]/Drives/… + { + parsedElements = 2; + if (pathComponents.count > 2) + { + location.driveID = pathComponents[2]; // [Bookmark UUID]/Drives/[Drive ID]/… + parsedElements = 3; + + // if ((pathComponents.count > 3) && (pathComponents[3].length == uuidStringLength)) // [Bookmark UUID]/Drives/[Drive ID]/[Local ID]/… + if (pathComponents.count > 3) // [Bookmark UUID]/Drives/[Drive ID]/[Local ID]/… + { + location.localID = pathComponents[3]; + parsedElements = 4; + } + else + { + // [Bookmark UUID]/Drives/[Drive ID] is virtual + location.isVirtual = YES; + } + } + } + else // if (pathComponents[1].length == uuidStringLength) // [Bookmark UUID]/[Local ID]/… + { + location.localID = pathComponents[1]; + parsedElements = 2; + } + } + + if (pathComponents.count > parsedElements) + { + location.additionalPathElements = [pathComponents subarrayWithRange:NSMakeRange(parsedElements, pathComponents.count - parsedElements)]; + } + } + } + } + + return (location); +} + ++ (nullable NSURL *)urlForLocation:(OCVaultLocation *)location +{ + NSURL *returnURL = nil; + + if (location.isVirtual) + { + // VFS Node + if (location.vfsNodeID != nil) + { + // VFS/[VFS Node ID] + // [Bookmark UUID]VFS/[VFS Node ID] + returnURL = [[self vfsStorageRootURLForBookmarkUUID:location.bookmarkUUID] URLByAppendingPathComponent:location.vfsNodeID]; + } + else if ((location.bookmarkUUID != nil) && (location.driveID != nil)) + { + // [Bookmark UUID]/Drives/[Drive ID] + returnURL = [[[self storageRootURLForBookmarkUUID:location.bookmarkUUID] URLByAppendingPathComponent:OCVaultPathDrives isDirectory:YES] URLByAppendingPathComponent:location.driveID isDirectory:YES]; + } + } + else if ((location.bookmarkUUID != nil) && (location.localID != nil)) + { + // Item location + if (location.driveID != nil) + { + // Drive-based: [storageRoot]/Drives/[LocalID]/… + returnURL = [[[[self storageRootURLForBookmarkUUID:location.bookmarkUUID] URLByAppendingPathComponent:OCVaultPathDrives isDirectory:YES] URLByAppendingPathComponent:location.driveID isDirectory:YES] URLByAppendingPathComponent:location.localID isDirectory:YES]; + } + else + { + // OC10 based: [storageRoot]/[LocalID]/… + returnURL = [[self storageRootURLForBookmarkUUID:location.bookmarkUUID] URLByAppendingPathComponent:location.localID isDirectory:YES]; + } + } + + if ((returnURL != nil) && (location.additionalPathElements.count > 0)) + { + // Append (additional) path elements + returnURL = [returnURL URLByAppendingPathComponent:[location.additionalPathElements componentsJoinedByString:@"/"] isDirectory:NO]; + } + + return (returnURL); +} + @end NSString *OCVaultPathVaults = @"Vaults"; NSString *OCVaultPathHTTPPipeline = @"HTTPPipeline"; +NSString *OCVaultPathDrives = @"Drives"; +NSString *OCVaultPathVFS = @"VFS"; + +OCKeyValueStoreKey OCKeyValueStoreKeyVaultDriveList = @"vaultDriveList"; +NSNotificationName OCVaultDriveListChanged = @"OCVaultDriveListChanged"; diff --git a/ownCloudSDK/Vaults/OCVaultDriveList.h b/ownCloudSDK/Vaults/OCVaultDriveList.h new file mode 100644 index 00000000..6ff5cad4 --- /dev/null +++ b/ownCloudSDK/Vaults/OCVaultDriveList.h @@ -0,0 +1,33 @@ +// +// OCVaultDriveList.h +// ownCloudSDK +// +// Created by Felix Schwarz on 16.05.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 "OCDrive.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVaultDriveList : NSObject + +@property(strong) NSArray *drives; //!< All (active) drives +@property(strong) NSMutableSet *subscribedDriveIDs; //!< List of all drives the user is subscribed to, may contain active and detached IDs + +@property(strong,nullable) NSArray *detachedDrives; //!< All detached drives + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Vaults/OCVaultDriveList.m b/ownCloudSDK/Vaults/OCVaultDriveList.m new file mode 100644 index 00000000..aa755844 --- /dev/null +++ b/ownCloudSDK/Vaults/OCVaultDriveList.m @@ -0,0 +1,61 @@ +// +// OCVaultDriveList.m +// ownCloudSDK +// +// Created by Felix Schwarz on 16.05.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 "OCVaultDriveList.h" +#import "OCEvent.h" + +@implementation OCVaultDriveList + +- (instancetype)init +{ + if ((self = [super init]) != nil) + { + _subscribedDriveIDs = [NSMutableSet new]; + _drives = @[ ]; + } + + return (self); +} + ++ (BOOL)supportsSecureCoding +{ + return (YES); +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)decoder +{ + if ((self = [super init]) != nil) + { + _drives = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"drives"]; + _subscribedDriveIDs = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"subscribedDriveIDs"]; + + _detachedDrives = [decoder decodeObjectOfClasses:OCEvent.safeClasses forKey:@"detachedDrives"]; + } + + return (self); +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder +{ + [coder encodeObject:_drives forKey:@"drives"]; + [coder encodeObject:_subscribedDriveIDs forKey:@"subscribedDriveIDs"]; + + [coder encodeObject:_detachedDrives forKey:@"detachedDrives"]; +} + +@end diff --git a/ownCloudSDK/Vaults/OCVaultLocation.h b/ownCloudSDK/Vaults/OCVaultLocation.h new file mode 100644 index 00000000..bb17d114 --- /dev/null +++ b/ownCloudSDK/Vaults/OCVaultLocation.h @@ -0,0 +1,43 @@ +// +// OCVaultLocation.h +// ownCloudSDK +// +// Created by Felix Schwarz on 26.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 "OCBookmark.h" +#import "OCTypes.h" +#import "OCVFSTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCVaultLocation : NSObject + +@property(strong, nullable) OCBookmarkUUID bookmarkUUID; +@property(strong, nullable) OCDriveID driveID; +@property(strong, nullable) OCLocalID localID; + +@property(assign) BOOL isVirtual; +@property(strong, nullable) OCVFSNodeID vfsNodeID; + +@property(strong, nullable) NSArray *additionalPathElements; + +#pragma mark - VFS item ID +- (nullable instancetype)initWithVFSItemID:(OCVFSItemID)vfsItemID; +@property(readonly,nonatomic) OCVFSItemID vfsItemID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ownCloudSDK/Vaults/OCVaultLocation.m b/ownCloudSDK/Vaults/OCVaultLocation.m new file mode 100644 index 00000000..c22c006c --- /dev/null +++ b/ownCloudSDK/Vaults/OCVaultLocation.m @@ -0,0 +1,108 @@ +// +// OCVaultLocation.m +// ownCloudSDK +// +// Created by Felix Schwarz on 26.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 "OCVaultLocation.h" +#import "OCVFSCore.h" + +@implementation OCVaultLocation + +#pragma mark - VFS item ID +- (instancetype)initWithVFSItemID:(OCVFSItemID)vfsItemID +{ + if ((self = [super init]) != nil) + { + BOOL recognized = NO; + + NSArray *segments = [vfsItemID componentsSeparatedByString:@"\\"]; + + if (segments.count > 0) + { + if ([segments[0] isEqual:@"V"]) + { + // Virtual items: V\[bookmarkUUID]\[driveID] or V\[vfsNodeID] + if (segments.count > 1) + { + if (segments.count > 2) + { + OCBookmarkUUIDString uuidString = segments[1]; + self.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:uuidString]; + self.driveID = segments[2]; + self.isVirtual = YES; + recognized = YES; + } + else + { + self.vfsNodeID = segments[1]; + self.isVirtual = YES; + recognized = YES; + } + } + } + + if ([segments[0] isEqual:@"I"]) + { + // Real items: I\[bookmarkUUID]\[driveID]\[localID][\[fileName]] + if (segments.count > 3) + { + OCBookmarkUUIDString uuidString = segments[1]; + self.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:uuidString]; + self.driveID = segments[2]; + self.localID = segments[3]; + recognized = YES; + } + else if (segments.count > 2) + { + OCBookmarkUUIDString uuidString = segments[1]; + self.bookmarkUUID = [[NSUUID alloc] initWithUUIDString:uuidString]; + self.localID = segments[2]; + recognized = YES; + } + } + } + + if (!recognized) + { + return (nil); + } + } + + return (self); +} + +- (OCVFSItemID)vfsItemID +{ + if (_vfsNodeID != nil) + { + if ((_bookmarkUUID != nil) && (_driveID != nil)) + { + return ([[NSString alloc] initWithFormat:@"V\\%@\\%@", _bookmarkUUID, _driveID]); + } + else + { + return ([[NSString alloc] initWithFormat:@"V\\%@", _vfsNodeID]); + } + } + else if ((_bookmarkUUID != nil) && (_localID != nil)) + { + return ([OCVFSCore composeVFSItemIDForOCItemWithBookmarkUUID:_bookmarkUUID.UUIDString driveID:_driveID localID:_localID]); + } + + return (nil); +} + +@end diff --git a/ownCloudSDK/Vaults/Prepopulation/OCVault+Prepopulation.m b/ownCloudSDK/Vaults/Prepopulation/OCVault+Prepopulation.m index 2e8486c4..ae8a1d7f 100644 --- a/ownCloudSDK/Vaults/Prepopulation/OCVault+Prepopulation.m +++ b/ownCloudSDK/Vaults/Prepopulation/OCVault+Prepopulation.m @@ -257,7 +257,7 @@ - (nullable NSProgress *)_prepopulateDatabaseWithXMLParserProvider:(OCXMLParser for (OCPath openPath in openPaths) { - [db addDirectoryUpdateJob:[OCCoreDirectoryUpdateJob withPath:openPath] completionHandler:^(OCDatabase *db, NSError *error, OCCoreDirectoryUpdateJob *updateJob) { + [db addDirectoryUpdateJob:[OCCoreDirectoryUpdateJob withLocation:[OCLocation legacyRootPath:openPath]] completionHandler:^(OCDatabase *db, NSError *error, OCCoreDirectoryUpdateJob *updateJob) { if (error != nil) { completionError = error; @@ -326,7 +326,7 @@ - (nullable NSProgress *)streamMetadataWithCompletionHandler:(void(^)(NSError *_ } }; - retrieveItemsProgress = [connection retrieveItemListAtPath:@"/" depth:OCPropfindDepthInfinity options:@{ + retrieveItemsProgress = [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:OCPropfindDepthInfinity options:@{ OCConnectionOptionResponseStreamHandler : [streamHandler copy] } resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { [connection disconnectWithCompletionHandler:^{ @@ -395,7 +395,7 @@ - (nullable NSProgress *)retrieveMetadataWithCompletionHandler:(void(^)(NSError NSURL *responseFileURL = [self.davRawResponseFolderURL URLByAppendingPathComponent:NSUUID.UUID.UUIDString isDirectory:NO]; NSProgress *retrieveItemsProgress; - retrieveItemsProgress = [connection retrieveItemListAtPath:@"/" depth:OCPropfindDepthInfinity options:@{ + retrieveItemsProgress = [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:OCPropfindDepthInfinity options:@{ OCConnectionOptionResponseDestinationURL : responseFileURL } resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { [connection disconnectWithCompletionHandler:^{ diff --git a/ownCloudSDK/Wait Condition/OCWaitCondition+Diagnostic.m b/ownCloudSDK/Wait Condition/OCWaitCondition+Diagnostic.m index ac9f4ad9..d1f0e795 100644 --- a/ownCloudSDK/Wait Condition/OCWaitCondition+Diagnostic.m +++ b/ownCloudSDK/Wait Condition/OCWaitCondition+Diagnostic.m @@ -44,7 +44,8 @@ @implementation OCWaitCondition (Diagnostic) if ((mdRefreshCondition = OCTypedCast(self, OCWaitConditionMetaDataRefresh)) != nil) { nodes = [nodes arrayByAddingObjectsFromArray:@[ - [OCDiagnosticNode withLabel:@"Item Path" content:mdRefreshCondition.itemPath], + [OCDiagnosticNode withLabel:@"Item Path" content:mdRefreshCondition.itemLocation.path], + [OCDiagnosticNode withLabel:@"Item Drive" content:mdRefreshCondition.itemLocation.driveID], [OCDiagnosticNode withLabel:@"Item Version" content:mdRefreshCondition.itemVersionIdentifier.description], [OCDiagnosticNode withLabel:@"Expiration Date" content:mdRefreshCondition.expirationDate.description] ]]; diff --git a/ownCloudSDK/Wait Condition/OCWaitConditionIssue.m b/ownCloudSDK/Wait Condition/OCWaitConditionIssue.m index 4ca918b9..e8adac43 100644 --- a/ownCloudSDK/Wait Condition/OCWaitConditionIssue.m +++ b/ownCloudSDK/Wait Condition/OCWaitConditionIssue.m @@ -73,7 +73,8 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O OCSyncRecord *syncRecord = nil; if (((core = options[OCWaitConditionOptionCore]) != nil) && - ((syncRecord = options[OCWaitConditionOptionSyncRecord]) != nil) + ((syncRecord = options[OCWaitConditionOptionSyncRecord]) != nil) && + (syncRecord.recordID != nil) ) { NSDictionary *userInfo = @{ @@ -144,7 +145,7 @@ - (BOOL)handleEvent:(OCEvent *)event withOptions:(OCWaitConditionOptions)options { [syncRecord removeWaitCondition:self]; - if (syncRecord.recordID != nil) // Check if the sync record has been removed as part of the issue resolution (f.ex. when descheduling) + if ((syncRecord.recordID != nil) && !syncRecord.removed) // Check if the sync record has been removed as part of the issue resolution (f.ex. when descheduling) { syncContext.updateStoredSyncRecordAfterItemUpdates = YES; } diff --git a/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.h b/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.h index b3d29409..1e0f38e5 100644 --- a/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.h +++ b/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.h @@ -18,13 +18,14 @@ #import "OCWaitCondition.h" #import "OCItemVersionIdentifier.h" +#import "OCLocation.h" NS_ASSUME_NONNULL_BEGIN @interface OCWaitConditionMetaDataRefresh : OCWaitCondition #pragma mark - Item path -@property(strong) OCPath itemPath; +@property(strong) OCLocation *itemLocation; #pragma mark - Metadata @property(strong,nullable) OCItemVersionIdentifier *itemVersionIdentifier; @@ -32,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Condition expiration @property(strong,nullable) NSDate *expirationDate; -+ (instancetype)waitForPath:(OCPath)path versionOtherThan:(OCItemVersionIdentifier *)itemVersionIdentifier until:(NSDate * _Nullable)expirationDate; ++ (instancetype)waitForLocation:(OCLocation *)location versionOtherThan:(OCItemVersionIdentifier *)itemVersionIdentifier until:(NSDate * _Nullable)expirationDate; @end diff --git a/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.m b/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.m index 9a8bcfc0..9d15a5e8 100644 --- a/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.m +++ b/ownCloudSDK/Wait Condition/OCWaitConditionMetaDataRefresh.m @@ -29,11 +29,11 @@ @interface OCWaitConditionMetaDataRefresh () @implementation OCWaitConditionMetaDataRefresh -+ (instancetype)waitForPath:(OCPath)path versionOtherThan:(OCItemVersionIdentifier *)itemVersionIdentifier until:(NSDate * _Nullable)expirationDate ++ (instancetype)waitForLocation:(OCLocation *)location versionOtherThan:(OCItemVersionIdentifier *)itemVersionIdentifier until:(NSDate * _Nullable)expirationDate { OCWaitConditionMetaDataRefresh *waitCondition = [OCWaitConditionMetaDataRefresh new]; - waitCondition.itemPath = path; + waitCondition.itemLocation = location; waitCondition.itemVersionIdentifier = itemVersionIdentifier; waitCondition.expirationDate = expirationDate; @@ -58,15 +58,15 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O NSError *error = nil; OCItem *cachedItem = nil; - if ((cachedItem = [core cachedItemAtPath:self.itemPath error:&error]) != nil) + if ((cachedItem = [core cachedItemAtLocation:self.itemLocation error:&error]) != nil) { // Get latest remote version cachedItem = (cachedItem.remoteItem != nil) ? cachedItem.remoteItem : cachedItem; // Check path - if ((cachedItem.path != nil) && ![self.itemPath isEqual:cachedItem.path]) + if ((cachedItem.path != nil) && ![self.itemLocation.path isEqual:cachedItem.path]) { - OCLogDebug(@"Metadata refresh wait condition found change of %@, now %@ - proceeding with sync record %@", self.itemPath, cachedItem.path, syncRecordID); + OCLogDebug(@"Metadata refresh wait condition found change of %@, now %@ - proceeding with sync record %@", self.itemLocation.path, cachedItem.path, syncRecordID); state = OCWaitConditionStateProceed; } @@ -77,7 +77,7 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O if (![cachedItem.itemVersionIdentifier isEqual:self.itemVersionIdentifier]) { // Item version identifier has changed - OCLogDebug(@"Metadata refresh wait condition found change of %@: %@ vs %@ - proceeding with sync record %@", self.itemPath, self.itemVersionIdentifier, cachedItem.itemVersionIdentifier, syncRecordID); + OCLogDebug(@"Metadata refresh wait condition found change of %@: %@ vs %@ - proceeding with sync record %@", self.itemLocation.path, self.itemVersionIdentifier, cachedItem.itemVersionIdentifier, syncRecordID); state = OCWaitConditionStateProceed; } @@ -86,7 +86,7 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O else { // Item is gone - OCLogDebug(@"Metadata refresh wait condition found %@ missing - proceeding with sync record %@", self.itemPath, syncRecordID); + OCLogDebug(@"Metadata refresh wait condition found %@ missing - proceeding with sync record %@", self.itemLocation.path, syncRecordID); state = OCWaitConditionStateProceed; } @@ -95,7 +95,7 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O // Check if the condition has expired if ((_expirationDate != nil) && ([_expirationDate timeIntervalSinceNow] < 0) && (state == OCWaitConditionStateWait)) { - OCLogDebug(@"Metadata refresh wait condition timed out for %@, sync record %@", self.itemPath, syncRecordID); + OCLogDebug(@"Metadata refresh wait condition timed out for %@, sync record %@", self.itemLocation.path, syncRecordID); state = OCWaitConditionStateProceed; } @@ -107,9 +107,9 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O __weak OCWaitConditionMetaDataRefresh *weakSelf = self; __weak OCCore *weakCore = core; __block BOOL didNotify = NO; - OCPath itemPath = self.itemPath; + OCLocation *itemLocation = self.itemLocation; - _itemTracker = [core trackItemAtPath:itemPath trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial) { + _itemTracker = [core trackItemAtLocation:itemLocation trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable item, BOOL isInitial) { OCWaitConditionMetaDataRefresh *strongSelf = weakSelf; BOOL doNotify = NO; @@ -121,19 +121,19 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O { didNotify = YES; - OCWTLogDebug(nil, @"Metadata refresh wait condition notified of removal of %@ - waking up sync record %@", weakSelf.itemPath, syncRecordID); + OCWTLogDebug(nil, @"Metadata refresh wait condition notified of removal of %@ - waking up sync record %@", weakSelf.itemLocation, syncRecordID); doNotify = YES; } } - if ((item.path != nil) && ![itemPath isEqual:item.path]) + if ((item.path != nil) && ![itemLocation.path isEqual:item.path]) { if (!didNotify) { didNotify = YES; - OCWTLogDebug(nil, @"Metadata refresh wait condition notified of move of %@ to %@ - waking up sync record %@", weakSelf.itemPath, item.path, syncRecordID); + OCWTLogDebug(nil, @"Metadata refresh wait condition notified of move of %@ to %@ - waking up sync record %@", weakSelf.itemLocation, item.path, syncRecordID); doNotify = YES; } @@ -145,7 +145,7 @@ - (void)evaluateWithOptions:(OCWaitConditionOptions)options completionHandler:(O { didNotify = YES; - OCWTLogDebug(nil, @"Metadata refresh wait condition notified of change of %@: %@ vs %@ - waking up sync record %@", weakSelf.itemPath, weakSelf.itemVersionIdentifier, item.itemVersionIdentifier, syncRecordID); + OCWTLogDebug(nil, @"Metadata refresh wait condition notified of change of %@: %@ vs %@ - waking up sync record %@", weakSelf.itemLocation, weakSelf.itemVersionIdentifier, item.itemVersionIdentifier, syncRecordID); doNotify = YES; } @@ -189,7 +189,7 @@ - (void)encodeWithCoder:(nonnull NSCoder *)coder { [super encodeWithCoder:coder]; - [coder encodeObject:_itemPath forKey:@"itemPath"]; + [coder encodeObject:_itemLocation forKey:@"itemLocation"]; [coder encodeObject:_expirationDate forKey:@"expirationDate"]; [coder encodeObject:_itemVersionIdentifier forKey:@"itemVersionIdentifier"]; } @@ -198,9 +198,19 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)decoder { if ((self = [super initWithCoder:decoder]) != nil) { - _itemPath = [decoder decodeObjectOfClass:[NSString class] forKey:@"itemPath"]; + _itemLocation = [decoder decodeObjectOfClass:OCLocation.class forKey:@"itemLocation"]; _expirationDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"expirationDate"]; _itemVersionIdentifier = [decoder decodeObjectOfClass:[OCItemVersionIdentifier class] forKey:@"itemVersionIdentifier"]; + + if (_itemLocation == nil) + { + NSString *itemPath; + + if ((itemPath = [decoder decodeObjectOfClass:NSString.class forKey:@"itemPath"]) != nil) + { + _itemLocation = [OCLocation legacyRootPath:itemPath]; + } + } } return (self); diff --git a/ownCloudSDK/ownCloudSDK.h b/ownCloudSDK/ownCloudSDK.h index 521bc4d4..174c776a 100644 --- a/ownCloudSDK/ownCloudSDK.h +++ b/ownCloudSDK/ownCloudSDK.h @@ -26,14 +26,20 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import +#import + #import #import #import +#import +#import + #import #import #import #import +#import #import #import @@ -46,6 +52,8 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import #import +#import +#import #import #import @@ -58,6 +66,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import #import +#import #import #import @@ -73,6 +82,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import #import #import #import @@ -100,6 +110,9 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import +#import + #import #import #import @@ -136,6 +149,7 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import #import #import #import @@ -151,6 +165,55 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import + +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import + #import #import #import @@ -158,12 +221,18 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import +#import #import #import +#import +#import +#import #import #import -#import +#import +#import #import #import @@ -185,6 +254,10 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import +#import +#import +#import + #import #import @@ -195,6 +268,9 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import #import +#import +#import +#import #import @@ -259,3 +335,13 @@ FOUNDATION_EXPORT const unsigned char ownCloudSDKVersionString[]; #import #import + +#import +#import +#import +#import +#import + +#import +#import +#import diff --git a/ownCloudSDKTests/AuthenticationTests.m b/ownCloudSDKTests/AuthenticationTests.m index 55a3f854..b54bacdb 100644 --- a/ownCloudSDKTests/AuthenticationTests.m +++ b/ownCloudSDKTests/AuthenticationTests.m @@ -455,7 +455,7 @@ - (void)testOpenIDConnect { connection.delegate = self; - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods) { + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { OCLog(@"preferredAuthenticationMethods: %@ supportedMethods: %@", preferredAuthenticationMethods, supportedMethods); if ([preferredAuthenticationMethods.firstObject isEqual:OCAuthenticationMethodIdentifierOpenIDConnect]) diff --git a/ownCloudSDKTests/ConnectionTests.m b/ownCloudSDKTests/ConnectionTests.m index 6feb6246..351438ce 100644 --- a/ownCloudSDKTests/ConnectionTests.m +++ b/ownCloudSDKTests/ConnectionTests.m @@ -79,7 +79,7 @@ - (void)_runPreparationTestsForURL:(NSURL *)url completionHandler:(void(^)(NSURL connection = [[OCConnection alloc] initWithBookmark:bookmark]; - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods) { + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { OCLog(@"Issues: %@", issue.issues); OCLog(@"SuggestedURL: %@", suggestedURL); OCLog(@"Supported authentication methods: %@ - Preferred authentication methods: %@", supportedMethods, preferredAuthenticationMethods); @@ -294,7 +294,7 @@ - (void)_testConnectWithUserEnteredURLString:(NSString *)userEnteredURLString us if ((connection = [[OCConnection alloc] initWithBookmark:bookmark]) != nil) { // Prepare for setup - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods) + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { // Check for warnings and errors NSArray *errorIssues = [issue issuesWithLevelGreaterThanOrEqualTo:OCIssueLevelError]; @@ -436,7 +436,7 @@ - (void)testConnectMinimumServerVersion if ((connection = [[OCConnection alloc] initWithBookmark:bookmark]) != nil) { - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods) + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { OCLog(@"Issue: %@", issue); @@ -489,8 +489,7 @@ - (void)testConnectWithFakedCert NSURL *fakeCertURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"fake-demo_owncloud_org" withExtension:@"cer"]; NSData *fakeCertData = [NSData dataWithContentsOfURL:fakeCertURL]; - connection.bookmark.certificate = [OCCertificate certificateWithCertificateData:fakeCertData hostName:@"demo.owncloud.org"]; - + [connection.bookmark.certificateStore storeCertificate:[OCCertificate certificateWithCertificateData:fakeCertData hostName:@"demo.owncloud.org"] forHostname:@"demo.owncloud.org"]; } connectAction:^(NSError *error, OCIssue *issue, OCConnection *connection) { // Testing just the connect here @@ -520,7 +519,7 @@ - (void)testConnectAndGetRootList { // connection.bookmark.url = [NSURL URLWithString:@"https://owncloud-io.lan/"]; - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at root: %@", items); XCTAssert((error==nil), @"No error"); @@ -559,7 +558,7 @@ - (void)testConnectAndGetThumbnails [OCEvent registerEventHandler:self forIdentifier:@"test"]; [self _testConnectWithUserEnteredURLString:@"http://admin:admin@demo.owncloud.org" useAuthMethod:OCAuthenticationMethodIdentifierBasicAuth preConnectAction:nil connectAction:^(NSError *error, OCIssue *issue, OCConnection *connection) { - OCLog(@"User: %@ Preview API: %d", connection.loggedInUser.userName, connection.supportsPreviewAPI); + OCLog(@"User: %@", connection.loggedInUser.userName); XCTAssert((error==nil), @"No error: %@", error); XCTAssert((issue==nil), @"No issue: %@", issue); @@ -569,7 +568,7 @@ - (void)testConnectAndGetThumbnails { // connection.bookmark.url = [NSURL URLWithString:@"https://owncloud-io.lan/"]; - [connection retrieveItemListAtPath:@"/Photos" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:[OCLocation legacyRootPath:@"/Photos"] depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at /Photos: %@", items); for (OCItem *item in items) @@ -658,7 +657,7 @@ - (void)testConnectAndDownloadFile if (error == nil) { - [connection retrieveItemListAtPath:@"/Photos" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:[OCLocation legacyRootPath:@"/Photos"] depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at /Photos: %@", items); OCItem *downloadItem = nil; @@ -731,7 +730,7 @@ - (void)testVanishedFileDownload if (error == nil) { - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at root: %@", items); XCTAssert((error==nil), @"No error"); @@ -799,7 +798,7 @@ - (void)testConnectAndBackgroundItemListRetrieval if (error == nil) { - [connection retrieveItemListAtPath:@"/Photos" depth:1 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { + [connection retrieveItemListAtLocation:[OCLocation legacyRootPath:@"/Photos"] depth:1 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { OCLog(@"Items at /Photos: %@, Error: %@, Path: %@, Depth: %ld", event.result, event.error, event.path, event.depth); XCTAssert(event.result!=nil); @@ -844,7 +843,7 @@ - (void)testConnectAndUploadFile if (error == nil) { - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at /: %@", items); OCItem *rootItem = nil; @@ -914,7 +913,7 @@ - (void)testConnectAndFavoriteFirstFile { // connection.bookmark.url = [NSURL URLWithString:@"https://owncloud-io.lan/"]; - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at root: %@", items); XCTAssert((error==nil), @"No error"); @@ -1066,6 +1065,30 @@ - (void)testCapabilitiesDecoding XCTAssert([capabilities.notificationEndpoints isEqualToArray:endpoints]); } +- (void)_testAppProviderEndpointRetrieval +{ + XCTestExpectation *expectConnect = [self expectationWithDescription:@"Connected"]; + OCConnection *connection = nil; + OCBookmark *bookmark = OCTestTarget.ocisBookmark; + + connection = [[OCConnection alloc] initWithBookmark:bookmark]; + + XCTAssert(connection!=nil); + + [connection connectWithCompletionHandler:^(NSError *error, OCIssue *issue) { + XCTAssert(error==nil); + XCTAssert(issue==nil); + + if (error == nil) + { + // Unimplemented for now due to unavailability of Basic Auth on public ocis instance + } + + [expectConnect fulfill]; + }]; + + [self waitForExpectationsWithTimeout:60 handler:nil]; +} - (void)_testPropFindZeroStresstest { @@ -1092,7 +1115,7 @@ - (void)_testPropFindZeroStresstest if (error == nil) { - [connection retrieveItemListAtPath:@"/" depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { OCItem *rootFolder = ((NSArray *)event.result).firstObject; for (NSUInteger i=0; i < scheduleCount; i++) @@ -1123,14 +1146,14 @@ - (void)_testPropFindZeroStresstest } } - [connection retrieveItemListAtPath:@"/" depth:0 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at /: %@, Error: %@", items, error); XCTAssert(items.count > 0); XCTAssert(items.firstObject.eTag != nil); }]; - [connection retrieveItemListAtPath:@"/" depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { OCLog(@"Item at /: %@, Error: %@, Path: %@, Depth: %ld", event.result, event.error, event.path, event.depth); XCTAssert(event.result!=nil); @@ -1144,7 +1167,7 @@ - (void)_testPropFindZeroStresstest } userInfo:nil ephermalUserInfo:nil]]; } userInfo:nil ephermalUserInfo:nil]]; - [connection retrieveItemListAtPath:@"/" depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent *event, id sender) { OCLog(@"Item at /: %@, Error: %@, Path: %@, Depth: %ld", event.result, event.error, event.path, event.depth); XCTAssert(event.result!=nil); @@ -1166,7 +1189,7 @@ - (void)_testPropFindZeroStresstest } } userInfo:nil ephermalUserInfo:nil]]; - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { OCLog(@"Items at /: %@, Error: %@", items, error); XCTAssert(items.count > 0); diff --git a/ownCloudSDKTests/CoreRedirectTests.m b/ownCloudSDKTests/CoreRedirectTests.m index c9b52475..ddb37579 100644 --- a/ownCloudSDKTests/CoreRedirectTests.m +++ b/ownCloudSDKTests/CoreRedirectTests.m @@ -65,8 +65,7 @@ - (void)retrieveCertificateForBookmark:(OCBookmark *)bookmark response:(void(^)( [connection connectWithCompletionHandler:^(NSError * _Nullable error, OCIssue * _Nullable issue) { [connection sendRequest:[OCHTTPRequest requestWithURL:[NSURL URLWithString:@"/status.php" relativeToURL:bookmark.url]] ephermalCompletionHandler:^(OCHTTPRequest * _Nonnull request, OCHTTPResponse * _Nullable response, NSError * _Nullable error) { - bookmark.certificate = request.httpResponse.certificate; - bookmark.certificateModificationDate = NSDate.date; + [bookmark.certificateStore storeCertificate:request.httpResponse.certificate forHostname:request.url.host]; [connection disconnectWithCompletionHandler:^{ certificateHandler(request.httpResponse.certificate); @@ -139,7 +138,7 @@ - (void)testConnectionSetupCookieRedirect connection.hostSimulator = self->hostSimulator; connection.cookieStorage = [OCHTTPCookieStorage new]; - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods) { + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue * _Nullable issue, NSURL * _Nullable suggestedURL, NSArray * _Nullable supportedMethods, NSArray * _Nullable preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { XCTAssert(supportedMethods.count > 0); XCTAssert(preferredAuthenticationMethods.count > 0); diff --git a/ownCloudSDKTests/CoreSharingTests.m b/ownCloudSDKTests/CoreSharingTests.m index 7526c4ca..3cd1e24e 100644 --- a/ownCloudSDKTests/CoreSharingTests.m +++ b/ownCloudSDKTests/CoreSharingTests.m @@ -31,7 +31,7 @@ - (void)testSearchController __block XCTestExpectation *expectReceiveSecondResults = [self expectationWithDescription:@"Receipt of second results"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; __block OCRecipientSearchController *searchController; __weak CoreSharingTests *weakSelf = self; @@ -163,7 +163,7 @@ - (void)testSharingQueriesCreateUpdateAndDelete __block XCTestExpectation *expectShareQuerySubItemsLosesNewShare = [self expectationWithDescription:@"shareQuerySubitems loses new share"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; OCShareQuery *shareQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; NSString *initialName = NSUUID.UUID.UUIDString, *updatedName= NSUUID.UUID.UUIDString; __block OCShare *createdShare = nil; @@ -345,7 +345,7 @@ - (void)testSharingQueriesCreateUpdateAndDelete [core startQuery:shareQuery]; - [core createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { + [core createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { OCLogDebug(@"Created share: %@, error: %@", newShare, error); XCTAssert(error == nil); @@ -394,7 +394,7 @@ - (void)testShareQueryDiffNoDifferences __block XCTestExpectation *expectInitialShareQueryResults = [self expectationWithDescription:@"Initial share query results"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; OCShareQuery *shareQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; NSString *initialName = NSUUID.UUID.UUIDString; __block OCShare *createdShare = nil; @@ -434,7 +434,7 @@ - (void)testShareQueryDiffNoDifferences if (shareItem != nil) { - [core createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { + [core createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { OCLogDebug(@"Created share: %@, error: %@", newShare, error); XCTAssert(error == nil); @@ -491,7 +491,7 @@ - (void)testShareQueryDiffCreatedNewShare __block XCTestExpectation *expectUpdatedShareQueryResults = [self expectationWithDescription:@"Updated share query results"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; OCShareQuery *shareQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; NSString *initialName = NSUUID.UUID.UUIDString, *secondName = NSUUID.UUID.UUIDString; __block OCShare *createdShare = nil; @@ -548,7 +548,7 @@ - (void)testShareQueryDiffCreatedNewShare if (shareItem != nil) { - [core createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { + [core createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { OCLogDebug(@"Created share: %@, error: %@", newShare, error); XCTAssert(error == nil); @@ -566,7 +566,7 @@ - (void)testShareQueryDiffCreatedNewShare [expectInitialShareQueryResults fulfill]; expectInitialShareQueryResults = nil; - [core.connection createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:secondName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + [core.connection createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:secondName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { createdSecondShare = (OCShare *)event.result; [core reloadQuery:query]; // Reload query so that the core gets to compare existing results with fresh results and include the second created one @@ -626,7 +626,7 @@ - (void)testShareQueryDiffUpdatedShare __block XCTestExpectation *expectUpdatedShareQueryResults = [self expectationWithDescription:@"Updated share query results"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; OCShareQuery *shareQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; NSString *initialName = NSUUID.UUID.UUIDString, *secondName = NSUUID.UUID.UUIDString; __block OCShare *createdShare = nil; @@ -666,7 +666,7 @@ - (void)testShareQueryDiffUpdatedShare if (shareItem != nil) { - [core createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { + [core createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { OCLogDebug(@"Created share: %@, error: %@", newShare, error); XCTAssert(error == nil); @@ -738,7 +738,7 @@ - (void)testShareQueryPollingUpdates __block XCTestExpectation *expectInitialPopulationHandlerCall = [self expectationWithDescription:@"Initial population handler called"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; OCShareQuery *shareQuery = [OCShareQuery queryWithScope:OCShareScopeSharedByUser item:nil]; NSString *initialName = NSUUID.UUID.UUIDString; __block OCShare *createdShare = nil; @@ -816,7 +816,7 @@ - (void)testShareQueryPollingUpdates XCTAssert(query.queryResults.count == 0); - [core.connection createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + [core.connection createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { createdShare = (OCShare *)event.result; OCLogDebug(@"Created share: %@, error: %@", createdShare, error); @@ -851,7 +851,7 @@ - (void)testFederatedAccept __block OCCore *core = nil; [remoteConnection connectWithCompletionHandler:^(NSError *error, OCIssue *issue) { - [remoteConnection createShare:[OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:[OCTestTarget.userLogin stringByAppendingFormat:@"@%@", OCTestTarget.userBookmark.url.host] displayName:nil]] path:@"/Photos/" permissions:OCSharePermissionsMaskRead expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + [remoteConnection createShare:[OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:[OCTestTarget.userLogin stringByAppendingFormat:@"@%@", OCTestTarget.userBookmark.url.host] displayName:nil]] location:[OCLocation legacyRootPath:@"/Photos/"] permissions:OCSharePermissionsMaskRead expiration:nil] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { XCTAssert(event.error == nil); XCTAssert(event.result != nil); @@ -958,7 +958,7 @@ - (void)testSharingItemUpdatesInQueries __block XCTestExpectation *expectLinkShareDisappear = [self expectationWithDescription:@"Link share disappeared"]; OCCore *core = [[OCCore alloc] initWithBookmark:OCTestTarget.userBookmark]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; NSString *initialName = NSUUID.UUID.UUIDString; __block OCItem *shareItem = nil; @@ -987,7 +987,7 @@ - (void)testSharingItemUpdatesInQueries XCTAssert(((shareItem.shareTypesMask & OCShareTypesMaskLink) == 0), @"Item %@ on test server may not already be shared by a link", item); } - [core createShare:[OCShare shareWithPublicLinkToPath:shareItem.path linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { + [core createShare:[OCShare shareWithPublicLinkToLocation:shareItem.location linkName:initialName permissions:OCSharePermissionsMaskRead password:nil expiration:nil] options:nil completionHandler:^(NSError * _Nullable error, OCShare * _Nullable newShare) { XCTAssert(error == nil); XCTAssert(newShare != nil); @@ -1065,7 +1065,7 @@ - (void)_testPrivateLinkCreationAndResolutionWithTestItemSelector:(void(^)(OCCon XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { [expectLists fulfill]; testItemSelector(connection, items, ^(OCItem *item) { @@ -1152,7 +1152,7 @@ - (void)testPrivateLinkCreationAndResolutionWithFileInSubfolder { if ((item.type == OCItemTypeCollection) && (item.privateLink == nil) && !item.isRoot) { - [connection retrieveItemListAtPath:item.path depth:1 completionHandler:^(NSError *error, NSArray *subItems) { + [connection retrieveItemListAtLocation:item.location depth:1 options:nil completionHandler:^(NSError *error, NSArray *subItems) { for (OCItem *item in subItems) { if ((item.type == OCItemTypeFile) && (item.privateLink == nil)) diff --git a/ownCloudSDKTests/CoreSyncTests.m b/ownCloudSDKTests/CoreSyncTests.m index ea1e7f89..cc021eb2 100644 --- a/ownCloudSDKTests/CoreSyncTests.m +++ b/ownCloudSDKTests/CoreSyncTests.m @@ -138,32 +138,32 @@ - (void)_testSyncAnchorIncreaseOnETagChange // TODO: Fix this test to rely on ev [core startQuery:syncAnchorQuery]; - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -179,7 +179,7 @@ - (void)_testSyncAnchorIncreaseOnETagChange // TODO: Fix this test to rely on ev XCTAssert([rowDictionary[@"syncAnchor"] isEqual:@(1)]); } completionHandler:^{ // Modify eTag in database to make the next retrieved update look like a change and prompt a syncAnchor increase - [core.vault.database retrieveCacheItemsAtPath:@"/" itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + [core.vault.database retrieveCacheItemsAtLocation:[OCLocation legacyRootLocation] itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { OCItem *item; XCTAssert(items.count == 1); // Check if itemOnly==YES works @@ -404,7 +404,7 @@ - (void)testSyncAnchorQueryUpdates [core startQuery:syncAnchorQuery]; - [core startQuery:[OCQuery queryForPath:@"/"]]; + [core startQuery:[OCQuery queryForLocation:OCLocation.legacyRootLocation]]; }]; [self waitForExpectationsWithTimeout:60 handler:nil]; @@ -454,32 +454,32 @@ - (void)testDelete OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -618,32 +618,32 @@ - (void)testCreateFolder OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -764,32 +764,32 @@ - (void)testCopy OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -827,32 +827,32 @@ - (void)testCopy [dirCreatedExpectation fulfill]; - newFolderQuery = [OCQuery queryForPath:newFolderItem.path]; + newFolderQuery = [OCQuery queryForLocation:newFolderItem.location]; newFolderQuery.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] NEW QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] NEW QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] NEW Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] NEW Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1054,32 +1054,32 @@ - (void)testMove OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1118,32 +1118,32 @@ - (void)testMove localIDTargetFolderInitial = item.localID; - newFolderQuery = [OCQuery queryForPath:item.path]; + newFolderQuery = [OCQuery queryForLocation:item.location]; newFolderQuery.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] NEW QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] NEW QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] NEW Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] NEW Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1407,32 +1407,32 @@ - (void)testRenameStressTest OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/Photos/"]; + query = [OCQuery queryForLocation:[OCLocation legacyRootPath:@"/Photos/"]]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1569,32 +1569,32 @@ - (void)testDownload OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1731,32 +1731,32 @@ - (void)testUpload OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -1893,7 +1893,7 @@ - (void)testItemUpdates OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (query.state == OCQueryStateIdle) @@ -1924,7 +1924,7 @@ - (void)testItemUpdates localIDAfterFirstUpdate = item.localID; - [core.connection retrieveItemListAtPath:item.path depth:0 completionHandler:^(NSError *error, NSArray *items) { + [core.connection retrieveItemListAtLocation:item.location depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { XCTAssert(items.count == 1); XCTAssert(items.firstObject.isFavorite.boolValue); @@ -1944,7 +1944,7 @@ - (void)testItemUpdates [favoriteUnsetExpectation fulfill]; - [core.connection retrieveItemListAtPath:item.path depth:0 completionHandler:^(NSError *error, NSArray *items) { + [core.connection retrieveItemListAtLocation:item.location depth:0 options:nil completionHandler:^(NSError *error, NSArray *items) { XCTAssert(items.count == 1); XCTAssert(!items.firstObject.isFavorite.boolValue); diff --git a/ownCloudSDKTests/CoreTests.m b/ownCloudSDKTests/CoreTests.m index 423e3296..91c2caa9 100644 --- a/ownCloudSDKTests/CoreTests.m +++ b/ownCloudSDKTests/CoreTests.m @@ -106,33 +106,33 @@ - (void)testSimpleQuery OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -172,33 +172,33 @@ - (void)testSimpleQuery } } - subfolderQuery = [OCQuery queryForPath:subfolderPath]; + subfolderQuery = [OCQuery queryForLocation:[OCLocation legacyRootPath:subfolderPath]]; subfolderQuery.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -284,7 +284,7 @@ - (void)testSimpleQueryWithSimulatedCacheUpdate OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if ((query.state == OCQueryStateWaitingForServerReply) && !didDisruptOnce) @@ -303,27 +303,27 @@ - (void)testSimpleQueryWithSimulatedCacheUpdate if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -333,43 +333,43 @@ - (void)testSimpleQueryWithSimulatedCacheUpdate { if (subfolderQuery==nil) { - OCPath subfolderPath = nil; + OCLocation *subfolderLocation = nil; for (OCItem *item in query.queryResults) { if (item.type == OCItemTypeCollection) { - subfolderPath = item.path; + subfolderLocation = item.location; } } - subfolderQuery = [OCQuery queryForPath:subfolderPath]; + subfolderQuery = [OCQuery queryForLocation:subfolderLocation]; subfolderQuery.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -446,33 +446,33 @@ - (void)testOfflineCaching OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/"]; + query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -482,43 +482,43 @@ - (void)testOfflineCaching { if (subfolderQuery == nil) { - OCPath subfolderPath = nil; + OCLocation *subfolderLocation = nil; for (OCItem *item in query.queryResults) { if (item.type == OCItemTypeCollection) { - subfolderPath = item.path; + subfolderLocation = item.location; } } - subfolderQuery = [OCQuery queryForPath:subfolderPath]; + subfolderQuery = [OCQuery queryForLocation:subfolderLocation]; subfolderQuery.changesAvailableNotificationHandler = ^(OCQuery *query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { OCLog(@"============================================"); - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); [changeset enumerateChangesUsingBlock:^(OCQueryChangeSet *changeSet, OCQueryChangeSetOperation operation, NSArray *items, NSIndexSet *indexSet) { switch(operation) { case OCQueryChangeSetOperationInsert: - OCLog(@"[%@] Insertions: %@", query.queryPath, items); + OCLog(@"[%@] Insertions: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationRemove: - OCLog(@"[%@] Removals: %@", query.queryPath, items); + OCLog(@"[%@] Removals: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationUpdate: - OCLog(@"[%@] Updates: %@", query.queryPath, items); + OCLog(@"[%@] Updates: %@", query.queryLocation, items); break; case OCQueryChangeSetOperationContentSwap: - OCLog(@"[%@] Content Swap", query.queryPath); + OCLog(@"[%@] Content Swap", query.queryLocation); break; } }]; @@ -534,7 +534,7 @@ - (void)testOfflineCaching { [idlePaths addObject:item.path]; - XCTAssert(![item.path isEqualToString:query.queryPath], @"root item not contained in live results: %@", item); + XCTAssert(![item.location isEqual:query.queryLocation], @"root item not contained in live results: %@", item); } dispatch_async(dispatch_get_main_queue(), ^{ @@ -554,7 +554,7 @@ - (void)testOfflineCaching { [fromCachePaths addObject:item.path]; - XCTAssert(![item.path isEqualToString:query.queryPath], @"root item not contained in cached results: %@", item); + XCTAssert(![item.location isEqual:query.queryLocation], @"root item not contained in cached results: %@", item); } XCTAssert((fromCachePaths.count==idlePaths.count), @"Same number of cached and idle paths"); @@ -591,6 +591,7 @@ - (void)testOfflineCaching }]; } +/* - (void)testThumbnailRetrieval { OCBookmark *bookmark = nil; @@ -630,17 +631,17 @@ - (void)testThumbnailRetrieval OCLog(@"Vault location: %@", core.vault.rootURL); - query = [OCQuery queryForPath:@"/Photos/"]; + query = [OCQuery queryForLocation:[OCLocation legacyRootPath:@"/Photos/"]]; query.changesAvailableNotificationHandler = ^(OCQuery *query) { - OCLog(@"[%@] QUERY STATE: %lu", query.queryPath, (unsigned long)query.state); + OCLog(@"[%@] QUERY STATE: %lu", query.queryLocation, (unsigned long)query.state); if (query.state == OCQueryStateIdle) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagDefault completionHandler:^(OCQuery *query, OCQueryChangeSet *changeset) { if (changeset != nil) { - OCLog(@"[%@] Query result: %@", query.queryPath, changeset.queryResult); + OCLog(@"[%@] Query result: %@", query.queryLocation, changeset.queryResult); for (OCItem *item in changeset.queryResult) { @@ -749,6 +750,7 @@ - (void)testThumbnailRetrieval XCTAssert((error==nil), @"Erased with error: %@", error); }]; } +*/ - (void)core:(OCCore *)core handleError:(NSError *)error issue:(OCIssue *)issue { @@ -820,8 +822,8 @@ - (void)testInvalidLoginData - (void)testOverlappingQueries { OCBookmark *bookmark = [OCTestTarget userBookmark]; - OCQuery *queryOne = [OCQuery queryForPath:@"/"]; - OCQuery *queryTwo = [OCQuery queryForPath:@"/"]; + OCQuery *queryOne = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; + OCQuery *queryTwo = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; __block XCTestExpectation *initialPopulationReceivedExpectation = [self expectationWithDescription:@"Initial database population"]; __block XCTestExpectation *coreReturnedExpectation = [self expectationWithDescription:@"Core returned"]; __block XCTestExpectation *queryOneItemsReceivedExpectation = [self expectationWithDescription:@"Query 1 idle"]; @@ -840,7 +842,7 @@ - (void)testOverlappingQueries __block NSTimeInterval queryTwoTimestampIdle = 0; [[OCCoreManager sharedCoreManager] requestCoreForBookmark:bookmark setup:nil completionHandler:^(OCCore * _Nullable core, NSError * _Nullable error) { - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; __weak OCCore *weakCore = core; core.vault.database.itemFilter = self.databaseSanityCheckFilter; @@ -940,7 +942,7 @@ - (void)testQueryFilter __block OCItem *onlyItem = nil; [[OCCoreManager sharedCoreManager] requestCoreForBookmark:bookmark setup:nil completionHandler:^(OCCore * _Nullable core, NSError * _Nullable error) { - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; core.vault.database.itemFilter = self.databaseSanityCheckFilter; core.automaticItemListUpdatesEnabled = NO; @@ -1040,14 +1042,14 @@ - (void)testItemTracking OCLog(@"Vault location: %@", core.vault.rootURL); - itemTracker = [core trackItemAtPath:trackPath trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTracker = [core trackItemAtLocation:[OCLocation legacyRootPath:trackPath] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked: isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) { [initialTrackingResponseFromServerExpectation fulfill]; - itemTrackerFromCache = [core trackItemAtPath:trackPath trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable cachedItem, BOOL isInitial) { + itemTrackerFromCache = [core trackItemAtLocation:[OCLocation legacyRootPath:trackPath] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable cachedItem, BOOL isInitial) { OCLog(@"Tracked from cache: isInitial=%d error=%@ item=%@", isInitial, error, cachedItem); XCTAssert([cachedItem.localID isEqual:serverItem.localID]); @@ -1111,7 +1113,7 @@ - (void)testItemTrackingNonExistant dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(58 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ }); - itemTrackerNonExistantItem = [core trackItemAtPath:@"/does.not.exist" trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTrackerNonExistantItem = [core trackItemAtLocation:[OCLocation legacyRootPath:@"/does.not.exist"] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked(NE): isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) @@ -1169,7 +1171,7 @@ - (void)testItemTrackingDeepNonExistant dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(58 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ }); - itemTrackerNonExistantItem = [core trackItemAtPath:@"/does.not.exist/either/" trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTrackerNonExistantItem = [core trackItemAtLocation:[OCLocation legacyRootPath:@"/does.not.exist/either/"] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked(NE): isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) @@ -1227,7 +1229,7 @@ - (void)testItemTrackingUnnormalizedPathError dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(58 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ }); - itemTrackerNonExistantItem = [core trackItemAtPath:@"//Photos" trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTrackerNonExistantItem = [core trackItemAtLocation:[OCLocation legacyRootPath:@"//Photos"] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked(NE): isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) @@ -1288,7 +1290,7 @@ - (void)testItemTrackingPathWithFormattingError dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(58 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ }); - itemTrackerInitial = [core trackItemAtPath:@"/Documents" trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTrackerInitial = [core trackItemAtLocation:[OCLocation legacyRootPath:@"/Documents"] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked(1): isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) @@ -1307,7 +1309,7 @@ - (void)testItemTrackingPathWithFormattingError [itemReturnedFromServerExpectation fulfill]; itemReturnedFromServerExpectation = nil; - itemTrackerSecondary = [core trackItemAtPath:@"/Documents" trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTrackerSecondary = [core trackItemAtLocation:[OCLocation legacyRootPath:@"/Documents"] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked(2): isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (serverItem != nil) @@ -1373,7 +1375,7 @@ - (void)testFavoriteRefresh OCLog(@"Vault location: %@", core.vault.rootURL); - itemTracker = [core trackItemAtPath:trackPath trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { + itemTracker = [core trackItemAtLocation:[OCLocation legacyRootPath:trackPath] trackingHandler:^(NSError * _Nullable error, OCItem * _Nullable serverItem, BOOL isInitial) { OCLog(@"Tracked: isInitial=%d error=%@ item=%@", isInitial, error, serverItem); if (isInitial) @@ -1532,7 +1534,7 @@ - (void)testDuplicateNameSuggestions // - style: copy dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atPath:@"/" isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleCopy filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { + [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atLocation:OCLocation.legacyRootLocation isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleCopy filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"ownCloud Manual copy.pdf"]); XCTAssert(rejectedAndTakenNames.count == 1); @@ -1541,7 +1543,7 @@ - (void)testDuplicateNameSuggestions // - style: bracketed dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atPath:@"/" isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleBracketed filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { + [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atLocation:OCLocation.legacyRootLocation isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleBracketed filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"ownCloud Manual (1).pdf"]); XCTAssert(rejectedAndTakenNames.count == 1); @@ -1550,7 +1552,7 @@ - (void)testDuplicateNameSuggestions // - style: copy + filter first suggestion dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atPath:@"/" isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleCopy filteredBy:^BOOL(NSString * _Nonnull suggestedName) { + [core suggestUnusedNameBasedOn:@"ownCloud Manual.pdf" atLocation:OCLocation.legacyRootLocation isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleCopy filteredBy:^BOOL(NSString * _Nonnull suggestedName) { return ![suggestedName isEqual:@"ownCloud Manual copy.pdf"]; } resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"ownCloud Manual copy 2.pdf"]); @@ -1561,7 +1563,7 @@ - (void)testDuplicateNameSuggestions // - style: unused dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"Unused.pdf" atPath:@"/" isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleBracketed filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { + [core suggestUnusedNameBasedOn:@"Unused.pdf" atLocation:OCLocation.legacyRootLocation isDirectory:NO usingNameStyle:OCCoreDuplicateNameStyleBracketed filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"Unused.pdf"]); XCTAssert(rejectedAndTakenNames.count == 0); @@ -1570,7 +1572,7 @@ - (void)testDuplicateNameSuggestions // - style: directory dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"Photos" atPath:@"/" isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { + [core suggestUnusedNameBasedOn:@"Photos" atLocation:OCLocation.legacyRootLocation isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:nil resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"Photos 2"]); XCTAssert(rejectedAndTakenNames.count == 1); @@ -1579,7 +1581,7 @@ - (void)testDuplicateNameSuggestions // - style: directory 0 dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"Photos 0" atPath:@"/" isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { + [core suggestUnusedNameBasedOn:@"Photos 0" atLocation:OCLocation.legacyRootLocation isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { return (![suggestedName isEqual:@"Photos 0"]); } resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"Photos 1"]); @@ -1590,7 +1592,7 @@ - (void)testDuplicateNameSuggestions // - style: directory 1 dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"Photos 1" atPath:@"/" isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { + [core suggestUnusedNameBasedOn:@"Photos 1" atLocation:OCLocation.legacyRootLocation isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { return (![suggestedName isEqual:@"Photos 1"]); } resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"Photos 2"]); @@ -1601,7 +1603,7 @@ - (void)testDuplicateNameSuggestions // - style: directory (1) - usage of different style dispatch_group_enter(suggestionWaitGroups); - [core suggestUnusedNameBasedOn:@"Photos (1)" atPath:@"/" isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { + [core suggestUnusedNameBasedOn:@"Photos (1)" atLocation:OCLocation.legacyRootLocation isDirectory:YES usingNameStyle:OCCoreDuplicateNameStyleNumbered filteredBy:^BOOL(NSString * _Nonnull suggestedName) { return (![suggestedName isEqual:@"Photos (1)"]); } resultHandler:^(NSString * _Nullable suggestedName, NSArray * _Nullable rejectedAndTakenNames) { XCTAssert([suggestedName isEqual:@"Photos (2)"]); @@ -1643,7 +1645,7 @@ - (void)testDirectURL [core startWithCompletionHandler:^(OCCore *core, NSError *error) { [coreStartedExpectation fulfill]; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { if ((query.state == OCQueryStateIdle) && (queryCompletionExpectation != nil)) @@ -1741,7 +1743,7 @@ - (void)testBookmarkItemResolution [OCBookmarkManager.sharedBookmarkManager addBookmark:bookmark]; [OCCoreManager.sharedCoreManager requestCoreForBookmark:bookmark setup:nil completionHandler:^(OCCore * _Nullable core, NSError * _Nullable error) { - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { [query requestChangeSetWithFlags:OCQueryChangeSetRequestFlagOnlyResults completionHandler:^(OCQuery * _Nonnull query, OCQueryChangeSet * _Nullable changeset) { diff --git a/ownCloudSDKTests/DataSourceTests.m b/ownCloudSDKTests/DataSourceTests.m new file mode 100644 index 00000000..575f0658 --- /dev/null +++ b/ownCloudSDKTests/DataSourceTests.m @@ -0,0 +1,341 @@ +// +// DataSourceTests.m +// ownCloudSDKTests +// +// Created by Felix Schwarz on 20.03.22. +// Copyright © 2022 ownCloud GmbH. All rights reserved. +// + +#import +#import + +OCDataItemType DataTypeA = @"A"; +OCDataItemType DataTypeB = @"B"; +OCDataItemType DataTypeC = @"C"; +OCDataItemType DataTypeD = @"D"; +OCDataItemType DataTypeE = @"E"; + +@interface TypeA2BConverter : OCDataConverter +@end + +@interface TypeB2CConverter : OCDataConverter +@end + +@interface TypeC2DConverter : OCDataConverter +@end + +@interface TypeD2EConverter : OCDataConverter +@end + +@interface TypeB2DConverter : OCDataConverter +@end + +@implementation TypeA2BConverter +-(OCDataItemType)inputType { return (DataTypeA); } +-(OCDataItemType)outputType { return (DataTypeB); } +@end + +@implementation TypeB2CConverter +-(OCDataItemType)inputType { return (DataTypeB); } +-(OCDataItemType)outputType { return (DataTypeC); } +@end + +@implementation TypeC2DConverter +-(OCDataItemType)inputType { return (DataTypeC); } +-(OCDataItemType)outputType { return (DataTypeD); } +@end + +@implementation TypeD2EConverter +-(OCDataItemType)inputType { return (DataTypeD); } +-(OCDataItemType)outputType { return (DataTypeE); } +@end + +@implementation TypeB2DConverter +-(OCDataItemType)inputType { return (DataTypeB); } +-(OCDataItemType)outputType { return (DataTypeD); } +@end + +@interface DataSourceTests : XCTestCase + +@end + +@implementation DataSourceTests + +- (void)testDataSourceInitialSnapshotAndBasicMutations +{ + OCDataSource *source = [OCDataSource new]; + OCDataSourceSnapshot *snapshot; + + OCDataSourceSubscription *subscription = [source subscribeWithUpdateHandler:^(OCDataSourceSubscription * _Nonnull subscription) { + OCLog(@"Subscription notified of update"); + } onQueue:nil trackDifferences:YES performInitialUpdate:NO]; + + // Take snapshot + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Add "a" + [source setItemReferences:@[@"a"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithArray:@[ @"a" ]]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Add "b" + [source setItemReferences:@[@"a",@"b"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a",@"b" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithArray:@[ @"b" ]]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Remove "b" + [source setItemReferences:@[@"a"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet setWithArray:@[ @"b" ]]]) ); + + // Update "a" + [source setItemReferences:@[@"a"] updated:[NSSet setWithArray:@[ @"a" ]]]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet setWithArray:@[ @"a" ]]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); +} + +- (void)testDataSourceInitialSnapshotAndTrickyMutations +{ + OCDataSource *source = [OCDataSource new]; + OCDataSourceSnapshot *snapshot; + + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + + OCDataSourceSubscription *subscription = [source subscribeWithUpdateHandler:^(OCDataSourceSubscription * _Nonnull subscription) { + OCLog(@"Subscription notified of update"); + } onQueue:nil trackDifferences:YES performInitialUpdate:NO]; + + // Take snapshot, check if contents is in it + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Add "d", update "b" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:[NSSet setWithArray:@[ @"b" ]]]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithArray:@[ @"d" ] ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet setWithArray:@[ @"b" ] ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // update "a", update "c" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:[NSSet setWithArray:@[ @"a", @"c" ]]]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet setWithArray:@[ @"a", @"c" ] ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Remove "d" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet setWithArray:@[ @"d" ] ]]) ); + + // Remove "c" + [source setItemReferences:@[@"a", @"b"] updated:nil]; + // Add "c" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet setWithArray:@[@"c"] ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set ]]) ); + + // Add "d" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:nil]; + // Remove "d" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + // Add "d" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithArray:@[@"d"] ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set ]]) ); + + // Remove "d" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + // Add "d" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:nil]; + // Remove "d" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet setWithArray:@[@"d"] ]]) ); + + // Update "d" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:[NSSet setWithArray:@[ @"d" ]]]; + // Remove "d" + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set ]]) ); + + // Add "d" + remove "c" + [source setItemReferences:@[@"a", @"b", @"d"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithArray:@[@"d"] ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet setWithArray:@[@"c"] ]]) ); + + // Add + Update "c" + [source setItemReferences:@[@"a", @"b", @"c", @"d"] updated:[NSSet setWithArray:@[ @"c" ]]]; + // Remove "c" + [source setItemReferences:@[@"a", @"b", @"d"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set ]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set ]]) ); + + // Update non-existant "f" + [source setItemReferences:@[@"a", @"b", @"d"] updated:[NSSet setWithArray:@[ @"f" ]]]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Add "c", remove "b" and update "d" and "a" + [source setItemReferences:@[@"a", @"c", @"d"] updated:[NSSet setWithArray:@[ @"d", @"a" ]]]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"c", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet setWithObject:@"c"]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet setWithObjects:@"a", @"d", nil]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet setWithObject:@"b"]]) ); + + // Set existing items + [source setItemReferences:@[@"a", @"c", @"d"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"c", @"d" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); +} + +- (void)testDataSourceInitialSnapshotAndOrderMutations +{ + OCDataSource *source = [OCDataSource new]; + OCDataSourceSnapshot *snapshot; + + [source setItemReferences:@[@"a", @"b", @"c"] updated:nil]; + + OCDataSourceSubscription *subscription = [source subscribeWithUpdateHandler:^(OCDataSourceSubscription * _Nonnull subscription) { + OCLog(@"Subscription notified of update"); + } onQueue:nil trackDifferences:YES performInitialUpdate:NO]; + + // Take snapshot, check if contents is in it + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"b", @"c" ] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); + + // Change order, check if any changes are reported (there shouldn't be) + [source setItemReferences:@[@"a", @"c", @"b"] updated:nil]; + snapshot = [subscription snapshotResettingChangeTracking:YES]; + XCTAssert( ([snapshot.items isEqual:@[ @"a", @"c", @"b"] ]) ); + XCTAssert( ([snapshot.addedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.updatedItems isEqual:[NSSet set]]) ); + XCTAssert( ([snapshot.removedItems isEqual:[NSSet set]]) ); +} + +- (void)testDataConverterAssembly +{ + OCDataRenderer *renderer = [[OCDataRenderer alloc] initWithConverters:@[ + [[TypeA2BConverter alloc] init], + [[TypeB2CConverter alloc] init], + [[TypeC2DConverter alloc] init], + [[TypeD2EConverter alloc] init], + ]]; + + // Assemble viable pipeline + + OCDataConverterPipeline *pipelineAToE; + + pipelineAToE = (OCDataConverterPipeline *)[renderer assembledConverterFrom:DataTypeA to:DataTypeE]; + + XCTAssert(pipelineAToE != nil); + XCTAssert(pipelineAToE.converters.count == 4); + XCTAssert([pipelineAToE.converters[0] isKindOfClass:TypeA2BConverter.class]); + XCTAssert([pipelineAToE.converters[1] isKindOfClass:TypeB2CConverter.class]); + XCTAssert([pipelineAToE.converters[2] isKindOfClass:TypeC2DConverter.class]); + XCTAssert([pipelineAToE.converters[3] isKindOfClass:TypeD2EConverter.class]); + + OCLogDebug(@"Assembled pipeline from A to E: %@", pipelineAToE.converters); + + // Try to assemble unviable pipeline + + OCDataConverterPipeline *pipelineEToA; + + pipelineEToA = (OCDataConverterPipeline *)[renderer assembledConverterFrom:DataTypeE to:DataTypeA]; + + XCTAssert(pipelineEToA == nil); +} + +- (void)testDataConverterAssemblyShortestRoute +{ + OCDataRenderer *renderer = [[OCDataRenderer alloc] initWithConverters:@[ + // 4-step route + [[TypeA2BConverter alloc] init], + [[TypeB2CConverter alloc] init], + [[TypeC2DConverter alloc] init], + [[TypeD2EConverter alloc] init], + + // Shortcut to allow 3-step route + [[TypeB2DConverter alloc] init], + ]]; + + // Assemble 3-4 step pipeline + OCDataConverterPipeline *pipelineAToE; + + pipelineAToE = (OCDataConverterPipeline *)[renderer assembledConverterFrom:DataTypeA to:DataTypeE]; + + XCTAssert(pipelineAToE != nil); + XCTAssert(pipelineAToE.converters.count == 3); + XCTAssert([pipelineAToE.converters[0] isKindOfClass:TypeA2BConverter.class]); + XCTAssert([pipelineAToE.converters[1] isKindOfClass:TypeB2DConverter.class]); + XCTAssert([pipelineAToE.converters[2] isKindOfClass:TypeD2EConverter.class]); + + OCLogDebug(@"Assembled pipeline from A to E: %@", pipelineAToE.converters); + + // Assemble 2-3 step pipeline + OCDataConverterPipeline *pipelineAToD; + + pipelineAToD = (OCDataConverterPipeline *)[renderer assembledConverterFrom:DataTypeA to:DataTypeD]; + + XCTAssert(pipelineAToD != nil); + XCTAssert(pipelineAToD.converters.count == 2); + XCTAssert([pipelineAToD.converters[0] isKindOfClass:TypeA2BConverter.class]); + XCTAssert([pipelineAToD.converters[1] isKindOfClass:TypeB2DConverter.class]); + + OCLogDebug(@"Assembled pipeline from A to E: %@", pipelineAToD.converters); +} + +@end diff --git a/ownCloudSDKTests/HostSimulatorTests.m b/ownCloudSDKTests/HostSimulatorTests.m index 2cb138ee..8f49429a 100644 --- a/ownCloudSDKTests/HostSimulatorTests.m +++ b/ownCloudSDKTests/HostSimulatorTests.m @@ -53,7 +53,7 @@ - (void)_runPreparationTestsForURL:(NSURL *)url completionHandler:(void(^)(NSURL connection = [[OCConnection alloc] initWithBookmark:bookmark]; connection.hostSimulator = hostSimulator; - [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods) { + [connection prepareForSetupWithOptions:nil completionHandler:^(OCIssue *issue, NSURL *suggestedURL, NSArray *supportedMethods, NSArray *preferredAuthenticationMethods, OCAuthenticationMethodBookmarkAuthenticationDataGenerationOptions _Nullable generationOptions) { OCLog(@"Issues: %@", issue.issues); OCLog(@"SuggestedURL: %@", suggestedURL); OCLog(@"Supported authentication methods: %@ - Preferred authentication methods: %@", supportedMethods, preferredAuthenticationMethods); diff --git a/ownCloudSDKTests/ItemPolicyTests.m b/ownCloudSDKTests/ItemPolicyTests.m index 6582533f..b6a8d4e9 100644 --- a/ownCloudSDKTests/ItemPolicyTests.m +++ b/ownCloudSDKTests/ItemPolicyTests.m @@ -53,7 +53,7 @@ - (void)_runTestWithBookmark:(OCBookmark *)bookmark implementation:(void(^)(OCCo }]; }; - OCQuery *query = [OCQuery queryForPath:@"/"]; + OCQuery *query = [OCQuery queryForLocation:OCLocation.legacyRootLocation]; query.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { if (query.state == OCQueryStateIdle) @@ -122,7 +122,7 @@ - (void)testAvailableOffline { folderItem = item; - folderQuery = [OCQuery queryForPath:folderItem.path]; + folderQuery = [OCQuery queryForLocation:folderItem.location]; folderQuery.changesAvailableNotificationHandler = ^(OCQuery * _Nonnull query) { if (query.state == OCQueryStateIdle) diff --git a/ownCloudSDKTests/MiscTests.m b/ownCloudSDKTests/MiscTests.m index 85900ddf..343c5533 100644 --- a/ownCloudSDKTests/MiscTests.m +++ b/ownCloudSDKTests/MiscTests.m @@ -257,7 +257,6 @@ - (void)testUserSerialization user.displayName = @"Display Name"; user.userName = @"userName"; user.emailAddress = @"em@il.address"; - user.avatarData = [NSData data]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -274,7 +273,6 @@ - (void)testUserSerialization XCTAssert([deserializedUser.displayName isEqual:user.displayName]); XCTAssert([deserializedUser.userName isEqual:user.userName]); XCTAssert([deserializedUser.emailAddress isEqual:user.emailAddress]); - XCTAssert([deserializedUser.avatarData isEqual:user.avatarData]); } #pragma mark - OCHTTPStatus diff --git a/ownCloudSDKTests/OCTestTarget.h b/ownCloudSDKTests/OCTestTarget.h index 3815f423..e0b84e69 100644 --- a/ownCloudSDKTests/OCTestTarget.h +++ b/ownCloudSDKTests/OCTestTarget.h @@ -19,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN @property(strong,readonly,nonnull,class) NSURL *federatedTargetURL; +@property(strong,readonly,nonnull,class) NSURL *ocisTargetURL; + @property(strong,readonly,nonnull,class) NSString *adminLogin; @property(strong,readonly,nonnull,class) NSString *adminPassword; @@ -41,6 +43,8 @@ NS_ASSUME_NONNULL_BEGIN + (OCBookmark *)federatedBookmark; ++ (OCBookmark *)ocisBookmark; + @end NS_ASSUME_NONNULL_END diff --git a/ownCloudSDKTests/OCTestTarget.m b/ownCloudSDKTests/OCTestTarget.m index 7c30fca8..ca0d84af 100644 --- a/ownCloudSDKTests/OCTestTarget.m +++ b/ownCloudSDKTests/OCTestTarget.m @@ -26,6 +26,11 @@ + (NSURL *)federatedTargetURL return ([NSURL URLWithString:@"https://demo.owncloud.com/"]); } ++ (NSURL *)ocisTargetURL +{ + return ([NSURL URLWithString:@"https://ocis.ocis-wopi.latest.owncloud.works/"]); +} + + (NSString *)adminLogin { return (@"admin"); @@ -106,4 +111,9 @@ + (OCBookmark *)federatedBookmark return ([self bookmarkWithURL:OCTestTarget.federatedTargetURL username:OCTestTarget.federatedLogin passphrase:OCTestTarget.federatedPassword]); } ++ (OCBookmark *)ocisBookmark +{ + return ([self bookmarkWithURL:OCTestTarget.ocisTargetURL username:OCTestTarget.adminLogin passphrase:OCTestTarget.adminPassword]); +} + @end diff --git a/ownCloudSDKTests/SharingTests.m b/ownCloudSDKTests/SharingTests.m index 7db11a2a..c29be6a0 100644 --- a/ownCloudSDKTests/SharingTests.m +++ b/ownCloudSDKTests/SharingTests.m @@ -35,8 +35,8 @@ - (void)testPublicShareCreationAndRetrieval XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { - OCShare *createShare = [OCShare shareWithPublicLinkToPath:items[1].path linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { + OCShare *createShare = [OCShare shareWithPublicLinkToLocation:items[1].location linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; [expectList fulfill]; @@ -50,7 +50,7 @@ - (void)testPublicShareCreationAndRetrieval XCTAssert([createShare.name isEqual:newShare.name]); XCTAssert(createShare.permissions == newShare.permissions); - XCTAssert([createShare.itemPath isEqual:newShare.itemPath]); + XCTAssert([createShare.itemLocation isEqual:newShare.itemLocation]); XCTAssert(newShare.url != nil); XCTAssert(newShare.token != nil); @@ -78,7 +78,7 @@ - (void)testPublicShareCreationAndRetrieval XCTAssert([share.token isEqual:newShare.token]); XCTAssert([share.creationDate isEqual:newShare.creationDate]); XCTAssert([share.expirationDate isEqual:newShare.expirationDate]); - XCTAssert([share.itemPath isEqual:newShare.itemPath]); + XCTAssert([share.itemLocation isEqual:newShare.itemLocation]); [expectSingleShareRetrieved fulfill]; @@ -106,7 +106,7 @@ - (void)testPublicShareCreationAndRetrieval XCTAssert([share.token isEqual:newShare.token]); XCTAssert([share.creationDate isEqual:newShare.creationDate]); XCTAssert([share.expirationDate isEqual:newShare.expirationDate]); - XCTAssert([share.itemPath isEqual:newShare.itemPath]); + XCTAssert([share.itemLocation isEqual:newShare.itemLocation]); } } @@ -153,11 +153,11 @@ - (void)testPublicShareCreationAndUpdate XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { - OCShare *passwordLessShare = [OCShare shareWithPublicLinkToPath:items[1].path linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:nil expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { + OCShare *passwordLessShare = [OCShare shareWithPublicLinkToLocation:items[1].location linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:nil expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; XCTAssert(!passwordLessShare.protectedByPassword); - OCShare *createShare = [OCShare shareWithPublicLinkToPath:items[1].path linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; + OCShare *createShare = [OCShare shareWithPublicLinkToLocation:items[1].location linkName:@"iOS SDK CI Share" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]]; XCTAssert(createShare.protectedByPassword); [expectList fulfill]; @@ -172,7 +172,7 @@ - (void)testPublicShareCreationAndUpdate XCTAssert([createShare.name isEqual:newShare.name]); XCTAssert(createShare.permissions == newShare.permissions); - XCTAssert([createShare.itemPath isEqual:newShare.itemPath]); + XCTAssert([createShare.itemLocation isEqual:newShare.itemLocation]); XCTAssert(newShare.url != nil); XCTAssert(newShare.token != nil); @@ -218,7 +218,7 @@ - (void)testPublicShareCreationAndUpdate #warning Temporarily removed due to https://github.com/owncloud/core/issues/35541 XCTAssert(!share.protectedByPassword); XCTAssert(share.expirationDate.timeIntervalSinceNow >= (24*60*60*12)); - XCTAssert([share.itemPath isEqual:newShare.itemPath]); + XCTAssert([share.itemLocation isEqual:newShare.itemLocation]); [connection deleteShare:newShare resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { OCLogDebug(@"deleteShare: error=%@", event.error); @@ -271,11 +271,11 @@ - (void)testUserToUserSharing XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCItem *shareItem = items.lastObject; OCItem *rootItem = items.firstObject; - OCShare *createShare = [OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] path:shareItem.path permissions:OCSharePermissionsMaskRead expiration:nil]; + OCShare *createShare = [OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] location:shareItem.location permissions:OCSharePermissionsMaskRead expiration:nil]; [expectList fulfill]; @@ -292,7 +292,7 @@ - (void)testUserToUserSharing XCTAssert(!createShare.recipient.user.isRemote); XCTAssert(createShare.recipient.user.isRemote == newShare.recipient.user.isRemote); XCTAssert(createShare.permissions == newShare.permissions); - XCTAssert([createShare.itemPath isEqual:newShare.itemPath]); + XCTAssert([createShare.itemLocation isEqual:newShare.itemLocation]); XCTAssert(newShare.token == nil); XCTAssert(newShare.creationDate != nil); @@ -402,7 +402,7 @@ - (void)testUserToUserSharing if ([share.identifier isEqual:newShare.identifier]) { // .. and check that it's now accepted. - XCTAssert([share.state isEqual:OCShareStateRejected]); + XCTAssert([share.state isEqual:OCShareStateDeclined]); } } @@ -459,11 +459,11 @@ - (void)testUserToUserToUserResharing XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { OCItem *shareItem = items.lastObject; OCItem *rootItem = items.firstObject; - OCShare *createShare = [OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] path:shareItem.path permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskShare expiration:nil]; + OCShare *createShare = [OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] location:shareItem.location permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskShare expiration:nil]; [expectList fulfill]; @@ -480,7 +480,7 @@ - (void)testUserToUserToUserResharing XCTAssert(!createShare.recipient.user.isRemote); XCTAssert(createShare.recipient.user.isRemote == newShare.recipient.user.isRemote); XCTAssert(createShare.permissions == newShare.permissions); - XCTAssert([createShare.itemPath isEqual:newShare.itemPath]); + XCTAssert([createShare.itemLocation isEqual:newShare.itemLocation]); XCTAssert(newShare.token == nil); XCTAssert(newShare.creationDate != nil); @@ -527,7 +527,7 @@ - (void)testUserToUserToUserResharing { [expectRecipientSharesContainNewShare fulfill]; - OCShare *createReshare = [OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:OCTestTarget.demoLogin displayName:nil]] path:share.itemPath permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskShare expiration:nil]; + OCShare *createReshare = [OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:OCTestTarget.demoLogin displayName:nil]] location:share.itemLocation permissions:OCSharePermissionsMaskRead|OCSharePermissionsMaskShare expiration:nil]; [recipientConnection createShare:createReshare options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { OCShare *newReshare = (OCShare *)event.result; @@ -652,8 +652,8 @@ - (void)testFederatedSharing NSString *remoteUserIDFull = [remoteUser stringByAppendingFormat:@"@%@", remoteHost]; - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { - OCShare *createShare = [OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:remoteUserIDFull displayName:nil]] path:items[1].path permissions:OCSharePermissionsMaskRead expiration:nil]; + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { + OCShare *createShare = [OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:remoteUserIDFull displayName:nil]] location:items[1].location permissions:OCSharePermissionsMaskRead expiration:nil]; [expectList fulfill]; @@ -673,7 +673,7 @@ - (void)testFederatedSharing XCTAssert([createShare.recipient.user.remoteUserName isEqual:remoteUser]); XCTAssert([createShare.recipient.user.remoteHost isEqual:remoteHost]); XCTAssert(createShare.permissions == newShare.permissions); - XCTAssert([createShare.itemPath isEqual:newShare.itemPath]); + XCTAssert([createShare.itemLocation isEqual:newShare.itemLocation]); XCTAssert(newShare.token != nil); XCTAssert(newShare.creationDate != nil); @@ -804,14 +804,14 @@ - (void)testSharingErrors XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { dispatch_group_t waitForCompletion = dispatch_group_create(); // OCItem *shareItem = items.lastObject; OCItem *rootItem = items.firstObject; - OCShare *createRootShare = [OCShare shareWithRecipient:[OCRecipient recipientWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] path:rootItem.path permissions:OCSharePermissionsMaskRead expiration:nil]; + OCShare *createRootShare = [OCShare shareWithRecipient:[OCIdentity identityWithUser:[OCUser userWithUserName:OCTestTarget.userLogin displayName:nil]] location:rootItem.location permissions:OCSharePermissionsMaskRead expiration:nil]; [expectList fulfill]; @@ -872,13 +872,13 @@ - (void)testSharesRetrieval XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { - [connection createShare:[OCShare shareWithPublicLinkToPath:items[1].path linkName:@"iOS SDK CI" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { + [connection createShare:[OCShare shareWithPublicLinkToLocation:items[1].location linkName:@"iOS SDK CI" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { OCLog(@"error=%@, newShare=%@", event.error, event.result); [expectShareCreated fulfill]; - [connection retrieveItemListAtPath:@"/Documents/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:[OCLocation legacyRootPath:@"/Documents/"] depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { [expectLists fulfill]; if (error == nil) @@ -915,14 +915,14 @@ - (void)testRecipientsSearch [connection connectWithCompletionHandler:^(NSError *error, OCIssue *issue) { [expectConnect fulfill]; - void (^CountUsersAndGroups)(NSArray *recipient, NSUInteger expectedUsers, NSUInteger expectedGroups) = ^(NSArray *recipients, NSUInteger expectedUsers, NSUInteger expectedGroups) { + void (^CountUsersAndGroups)(NSArray *recipient, NSUInteger expectedUsers, NSUInteger expectedGroups) = ^(NSArray *recipients, NSUInteger expectedUsers, NSUInteger expectedGroups) { NSInteger users = 0, groups = 0; - for (OCRecipient *recipient in recipients) + for (OCIdentity *recipient in recipients) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - XCTAssert([[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:recipient]] isEqual:recipient]); // Test OCRecipient/OCUser/OCGroup archive/dearchive/comparison + XCTAssert([[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:recipient]] isEqual:recipient]); // Test OCIdentity/OCUser/OCGroup archive/dearchive/comparison #pragma clang diagnostic pop if (recipient.type == OCRecipientTypeUser) @@ -940,63 +940,63 @@ - (void)testRecipientsSearch XCTAssert(expectedGroups == groups); }; - [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:nil searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:nil searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 3, 1); - [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:@[@(OCShareTypeGroupShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:@[@(OCShareTypeGroupShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 0, 1); - [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 3, 0); - [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare), @(OCShareTypeGroupShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare), @(OCShareTypeGroupShare)] searchTerm:nil maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 3, 1); - [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare)] searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeFile ofShareType:@[@(OCShareTypeUserShare)] searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 1, 0); - [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:@[@(OCShareTypeGroupShare)] searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:@[@(OCShareTypeGroupShare)] searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 0, 1); - [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin" maximumNumberOfRecipients:200 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); XCTAssert(recipients!=nil); CountUsersAndGroups(recipients, 1, 1); - [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin" maximumNumberOfRecipients:0 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin" maximumNumberOfRecipients:0 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error!=nil); XCTAssert([error isOCErrorWithCode:OCErrorInsufficientParameters]); XCTAssert(recipients==nil); - [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin@demo.owncloud." maximumNumberOfRecipients:10 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { + [connection retrieveRecipientsForItemType:OCItemTypeCollection ofShareType:nil searchTerm:@"admin@demo.owncloud." maximumNumberOfRecipients:10 completionHandler:^(NSError * _Nullable error, NSArray * _Nullable recipients) { OCLog(@"Retrieved recipients=%@ with error=%@", recipients, error); XCTAssert(error==nil); @@ -1039,7 +1039,7 @@ - (void)testSharingItemWithSpecialChars XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { NSString *folderName = [@"Test+" stringByAppendingString:[NSDate new].description]; [expectLists fulfill]; @@ -1054,7 +1054,7 @@ - (void)testSharingItemWithSpecialChars [expectFolderCreated fulfill]; - [connection createShare:[OCShare shareWithPublicLinkToPath:newFolderItem.path linkName:@"iOS SDK CI" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { + [connection createShare:[OCShare shareWithPublicLinkToLocation:newFolderItem.location linkName:@"iOS SDK CI" permissions:OCSharePermissionsMaskRead password:@"test" expiration:[NSDate dateWithTimeIntervalSinceNow:24*60*60 * 2]] options:nil resultTarget:[OCEventTarget eventTargetWithEphermalEventHandlerBlock:^(OCEvent * _Nonnull event, id _Nonnull sender) { OCLog(@"error=%@, newShare=%@", event.error, event.result); XCTAssert(event.error == nil); @@ -1100,7 +1100,7 @@ - (void)testRequestAndResolvePrivateLink XCTAssert(error==nil); XCTAssert(issue==nil); - [connection retrieveItemListAtPath:@"/" depth:1 completionHandler:^(NSError *error, NSArray *items) { + [connection retrieveItemListAtLocation:OCLocation.legacyRootLocation depth:1 options:nil completionHandler:^(NSError *error, NSArray *items) { [expectLists fulfill]; for (OCItem *item in items) diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeFile.h b/tools/ocapigen/Code Generation/Generator/OCCodeFile.h new file mode 100644 index 00000000..d2d6a347 --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeFile.h @@ -0,0 +1,43 @@ +// +// OCCodeFile.h +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 "OCCodeFileSegment.h" +#import "OCCodeGenerator.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCodeFile : NSObject + +@property(weak,nullable) OCCodeGenerator *generator; + +@property(strong) NSURL *url; +@property(strong) NSMutableArray *segments; + +- (instancetype)initWithURL:(NSURL *)url generator:(OCCodeGenerator *)generator; + +- (OCCodeFileSegment *)segmentForName:(OCCodeFileSegmentName)name; +- (OCCodeFileSegment *)segmentForName:(OCCodeFileSegmentName)name after:(nullable OCCodeFileSegment *)afterSegment; + +- (void)read; +- (NSString *)composedFileContents; +- (nullable NSError *)write; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeFile.m b/tools/ocapigen/Code Generation/Generator/OCCodeFile.m new file mode 100644 index 00000000..b2fc1be2 --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeFile.m @@ -0,0 +1,129 @@ +// +// OCCodeFile.m +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 "OCCodeFile.h" + +@implementation OCCodeFile + +- (instancetype)initWithURL:(NSURL *)url generator:(nonnull OCCodeGenerator *)generator +{ + if ((self = [super init]) != nil) + { + _url = url; + _segments = [NSMutableArray new]; + _generator = generator; + + [self read]; + } + + return (self); +} + +- (void)read +{ + NSError *error = nil; + NSString *contents = [NSString stringWithContentsOfURL:_url encoding:NSUTF8StringEncoding error:&error]; + + NSArray *lines = [contents componentsSeparatedByString:[NSString stringWithFormat:@"\n"]]; + OCCodeFileSegment *segment = [[OCCodeFileSegment alloc] initWithAttributeHeaderLine:nil name:OCCodeFileSegmentNameLeadComment file:self generator:self.generator]; + + [_segments addObject:segment]; + + for (NSString *line in lines) + { + NSDictionary *segmentHeaderAttributes; + + if ((segmentHeaderAttributes = [_generator decodeSegmentAttributesLine:line]) != nil) + { + if (![segmentHeaderAttributes[OCCodeFileSegmentAttributeName] isEqual:segment.name] || (segment.lines.count == 0)) + { + [segment removeLastLineIfEmpty]; // remove extra trailing line + + segment = [[OCCodeFileSegment alloc] initWithAttributeHeaderLine:line name:segmentHeaderAttributes[OCCodeFileSegmentAttributeName] file:self generator:self.generator]; + [_segments addObject:segment]; + } + } + else + { + [segment _loadLine:line]; + } + } +} + +- (OCCodeFileSegment *)segmentForName:(OCCodeFileSegmentName)name +{ + return ([self segmentForName:name after:nil]); +} + +- (OCCodeFileSegment *)segmentForName:(OCCodeFileSegmentName)name after:(OCCodeFileSegment *)afterSegment +{ + for (OCCodeFileSegment *segment in _segments) + { + if ([segment.name isEqual:name]) + { + return (segment); + } + } + + OCCodeFileSegment *segment = [[OCCodeFileSegment alloc] initWithAttributeHeaderLine:nil name:name file:self generator:self.generator]; + segment.file = self; + + NSUInteger afterSegmentIdx = NSNotFound; + + if (afterSegment != nil) + { + afterSegmentIdx = [_segments indexOfObjectIdenticalTo:afterSegment]; + } + + if (afterSegmentIdx == NSNotFound) + { + [_segments addObject:segment]; + } + else + { + [_segments insertObject:segment atIndex:afterSegmentIdx+1]; + } + + return (segment); +} + +- (NSString *)composedFileContents +{ + NSMutableString *fileContents = [NSMutableString new]; + + for (OCCodeFileSegment *segment in _segments) + { + if (segment.hasContent) + { + [fileContents appendFormat:@"%@\n", segment.composedSegment]; // add extra trailing line + } + } + + return (fileContents); +} + +- (NSError *)write +{ + NSError *error=nil; + + [[self composedFileContents] writeToURL:_url atomically:YES encoding:NSUTF8StringEncoding error:&error]; + + return (error); +} + +@end diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.h b/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.h new file mode 100644 index 00000000..bea3854c --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.h @@ -0,0 +1,77 @@ +// +// OCCodeFileSegment.h +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 + +@class OCCodeGenerator; +@class OCCodeFile; + +typedef NSString* OCCodeFileSegmentName NS_TYPED_ENUM; + +typedef NSString* OCCodeFileSegmentAttribute NS_TYPED_ENUM; + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCodeFileSegment : NSObject + +@property(weak,nullable) OCCodeGenerator *generator; +@property(weak,nullable) OCCodeFile *file; + +@property(strong,nonatomic) OCCodeFileSegmentName name; + +@property(assign,nonatomic) BOOL locked; + +@property(strong,nullable,nonatomic) NSDictionary *attributes; +@property(strong,nonatomic) NSString *attributeHeaderLine; + +@property(strong) NSMutableArray *lines; + +- (instancetype)initWithAttributeHeaderLine:(nullable NSString *)attributeHeaderLine name:(OCCodeFileSegmentName)name file:(OCCodeFile *)file generator:(OCCodeGenerator *)generator; + +- (void)addLine:(NSString *)line, ... NS_FORMAT_FUNCTION(1,2); + +- (instancetype)clear; +- (void)removeLastLineIfEmpty; + +- (NSString *)composedSegment; +- (BOOL)hasContent; + +@end + +@interface OCCodeFileSegment (Internal) +- (void)_loadLine:(NSString *)line; +@end + +extern OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeName; +extern OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeLocked; +extern OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeCustomPropertyTypes; +extern OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeCustomPropertyNames; + +extern OCCodeFileSegmentName OCCodeFileSegmentNameLeadComment; +extern OCCodeFileSegmentName OCCodeFileSegmentNameIncludes; +extern OCCodeFileSegmentName OCCodeFileSegmentNameForwardDeclarations; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeLeadIn; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeSerialization; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeNativeSerialization; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeNativeDeserialization; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeDebugDescription; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeProperties; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeProtected; +extern OCCodeFileSegmentName OCCodeFileSegmentNameTypeLeadOut; + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.m b/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.m new file mode 100644 index 00000000..cf53facf --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeFileSegment.m @@ -0,0 +1,175 @@ +// +// OCCodeFileSegment.m +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 "OCCodeFileSegment.h" +#import "OCCodeGenerator.h" + +@interface OCCodeFileSegment () +{ + NSString *_attributeHeaderLine; +} +@end + +@implementation OCCodeFileSegment + +- (instancetype)initWithAttributeHeaderLine:(nullable NSString *)attributeHeaderLine name:(OCCodeFileSegmentName)name file:(OCCodeFile *)file generator:(OCCodeGenerator *)generator +{ + if ((self = [super init]) != nil) + { + _name = name; + _file = file; + _generator = generator; + self.attributeHeaderLine = attributeHeaderLine; + _lines = [NSMutableArray new]; + + if (attributeHeaderLine == nil) + { + _attributes = @{ + OCCodeFileSegmentAttributeName : name + }; + } + } + + return (self); +} + +- (NSString *)attributeHeaderLine +{ + if (_attributeHeaderLine != nil) + { + return (_attributeHeaderLine); + } + + return ([_generator encodeSegmentAttributesLineFrom:self]); +} + +- (void)setAttributeHeaderLine:(NSString *)attributeHeaderLine +{ + _attributeHeaderLine = attributeHeaderLine; + self.attributes = [_generator decodeSegmentAttributesLine:attributeHeaderLine]; +} + +- (BOOL)locked +{ + return (OCTypedCast(self.attributes[OCCodeFileSegmentAttributeLocked], NSNumber).boolValue); +} + +- (void)setLocked:(BOOL)locked +{ + [self setAttribute:OCCodeFileSegmentAttributeLocked to:(locked ? (__bridge id)kCFBooleanTrue : (__bridge id)kCFBooleanFalse)]; +} + +- (void)setAttribute:(OCCodeFileSegmentAttribute)attribute to:(id)value +{ + if (![_attributes[attribute] isEqual:value]) + { + NSMutableDictionary *mutableAttributes = [_attributes mutableCopy]; + mutableAttributes[attribute] = value; + _attributes = mutableAttributes; + + _attributeHeaderLine = nil; + } +} + +- (void)addLine:(NSString *)line, ... +{ + // Read-only blocks + if (self.locked) + { + return; + } + + va_list args; + + va_start(args, line); + NSString *formattedLine = [[NSString alloc] initWithFormat:line arguments:args]; + va_end(args); + + [_lines addObject:formattedLine]; +} + +- (void)_loadLine:(NSString *)line; +{ + // This method is only used for loading + [_lines addObject:line]; +} + +- (instancetype)clear +{ + if (self.locked) + { + return (nil); + } + + [_lines removeAllObjects]; + + return (self); +} + +- (void)removeLastLineIfEmpty +{ + if ([_lines.lastObject isEqual:@""]) + { + [_lines removeLastObject]; + } +} + +- (NSString *)composedSegment +{ + NSString *newLine = [NSString stringWithFormat:@"\n"]; + NSString *jointLines = [self.lines componentsJoinedByString:newLine]; + + if ([self.name isEqual:OCCodeFileSegmentNameLeadComment] && + ((self.attributes.count == 0) || ((self.attributes.count == 1) && (self.attributes[OCCodeFileSegmentAttributeName] != nil)))) + { + // Allow non-use of attribute header line for Lead Comment at the beginning of the file + return (jointLines); + } + + if (![jointLines hasPrefix:newLine]) + { + jointLines = [jointLines stringByAppendingString:newLine]; + } + + return ([NSString stringWithFormat:@"%@\n%@", self.attributeHeaderLine, jointLines]); +} + +- (BOOL)hasContent +{ + return ((self.lines.count > 0) || // Lines in "body" + ((self.attributes.count > 1) || ((self.attributes.count == 1) && (self.attributes[OCCodeFileSegmentAttributeName] == nil)))); // Attributes other than name +} + +@end + +OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeName = @"name"; +OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeLocked = @"locked"; +OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeCustomPropertyTypes = @"customPropertyTypes"; +OCCodeFileSegmentAttribute OCCodeFileSegmentAttributeCustomPropertyNames = @"customPropertyNames"; + +OCCodeFileSegmentName OCCodeFileSegmentNameLeadComment = @"lead comment"; +OCCodeFileSegmentName OCCodeFileSegmentNameIncludes = @"includes"; +OCCodeFileSegmentName OCCodeFileSegmentNameForwardDeclarations = @"forward declarations"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeLeadIn = @"type start"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeSerialization = @"type serialization"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeNativeSerialization = @"type native serialization"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeNativeDeserialization = @"type native deserialization"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeDebugDescription = @"type debug description"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeProperties = @"type properties"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeProtected = @"type protected"; +OCCodeFileSegmentName OCCodeFileSegmentNameTypeLeadOut = @"type end"; diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.h b/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.h new file mode 100644 index 00000000..454b8ae9 --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.h @@ -0,0 +1,80 @@ +// +// OCCodeGenerator.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchema.h" +#import "OCCodeFile.h" +#import "OCCodeFileSegment.h" + +NS_ASSUME_NONNULL_BEGIN + +@class OCCodeFile; + +typedef NSString* OCCodeRawPropertyName; +typedef NSString* OCCodeNativePropertyName; + +typedef NSString* OCCodeRawType; +typedef NSString* OCCodeRawFormat; +typedef NSString* OCCodeNativeType; + +@interface OCCodeGenerator : NSObject + +@property(strong) NSMutableDictionary *schemaByPath; +@property(strong) NSURL *targetFolderURL; + +@property(strong) NSString *segmentHeadLeadIn; +@property(strong,nullable) NSString *segmentHeadLeadOut; + +@property(strong) NSMutableArray *files; + +#pragma mark - Initialization +- (instancetype)initWithTargetFolder:(NSURL *)targetFolderURL; + +#pragma mark - Add schemas +- (void)addSchema:(OCSchema *)schema; + +#pragma mark - Request files +- (OCCodeFile *)fileForName:(NSString *)name; + +#pragma mark - Type and Naming convention / conversion (for subclassing) +@property(readonly,nonatomic) NSDictionary *rawToRawTypeMap; //!< Dictionary that maps raw types to "raw" types, i.e. "identityset" -> "IdentitySet", used by -nativeTypeForRAWType: +@property(readonly,nonatomic) NSDictionary *rawToNativeTypeMap; //!< Dictionary that maps raw types to native types, i.e. "string" -> "NSString", used by -nativeTypeForRAWType: +@property(readonly,nonatomic) NSDictionary *rawToNativePropertyNameMap; //!< Dictionary that maps raw names to native names, i.e. "description" -> "desc", used by -nativeNameForProperty: +@property(readonly,nonatomic) BOOL nativeTypesUseCamelCase; +@property(readonly,nonatomic,nullable) NSString *nativeTypesPrefix; + +- (nullable OCCodeNativeType)collectionTypeFor:(OCCodeNativeType)collectionType itemType:(OCCodeNativeType)itemType asReference:(BOOL)asReference inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Combines a collection and item type to a new type, i.e. NSArray and OCItem to NSArray. + +- (OCCodeNativePropertyName)nativeNameForProperty:(OCSchemaProperty *)property inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Mapping of raw names to model property names, i.e. "description" -> "desc" for ObjC (where "description" is effectively reserved) +- (OCCodeNativeType)nativeTypeForRAWType:(OCCodeRawType)rawType rawFormat:(nullable OCCodeRawFormat)rawFormat rawItemType:(nullable OCCodeRawType)rawItemType asReference:(BOOL)asReference inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Mapping of raw names to model types, i.e. "string" -> "NSString" (asReference=NO) + "NSString *" (asReference=YES) +- (OCCodeNativeType)nativeTypeForProperty:(OCSchemaProperty *)property asReference:(BOOL)asReference remappedFrom:(OCCodeNativeType _Nullable * _Nullable)outRemappedFrom inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Mapping of schema properties to model types, i.e. "array" -> "NSArray" (asReference=NO) + "NSArray *" (asReference=YES) +- (OCCodeNativeType)rawTypeForSchemaName:(NSString *)schemaName; //!< Formats a schema name as raw name for further processing (i.e. "odata.error.detail" -> "Odataerrordetail") +- (OCCodeNativeType)nativeTypeNameForSchema:(OCSchema *)schema inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Mapping of schema names to native model types, i.e. "user" -> "OCGUser" +- (BOOL)isGeneratedType:(OCCodeNativeType)type inSegment:(nullable OCCodeFileSegment *)fileSegment; //!< Returns YES if type is a generated type, i.e. to add forward declarations + +#pragma mark - Code generation +- (void)generate; +- (void)generateForSchema:(OCSchema *)schema; + +#pragma mark - Encode / Decode segment attribute line +- (nullable NSDictionary *)decodeSegmentAttributesLine:(NSString *)line; +- (NSString *)encodeSegmentAttributesLineFrom:(OCCodeFileSegment *)segment; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.m b/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.m new file mode 100644 index 00000000..ccfe393d --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/OCCodeGenerator.m @@ -0,0 +1,343 @@ +// +// OCCodeGenerator.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCCodeGenerator.h" +#import "OCCodeFileSegment.h" + +@implementation OCCodeGenerator + +- (instancetype)initWithTargetFolder:(NSURL *)targetFolderURL +{ + if ((self = [super init]) != nil) + { + _targetFolderURL = targetFolderURL; + _schemaByPath = [NSMutableDictionary new]; + _files = [NSMutableArray new]; + + _segmentHeadLeadIn = @"// occgen:"; + // _segmentHeadLeadOut = @"*/"; + } + + return (self); +} + +- (void)addSchema:(OCSchema *)schema +{ + _schemaByPath[schema.yamlPath] = schema; +} + +#pragma mark - Type and Naming convention / conversion +- (NSDictionary *)rawToRawTypeMap +{ + if (self.nativeTypesUseCamelCase) + { + return (@{ + @"itemreference" : @"ItemReference", + @"identityset" : @"IdentitySet", + @"specialfolder" : @"SpecialFolder", + @"filesysteminfo" : @"FilesystemInfo", + @"directoryobject" : @"DirectoryObject", + @"folderView" : @"FolderView", + @"opengraphfile" : @"OpenGraphFile", + @"Odataerror" : @"ODataError", + @"Odataerrormain" : @"ODataErrorMain", + @"Odataerrordetail" : @"ODataErrorDetail" + }); + } + + return (@{ }); +} + +- (NSDictionary *)rawToNativePropertyNameMap +{ + return (@{ }); +} + +- (NSDictionary *)rawToNativeTypeMap +{ + return (@{ }); +} + +- (nullable OCCodeNativeType)collectionTypeFor:(OCCodeNativeType)collectionType itemType:(OCCodeNativeType)itemType asReference:(BOOL)asReference inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + return ([NSString stringWithFormat:@"%@<%@ *>%@", collectionType, itemType, (asReference ? @" *" : @"")]); +} + +- (OCCodeNativePropertyName)nativeNameForProperty:(OCSchemaProperty *)property inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + if (property.name != nil) + { + OCCodeNativePropertyName nativePropertyName; + + if ((nativePropertyName = self.rawToNativePropertyNameMap[property.name]) != nil) + { + return (nativePropertyName); + } + } + + return (property.name); +} + +- (OCCodeNativeType)nativeTypeForRAWType:(OCCodeRawType)rawType rawFormat:(OCCodeRawFormat)rawFormat rawItemType:(OCCodeRawType)rawItemType asReference:(BOOL)asReference inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + OCCodeNativeType nativeType = nil; + BOOL dontAddPrefix = NO; + + if (rawType == nil) + { + return(nil); + } + + if ((nativeType == nil) && (rawItemType != nil)) + { + nativeType = [self collectionTypeFor:[self nativeTypeForRAWType:rawType rawFormat:nil rawItemType:nil asReference:NO inSegment:fileSegment] + itemType:[self nativeTypeForRAWType:rawItemType rawFormat:nil rawItemType:nil asReference:NO inSegment:fileSegment] + asReference:asReference + inSegment:fileSegment]; + + dontAddPrefix = YES; + } + + if (nativeType == nil) + { + OCSchema *schema = nil; + + schema = self.schemaByPath[rawType]; + + if (schema.name != nil) + { + rawType = [self rawTypeForSchemaName:schema.name]; + } + } + + if (self.rawToRawTypeMap[rawType] != nil) + { + rawType = self.rawToRawTypeMap[rawType]; + } + + if (nativeType == nil) + { + if (rawFormat != nil) + { + if ((nativeType = self.rawToNativeTypeMap[[rawType stringByAppendingFormat:@":%@", rawFormat]]) != nil) + { + return (nativeType); + } + } + + if (nativeType == nil) + { + if ((nativeType = self.rawToNativeTypeMap[rawType]) != nil) + { + return (nativeType); + } + } + } + + if (nativeType == nil) + { + nativeType = rawType; + } + + if (self.nativeTypesUseCamelCase) + { + nativeType = [[[nativeType substringToIndex:1] uppercaseString] stringByAppendingString:[nativeType substringFromIndex:1]]; + } + + if ((self.nativeTypesPrefix != nil) && !dontAddPrefix) + { + nativeType = [self.nativeTypesPrefix stringByAppendingString:nativeType]; + } + + return (nativeType); +} + +- (OCCodeNativeType)nativeTypeForProperty:(OCSchemaProperty *)property asReference:(BOOL)asReference remappedFrom:(OCCodeNativeType _Nullable * _Nullable)outRemappedFrom inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + OCCodeNativeType nativeType = nil; + NSDictionary *nativeTypesByPropertyName; + BOOL isRemapped = NO; + + if ((nativeTypesByPropertyName = fileSegment.attributes[OCCodeFileSegmentAttributeCustomPropertyTypes]) != nil) + { + OCCodeNativePropertyName nativePropertyName; + + if ((nativePropertyName = [self nativeNameForProperty:property inSegment:fileSegment]) != nil) + { + if ((nativeType = nativeTypesByPropertyName[nativePropertyName]) != nil) + { + isRemapped = YES; + } + } + } + + if ((nativeType == nil) || isRemapped) + { + OCCodeNativeType pureNativeType = nil; + + pureNativeType = [self nativeTypeForRAWType:property.type rawFormat:property.format rawItemType:property.itemType asReference:asReference inSegment:fileSegment]; + + if (nativeType == nil) + { + nativeType = pureNativeType; + } + else if (outRemappedFrom != NULL) + { + *outRemappedFrom = pureNativeType; + } + } + + return (nativeType); +} + +- (OCCodeNativeType)rawTypeForSchemaName:(NSString *)schemaName +{ + schemaName = [schemaName stringByReplacingOccurrencesOfString:@"." withString:@""]; + schemaName = [[[schemaName substringToIndex:1] uppercaseString] stringByAppendingString:[schemaName substringFromIndex:1]]; + + return (schemaName); +} + +- (OCCodeNativeType)nativeTypeNameForSchema:(OCSchema *)schema inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + return ([self nativeTypeForRAWType:[self rawTypeForSchemaName:schema.name] rawFormat:nil rawItemType:nil asReference:NO inSegment:fileSegment]); +} + +- (BOOL)isGeneratedType:(OCCodeNativeType)type inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + for (OCSchema *schema in _schemaByPath.allValues) + { + if ([[self nativeTypeNameForSchema:schema inSegment:fileSegment] isEqual:type]) + { + return (YES); + } + } + + return (NO); +} + +- (OCCodeFile *)fileForName:(NSString *)name +{ + OCCodeFile *file = [[OCCodeFile alloc] initWithURL:[_targetFolderURL URLByAppendingPathComponent:name] generator:self]; + + [_files addObject:file]; + + return (file); +} + +- (void)generate +{ + // Generate content + for (OCSchema *schema in _schemaByPath.allValues) + { + [self generateForSchema:schema]; + } + + // Write files + for (OCCodeFile *file in _files) + { + [file write]; + } +} + +- (void)generateForSchema:(OCSchema *)schema +{ + // Subclass +} + +#pragma mark - Encode / Decode segment attribute line +- (nullable NSDictionary *)decodeSegmentAttributesLine:(NSString *)line +{ + if (![line hasPrefix:self.segmentHeadLeadIn]) + { + return (nil); + } + + if ((self.segmentHeadLeadOut != nil) && ![line hasSuffix:self.segmentHeadLeadOut]) + { + return (nil); + } + + NSString *lineContent = [line substringWithRange:NSMakeRange(self.segmentHeadLeadIn.length, line.length - self.segmentHeadLeadIn.length - self.segmentHeadLeadOut.length)]; + + NSRange dividerRange = [lineContent rangeOfString:@"{"]; // Find start of JSON + + if (dividerRange.location == NSNotFound) + { + // Just the name - trim white space + return (@{ + OCCodeFileSegmentAttributeName : [lineContent stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] + }); + } + else + { + // Name + JSON + NSString *name = [[lineContent substringToIndex:dividerRange.location] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; + NSString *jsonString = [[lineContent substringFromIndex:dividerRange.location] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; + NSMutableDictionary *attributesDict = [NSMutableDictionary new]; + + if (name.length != 0) + { + attributesDict[OCCodeFileSegmentAttributeName] = name; + } + + if (jsonString.length != 0) + { + NSError *error = nil; + id jsonObject = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error]; + + if ((jsonObject == nil) && (error != nil)) + { + NSLog(@"Error parsing %@: %@", lineContent, error); + } + else + { + if ([jsonObject isKindOfClass:NSDictionary.class]) + { + [attributesDict addEntriesFromDictionary:(NSDictionary *)jsonObject]; + } + } + } + + return (attributesDict); + } + + return (nil); +} + +- (NSString *)encodeSegmentAttributesLineFrom:(OCCodeFileSegment *)segment +{ + NSMutableDictionary *attributesDict = [NSMutableDictionary new]; + NSError *error = nil; + + if (segment.attributes != nil) + { + [attributesDict addEntriesFromDictionary:segment.attributes]; + attributesDict[OCCodeFileSegmentAttributeName] = nil; + } + + return ([NSString stringWithFormat:@"%@ %@%@%@%@", + self.segmentHeadLeadIn, + segment.name, + ((attributesDict.count > 0) ? @" " : @""), + ((attributesDict.count > 0) ? [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:attributesDict options:NSJSONWritingSortedKeys error:&error] encoding:NSUTF8StringEncoding] : @""), + ((self.segmentHeadLeadOut != nil) ? self.segmentHeadLeadOut : @"") + ]); +} + +@end diff --git a/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.h b/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.h new file mode 100644 index 00000000..7f2dc205 --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.h @@ -0,0 +1,28 @@ +// +// OCCodeGeneratorObjC.h +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 "OCCodeGenerator.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCCodeGeneratorObjC : OCCodeGenerator + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.m b/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.m new file mode 100644 index 00000000..04da1514 --- /dev/null +++ b/tools/ocapigen/Code Generation/Generator/ObjC/OCCodeGeneratorObjC.m @@ -0,0 +1,372 @@ +// +// OCCodeGeneratorObjC.m +// ocapigen +// +// Created by Felix Schwarz on 27.01.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 "OCCodeGeneratorObjC.h" + +@implementation OCCodeGeneratorObjC + +- (NSDictionary *)rawToNativeTypeMap +{ + return (@{ + @"array" : @"NSArray", + @"object" : @"NSDictionary", + @"string" : @"NSString", + @"string:date-time" : @"NSDate", + @"integer" : @"NSNumber", + @"boolean" : @"NSNumber", + }); +} + +- (NSDictionary *)rawToNativePropertyNameMap +{ + return (@{ + @"id" : @"identifier", + @"description" : @"desc" + }); +} + +- (BOOL)nativeTypesUseCamelCase +{ + return (YES); +} + +- (NSString *)nativeTypesPrefix +{ + return (@"GA"); +} + +- (OCCodeNativeType)collectionTypeFor:(OCCodeNativeType)collectionType itemType:(OCCodeNativeType)itemType asReference:(BOOL)asReference inFile:(OCCodeFile *)file +{ + return ([NSString stringWithFormat:@"%@<%@ *>%@", collectionType, itemType, (asReference ? @"*" : @"")]); +} + +- (OCCodeNativeType)nativeTypeForProperty:(OCSchemaProperty *)property asReference:(BOOL)asReference remappedFrom:(OCCodeNativeType _Nullable __autoreleasing * _Nullable)outRemappedFrom inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + NSString *type = nil; + + if ([property.name hasSuffix:@"Url"]) + { + type = @"NSURL"; + } + + if (asReference) + { + type = [type stringByAppendingString:@" *"]; + } + + if (type != nil) + { + return (type); + } + + return ([super nativeTypeForProperty:property asReference:asReference remappedFrom:outRemappedFrom inSegment:fileSegment]); +} + +- (OCCodeNativeType)nativeTypeForRAWType:(OCCodeRawType)rawType rawFormat:(OCCodeRawFormat)rawFormat rawItemType:(OCCodeRawType)rawItemType asReference:(BOOL)asReference inSegment:(nullable OCCodeFileSegment *)fileSegment +{ + OCCodeNativeType nativeType; + + if ((nativeType = [super nativeTypeForRAWType:rawType rawFormat:rawFormat rawItemType:rawItemType asReference:asReference inSegment:fileSegment]) != nil) + { + if (asReference && ![nativeType hasSuffix:@"*"]) + { + nativeType = [nativeType stringByAppendingString:@" *"]; + } + } + + return (nativeType); +} + + +- (NSString *)commentForProperty:(OCSchemaProperty *)property ofSchema:(OCSchema *)schema +{ + NSString *comment = nil; + + if (property.desc != nil) + { + comment = property.desc; + } + + if ([property.type isEqual:@"integer"] || [property.type isEqual:@"boolean"] || (property.format != nil)) + { + if (comment != nil) + { + comment = [NSString stringWithFormat:@"[%@%@] %@", property.type, ((property.format != nil) ? [@":" stringByAppendingString:property.format] : @""), comment]; + } + else + { + comment = [NSString stringWithFormat:@"[%@%@]", property.type, ((property.format != nil) ? [@":" stringByAppendingString:property.format] : @"")]; + } + } + + if (property.pattern != nil) + { + comment = [comment stringByAppendingFormat:@" | pattern: %@", property.pattern]; + } + + return (comment); +} + +- (void)addCopyrightHeaderToSegment:(OCCodeFileSegment *)segment +{ + [segment addLine:@"//"]; + [segment addLine:@"// %@", segment.file.url.lastPathComponent]; + [segment addLine:@"// Autogenerated / Managed by ocapigen"]; + [segment addLine:@"// Copyright (C) %@ ownCloud GmbH. All rights reserved.", @(2022)]; + [segment addLine:@"//"]; + [segment addLine:@""]; + [segment addLine:@"/*"]; + [segment addLine:@" * Copyright (C) 2022, ownCloud GmbH."]; + [segment addLine:@" *"]; + [segment addLine:@" * This code is covered by the GNU Public License Version 3."]; + [segment addLine:@" *"]; + [segment addLine:@" * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/"]; + [segment addLine:@" * You should have received a copy of this license along with this program. If not, see ."]; + [segment addLine:@" *"]; + [segment addLine:@" */"]; + [segment addLine:@""]; +} + +- (void)generateForSchema:(OCSchema *)schema +{ + NSString *className = [self nativeTypeNameForSchema:schema inSegment:nil]; + OCCodeFileSegment *segment = nil, *forwardDeclarationsSegment = nil, *headerPropertiesSegment = nil; + NSMutableSet *forwardDeclaredTypeNames = [NSMutableSet new]; + + // ## + // ## Header file + // ## + + NSString *headerFileName = [className stringByAppendingString:@".h"]; + OCCodeFile *headerFile = [self fileForName:headerFileName]; + + // Lead comment + segment = [[headerFile segmentForName:OCCodeFileSegmentNameLeadComment] clear]; + [self addCopyrightHeaderToSegment:segment]; + + // Includes + segment = [[headerFile segmentForName:OCCodeFileSegmentNameIncludes after:segment] clear]; + + [segment addLine:@"#import "]; + [segment addLine:@"#import \"GAGraphObject.h\""]; + + // Forward declarations (prepare) + forwardDeclarationsSegment = [[headerFile segmentForName:OCCodeFileSegmentNameForwardDeclarations after:segment] clear]; + + // @interface … + segment = [[headerFile segmentForName:OCCodeFileSegmentNameTypeLeadIn after:forwardDeclarationsSegment] clear]; + + + [segment addLine:@"NS_ASSUME_NONNULL_BEGIN"]; + [segment addLine:@"@interface %@ : NSObject ", className]; + + // Properties + headerPropertiesSegment = [[headerFile segmentForName:OCCodeFileSegmentNameTypeProperties after:segment] clear]; + + for (OCSchemaProperty *property in schema.properties) + { + NSString *comment = [self commentForProperty:property ofSchema:schema]; + NSString *propertyClassNameOriginal = nil; + NSString *propertyClassName = [self nativeTypeForProperty:property asReference:NO remappedFrom:&propertyClassNameOriginal inSegment:headerPropertiesSegment]; + NSString *propertyTypeOriginal = nil; + NSString *propertyType = [self nativeTypeForProperty:property asReference:YES remappedFrom:&propertyTypeOriginal inSegment:headerPropertiesSegment]; + NSString *propertyName = [self nativeNameForProperty:property inSegment:headerPropertiesSegment]; + + comment = ((comment != nil) ? [NSString stringWithFormat:@" //!< %@", comment] : @""); + + [headerPropertiesSegment addLine:@"@property(strong%@) %@%@%@;%@", + (property.required ? @"" : @", nullable"), + propertyType, + ([propertyType containsString:@" "] ? @"" : @" "), + propertyName, + comment + ]; + + if (![propertyType hasPrefix:@"NS"]) + { + if (propertyTypeOriginal != nil) + { + [forwardDeclarationsSegment addLine:@"typedef %@ %@;", propertyTypeOriginal, propertyClassName]; + } + else + { + [forwardDeclaredTypeNames addObject:propertyClassName]; + } + } + else if (property.isCollection) + { + OCCodeNativeType propertClassName = [self nativeTypeForRAWType:property.itemType rawFormat:nil rawItemType:nil asReference:NO inSegment:segment]; + + if (![propertClassName hasPrefix:@"NS"]) + { + [forwardDeclaredTypeNames addObject:propertClassName]; + } + } + } + + // Protected + segment = [[headerFile segmentForName:OCCodeFileSegmentNameTypeProtected after:headerPropertiesSegment] clear]; + segment.locked = YES; + + // Forward declarations (render) + NSArray *forwardDeclaredTypeNamesSorted = [forwardDeclaredTypeNames.allObjects sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + if (forwardDeclarationsSegment != nil) + { + for (NSString *typeName in forwardDeclaredTypeNamesSorted) + { + [forwardDeclarationsSegment addLine:@"@class %@;", typeName]; + } + } + + // @end (class) + segment = [headerFile segmentForName:OCCodeFileSegmentNameTypeLeadOut after:segment]; + [segment clear]; + [segment addLine:@"@end"]; + [segment addLine:@"NS_ASSUME_NONNULL_END"]; + + // ## + // ## Implementation file + // ## + + NSString *implementationFileName = [className stringByAppendingString:@".m"]; + NSMutableString *debugDescriptionStringFormat = [NSMutableString new], *debugDescriptionStringContent = [NSMutableString new]; + OCCodeFile *implementationFile = [self fileForName:implementationFileName]; + OCCodeFileSegment *nativeSerializationSegment = nil, *nativeDeserializationSegment = nil, *debugDescriptionSegment = nil; + + // Lead comment + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameLeadComment] clear]; + [self addCopyrightHeaderToSegment:segment]; + + // Includes + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameIncludes after:segment] clear]; + + [segment addLine:@"#import \"%@\"", headerFileName]; + for (NSString *typeName in forwardDeclaredTypeNamesSorted) + { + [segment addLine:@"#import \"%@.h\"", typeName]; + } + + // Implementation lead-in + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeLeadIn after:segment] clear]; + [segment addLine:@"@implementation %@", className]; + + // Implementation serialization + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeSerialization after:segment] clear]; + nativeDeserializationSegment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeNativeDeserialization after:segment] clear]; + nativeSerializationSegment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeNativeSerialization after:nativeDeserializationSegment] clear]; + debugDescriptionSegment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeDebugDescription after:nativeSerializationSegment] clear]; + + [segment addLine:@"+ (nullable instancetype)decodeGraphData:(GAGraphData)structure context:(nullable GAGraphContext *)context error:(NSError * _Nullable * _Nullable)outError"]; + [segment addLine:@"{"]; + [segment addLine:@" %@ *instance = [self new];", className]; + [segment addLine:@""]; + + [nativeDeserializationSegment addLine:@"+ (BOOL)supportsSecureCoding"]; + [nativeDeserializationSegment addLine:@"{"]; + [nativeDeserializationSegment addLine:@" return (YES);"]; + [nativeDeserializationSegment addLine:@"}"]; + [nativeDeserializationSegment addLine:@""]; + [nativeDeserializationSegment addLine:@"- (instancetype)initWithCoder:(NSCoder *)decoder"]; + [nativeDeserializationSegment addLine:@"{"]; + [nativeDeserializationSegment addLine:@" if ((self = [super init]) != nil)"]; + [nativeDeserializationSegment addLine:@" {"]; + + [nativeSerializationSegment addLine:@"- (void)encodeWithCoder:(NSCoder *)coder"]; + [nativeSerializationSegment addLine:@"{"]; + + for (OCSchemaProperty *property in schema.properties) + { + NSString *comment = [self commentForProperty:property ofSchema:schema]; + NSString *propertyClassName = [self nativeTypeForProperty:property asReference:NO remappedFrom:NULL inSegment:segment]; + NSString *propertyName = [self nativeNameForProperty:property inSegment:segment]; + NSString *collectionType = @"Nil"; + + // JSON -> properties mapping + comment = ((comment != nil) ? [NSString stringWithFormat:@" //!< %@", comment] : @""); + + if (property.isCollection) + { + collectionType = [[self nativeTypeForRAWType:property.type rawFormat:nil rawItemType:nil asReference:NO inSegment:segment] stringByAppendingString:@".class"]; + propertyClassName = [self nativeTypeForRAWType:property.itemType rawFormat:nil rawItemType:nil asReference:NO inSegment:segment]; + } + + if ([property.name isEqual:propertyName]) + { + if (property.required) + { + [segment addLine:@" GA_SET_REQ(%@, %@, %@);", property.name, propertyClassName, collectionType]; + } + else + { + [segment addLine:@" GA_SET(%@, %@, %@);", property.name, propertyClassName, collectionType]; + } + } + else + { + if (property.required) + { + [segment addLine:@" GA_MAP_REQ(%@, \"%@\", %@, %@);", propertyName, property.name, propertyClassName, collectionType]; + } + else + { + [segment addLine:@" GA_MAP(%@, \"%@\", %@, %@);", propertyName, property.name, propertyClassName, collectionType]; + } + } + + // Secure Coding deserialization + if ([collectionType isEqual:@"Nil"]) + { + [nativeDeserializationSegment addLine:@" _%@ = [decoder decodeObjectOfClass:%@.class forKey:@\"%@\"];", propertyName, propertyClassName, propertyName]; + } + else + { + [nativeDeserializationSegment addLine:@" _%@ = [decoder decodeObjectOfClasses:[NSSet setWithObjects: %@.class, %@, nil] forKey:@\"%@\"];", propertyName, propertyClassName, collectionType, propertyName]; + } + + // Secure Coding serialization + [nativeSerializationSegment addLine:@" [coder encodeObject:_%@ forKey:@\"%@\"];", propertyName, propertyName]; + + // Debug description + [debugDescriptionStringFormat appendString:@"\%@"]; + [debugDescriptionStringContent appendFormat:@", ((_%@!=nil) ? [NSString stringWithFormat:@\", %@: %%@\", _%@] : @\"\")", propertyName, propertyName, propertyName]; + } + [segment addLine:@""]; + [segment addLine:@" return (instance);"]; + [segment addLine:@"}"]; + + [nativeDeserializationSegment addLine:@" }"]; + [nativeDeserializationSegment addLine:@""]; + [nativeDeserializationSegment addLine:@" return (self);"]; + [nativeDeserializationSegment addLine:@"}"]; + + [nativeSerializationSegment addLine:@"}"]; + + [debugDescriptionSegment addLine:@"- (NSString *)description"]; + [debugDescriptionSegment addLine:@"{"]; + [debugDescriptionSegment addLine:@" return ([NSString stringWithFormat:@\"<%%@: %%p%@>\", NSStringFromClass(self.class), self%@]);", debugDescriptionStringFormat, debugDescriptionStringContent]; + [debugDescriptionSegment addLine:@"}"]; + + // Protected + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeProtected after:debugDescriptionSegment] clear]; + segment.locked = YES; + + segment = [[implementationFile segmentForName:OCCodeFileSegmentNameTypeLeadOut after:segment] clear]; + [segment addLine:@"@end"]; +} + +@end diff --git a/tools/ocapigen/Code Generation/Schema/OCSchema.h b/tools/ocapigen/Code Generation/Schema/OCSchema.h new file mode 100644 index 00000000..c199f843 --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchema.h @@ -0,0 +1,38 @@ +// +// OCSchema.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchemaProperty.h" +#import "OCYAMLNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCSchema : NSObject + +@property(strong) NSString *name; +@property(strong,nullable) NSString *desc; + +@property(strong) OCYAMLPath yamlPath; + +@property(strong) NSMutableArray *properties; + +- (instancetype)initWithYAMLNode:(OCYAMLNode *)node; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Schema/OCSchema.m b/tools/ocapigen/Code Generation/Schema/OCSchema.m new file mode 100644 index 00000000..61ad549a --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchema.m @@ -0,0 +1,73 @@ +// +// OCSchema.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchema.h" +#import "OCSchemaProperty.h" + +@implementation OCSchema + +- (instancetype)initWithYAMLNode:(OCYAMLNode *)node +{ + if ((self = [super init]) != nil) + { + self.name = node.name; + self.desc = node.childrenByName[@"description"].value; + + self.yamlPath = node.path; + + self.properties = [NSMutableArray new]; + + for (OCYAMLNode *propertyNode in node.childrenByName[@"properties"].children) + { + OCSchemaProperty *property = [OCSchemaProperty new]; + + property.schema = self; + property.yamlNode= propertyNode; + + property.name = propertyNode.name; + property.desc = propertyNode.childrenByName[@"description"].value; + property.type = propertyNode.childrenByName[@"type"].value; + property.required = [OCTypedCast(node.childrenByName[@"required"].value,NSArray) containsObject:propertyNode.name]; + property.format = propertyNode.childrenByName[@"format"].value; + property.pattern = propertyNode.childrenByName[@"pattern"].value; + + if (property.type == nil) + { + property.type = propertyNode.childrenByName[@"$ref"].value; + } + + if ([property.type isEqual:@"array"] && (propertyNode.childrenByName[@"items"] != nil)) + { + OCSchemaPropertyType itemType = propertyNode.childrenByName[@"items"].childrenByName[@"$ref"].value; + + if (itemType == nil) + { + itemType = propertyNode.childrenByName[@"items"].childrenByName[@"type"].value; + } + + property.itemType = itemType; + } + + [self.properties addObject:property]; + } + } + + return (self); +} + +@end diff --git a/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.h b/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.h new file mode 100644 index 00000000..6d1cb529 --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.h @@ -0,0 +1,27 @@ +// +// OCSchemaConstraint.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OCSchemaConstraint : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.m b/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.m new file mode 100644 index 00000000..e49e3a7a --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchemaConstraint.m @@ -0,0 +1,23 @@ +// +// OCSchemaConstraint.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchemaConstraint.h" + +@implementation OCSchemaConstraint + +@end diff --git a/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.h b/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.h new file mode 100644 index 00000000..6cdc70e3 --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.h @@ -0,0 +1,51 @@ +// +// OCSchemaProperty.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchemaConstraint.h" +#import "OCYAMLNode.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NSString* OCSchemaPropertyType NS_TYPED_ENUM; +typedef NSString* OCSchemaPropertyFormat; +typedef NSString* OCSchemaPropertyPattern; + +@class OCSchema; + +@interface OCSchemaProperty : NSObject + +@property(weak) OCSchema *schema; +@property(strong) OCYAMLNode *yamlNode; + +@property(strong) NSString *name; +@property(strong,nullable) OCSchemaPropertyType desc; + +@property(strong) OCSchemaPropertyType type; +@property(strong,nullable) OCSchemaPropertyType itemType; +@property(strong,nullable) OCSchemaPropertyFormat format; +@property(strong,nullable) OCSchemaPropertyPattern pattern; + +@property(readonly,nonatomic) BOOL isCollection; +@property(assign) BOOL required; + +@property(strong) NSMutableArray *constraints; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.m b/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.m new file mode 100644 index 00000000..15fcea83 --- /dev/null +++ b/tools/ocapigen/Code Generation/Schema/OCSchemaProperty.m @@ -0,0 +1,28 @@ +// +// OCSchemaProperty.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCSchemaProperty.h" + +@implementation OCSchemaProperty + +- (BOOL)isCollection +{ + return (self.itemType != nil); +} + +@end diff --git a/tools/ocapigen/YAML Parser/OCYAMLNode.h b/tools/ocapigen/YAML Parser/OCYAMLNode.h new file mode 100644 index 00000000..11380a5a --- /dev/null +++ b/tools/ocapigen/YAML Parser/OCYAMLNode.h @@ -0,0 +1,46 @@ +// +// OCYAMLNode.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 + +typedef NSString* OCYAMLPath; + +NS_ASSUME_NONNULL_BEGIN + +#define OCTypedCast(var,className) ([var isKindOfClass:[className class]] ? ((className *)var) : nil) + +@interface OCYAMLNode : NSObject + +@property(weak,nullable) OCYAMLNode *parentNode; + +@property(assign) NSUInteger indentLevel; + +@property(strong,readonly) NSString *name; +@property(strong,nullable) id value; + +@property(strong) NSMutableDictionary *childrenByName; +@property(strong) NSMutableArray *children; + +@property(strong,readonly,nonatomic) OCYAMLPath path; + +- (instancetype)initWithName:(NSString *)name value:(nullable id)value; +- (void)addChild:(OCYAMLNode *)child; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/YAML Parser/OCYAMLNode.m b/tools/ocapigen/YAML Parser/OCYAMLNode.m new file mode 100644 index 00000000..e74910f7 --- /dev/null +++ b/tools/ocapigen/YAML Parser/OCYAMLNode.m @@ -0,0 +1,61 @@ +// +// OCYAMLNode.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCYAMLNode.h" + +@implementation OCYAMLNode + +- (instancetype)initWithName:(NSString *)name value:(id)value +{ + if ((self = [super init]) != nil) + { + _name = name; + _value = value; + _childrenByName = NSMutableDictionary.new; + _children = NSMutableArray.new; + } + + return (self); +} + +- (void)addChild:(OCYAMLNode *)child +{ + child.parentNode = self; + + _childrenByName[child.name] = child; + [_children addObject:child]; +} + +- (OCYAMLPath)path +{ + NSString *path = self.name; + OCYAMLNode *node = self.parentNode; + + do + { + path = [NSString stringWithFormat:@"%@/%@", node.name, path]; + + node = node.parentNode; + }while(node != nil); + + path = [@"#/" stringByAppendingString:path]; + + return (path); +} + +@end diff --git a/tools/ocapigen/YAML Parser/OCYAMLParser.h b/tools/ocapigen/YAML Parser/OCYAMLParser.h new file mode 100644 index 00000000..95de8201 --- /dev/null +++ b/tools/ocapigen/YAML Parser/OCYAMLParser.h @@ -0,0 +1,35 @@ +// +// OCYAMLParser.h +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCYAMLNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OCYAMLParser : NSObject + +@property(strong,nonatomic) NSArray *resultNodes; + +- (instancetype)initWithFileContents:(NSString *)yamlFileContents; +- (void)parse; + +- (OCYAMLNode *)nodeForPath:(OCYAMLPath)path; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tools/ocapigen/YAML Parser/OCYAMLParser.m b/tools/ocapigen/YAML Parser/OCYAMLParser.m new file mode 100644 index 00000000..f3bd18b2 --- /dev/null +++ b/tools/ocapigen/YAML Parser/OCYAMLParser.m @@ -0,0 +1,214 @@ +// +// OCYAMLParser.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCYAMLParser.h" +#import "OCYAMLNode.h" + +@interface OCYAMLParser () +{ + NSMutableArray *_lines; + NSMutableArray *_nodeStack; + + NSMutableArray *_resultNodes; +} + +@end + +@implementation OCYAMLParser + +- (instancetype)initWithFileContents:(NSString *)yamlFileContents +{ + if ((self = [super init]) != nil) + { + // Split into lines + _lines = [[yamlFileContents componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet] mutableCopy]; + + // Remove empty lines + [_lines removeObject:@""]; + + // Node stack + _nodeStack = NSMutableArray.new; + _resultNodes = NSMutableArray.new; + } + + return (self); +} + +- (void)parse +{ + NSCharacterSet *whitespaceCharSet = NSCharacterSet.whitespaceCharacterSet; + + for (NSString *line in _lines) + { + NSUInteger lineLength = line.length; + NSUInteger lineIndentLevel = 0; + + // Determine level of indentation + for (NSUInteger idx=0; idx < lineLength; idx++) + { + NSString *idxChar = [line substringWithRange:NSMakeRange(idx, 1)]; + + if ([idxChar isEqual:@" "]) + { + lineIndentLevel++; + } + else + { + break; + } + } + + // Extract line content + NSString *lineContent = [line substringFromIndex:lineIndentLevel]; + + // Skip comments and empty contents + if ([lineContent hasPrefix:@"#"] || + [lineContent isEqual:@""]) + { + continue; + } + + // Handle lists + if ([lineContent hasPrefix:@"-"]) + { + OCYAMLNode *lastNode; + + if ((lastNode = _nodeStack.lastObject) != nil) + { + if (lineIndentLevel > lastNode.indentLevel) + { + NSMutableArray *listArray = nil; + + if (lastNode.value == nil) + { + listArray = NSMutableArray.new; + lastNode.value = listArray; + } + else + { + if ([lastNode.value isKindOfClass:NSMutableArray.class]) + { + listArray = lastNode.value; + } + } + + NSString *listLineContent = [[lineContent substringFromIndex:1] stringByTrimmingCharactersInSet:whitespaceCharSet]; + + if (![listLineContent isEqual:@""]) + { + [listArray addObject:listLineContent]; + } + + continue; + } + } + } + + // Split contents + NSRange splitPoint = [lineContent rangeOfString:@":"]; + + if (splitPoint.location == NSNotFound) + { + NSLog(@"Skipping %@", lineContent); + continue; + } + + NSString *name = [lineContent substringToIndex:splitPoint.location]; + NSString *value = [[lineContent substringFromIndex:splitPoint.location+1] stringByTrimmingCharactersInSet:whitespaceCharSet]; + + // Sanitize value + if ([value isEqual:@""]) { value = nil; } + + if ([value hasPrefix:@"'"] && [value hasSuffix:@"'"] && (value.length > 1)) + { + // Remove '' + value = [value substringWithRange:NSMakeRange(1, value.length-2)]; + } + + // Create node for line + OCYAMLNode *node = [[OCYAMLNode alloc] initWithName:name value:value]; + node.indentLevel = lineIndentLevel; + + // Remove nodes from stack with higher or same indent level + while ((_nodeStack.count > 0) && (_nodeStack.lastObject.indentLevel >= lineIndentLevel)) + { + [_nodeStack removeLastObject]; + }; + + // Add to parent node + OCYAMLNode *parentNode = _nodeStack.lastObject; + + if (parentNode == nil) + { + [_resultNodes addObject:node]; + } + else + { + [parentNode addChild:node]; + } + + // Push node onto stack + [_nodeStack addObject:node]; + } +} + +- (OCYAMLNode *)nodeForPath:(OCYAMLPath)path +{ + if ([path hasPrefix:@"#"]) { + path = [path substringFromIndex:1]; + } + + if ([path hasPrefix:@"/"]) { + path = [path substringFromIndex:1]; + } else { + // No relative path support + return (nil); + } + + NSArray *segments = [path componentsSeparatedByString:@"/"]; + + OCYAMLNode *node = nil; + + for (NSString *segName in segments) + { + if (node == nil) + { + for (OCYAMLNode *rootNode in _resultNodes) + { + if ([rootNode.name isEqual:segName]) + { + node = rootNode; + break; + } + } + + if (node != nil) { continue; } + + break; + } + + if (node.childrenByName[segName] != nil) + { + node = node.childrenByName[segName]; + } + } + + return (node); +} + +@end diff --git a/tools/ocapigen/ocapigen.xcodeproj/project.pbxproj b/tools/ocapigen/ocapigen.xcodeproj/project.pbxproj new file mode 100644 index 00000000..bb6a1c22 --- /dev/null +++ b/tools/ocapigen/ocapigen.xcodeproj/project.pbxproj @@ -0,0 +1,379 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + DCFE3BB427A1E7B900939415 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BB327A1E7B900939415 /* main.m */; }; + DCFE3BBF27A1E7E200939415 /* OCYAMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BBE27A1E7E200939415 /* OCYAMLParser.m */; }; + DCFE3BC227A1E84600939415 /* OCYAMLNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BC127A1E84600939415 /* OCYAMLNode.m */; }; + DCFE3BC527A1FB4C00939415 /* OCSchema.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BC427A1FB4C00939415 /* OCSchema.m */; }; + DCFE3BCA27A1FBB200939415 /* OCSchemaProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BC927A1FBB200939415 /* OCSchemaProperty.m */; }; + DCFE3BCD27A1FC5F00939415 /* OCSchemaConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BCC27A1FC5F00939415 /* OCSchemaConstraint.m */; }; + DCFE3BD027A2081900939415 /* OCCodeGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BCF27A2081900939415 /* OCCodeGenerator.m */; }; + DCFE3BD327A2988600939415 /* OCCodeFile.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BD227A2988600939415 /* OCCodeFile.m */; }; + DCFE3BD627A2AA2B00939415 /* OCCodeGeneratorObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BD527A2AA2B00939415 /* OCCodeGeneratorObjC.m */; }; + DCFE3BDC27A2D38B00939415 /* OCCodeFileSegment.m in Sources */ = {isa = PBXBuildFile; fileRef = DCFE3BDB27A2D38B00939415 /* OCCodeFileSegment.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + DCFE3BAE27A1E7B900939415 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + DCFE3BB027A1E7B900939415 /* ocapigen */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ocapigen; sourceTree = BUILT_PRODUCTS_DIR; }; + DCFE3BB327A1E7B900939415 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + DCFE3BBD27A1E7E200939415 /* OCYAMLParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCYAMLParser.h; sourceTree = ""; }; + DCFE3BBE27A1E7E200939415 /* OCYAMLParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCYAMLParser.m; sourceTree = ""; }; + DCFE3BC027A1E84600939415 /* OCYAMLNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCYAMLNode.h; sourceTree = ""; }; + DCFE3BC127A1E84600939415 /* OCYAMLNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCYAMLNode.m; sourceTree = ""; }; + DCFE3BC327A1FB4C00939415 /* OCSchema.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSchema.h; sourceTree = ""; }; + DCFE3BC427A1FB4C00939415 /* OCSchema.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSchema.m; sourceTree = ""; }; + DCFE3BC827A1FBB200939415 /* OCSchemaProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSchemaProperty.h; sourceTree = ""; }; + DCFE3BC927A1FBB200939415 /* OCSchemaProperty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSchemaProperty.m; sourceTree = ""; }; + DCFE3BCB27A1FC5F00939415 /* OCSchemaConstraint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCSchemaConstraint.h; sourceTree = ""; }; + DCFE3BCC27A1FC5F00939415 /* OCSchemaConstraint.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCSchemaConstraint.m; sourceTree = ""; }; + DCFE3BCE27A2081900939415 /* OCCodeGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCodeGenerator.h; sourceTree = ""; }; + DCFE3BCF27A2081900939415 /* OCCodeGenerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCodeGenerator.m; sourceTree = ""; }; + DCFE3BD127A2988600939415 /* OCCodeFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCodeFile.h; sourceTree = ""; }; + DCFE3BD227A2988600939415 /* OCCodeFile.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCodeFile.m; sourceTree = ""; }; + DCFE3BD427A2AA2B00939415 /* OCCodeGeneratorObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCodeGeneratorObjC.h; sourceTree = ""; }; + DCFE3BD527A2AA2B00939415 /* OCCodeGeneratorObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCodeGeneratorObjC.m; sourceTree = ""; }; + DCFE3BDA27A2D38B00939415 /* OCCodeFileSegment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCCodeFileSegment.h; sourceTree = ""; }; + DCFE3BDB27A2D38B00939415 /* OCCodeFileSegment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCCodeFileSegment.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DCFE3BAD27A1E7B900939415 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + DCFE3BA727A1E7B900939415 = { + isa = PBXGroup; + children = ( + DCFE3BC727A1FB5B00939415 /* Code Generation */, + DCFE3BC627A1FB4F00939415 /* YAML Parser */, + DCFE3BB227A1E7B900939415 /* ocapigen */, + DCFE3BB127A1E7B900939415 /* Products */, + ); + sourceTree = ""; + }; + DCFE3BB127A1E7B900939415 /* Products */ = { + isa = PBXGroup; + children = ( + DCFE3BB027A1E7B900939415 /* ocapigen */, + ); + name = Products; + sourceTree = ""; + }; + DCFE3BB227A1E7B900939415 /* ocapigen */ = { + isa = PBXGroup; + children = ( + DCFE3BB327A1E7B900939415 /* main.m */, + ); + path = ocapigen; + sourceTree = ""; + }; + DCFE3BC627A1FB4F00939415 /* YAML Parser */ = { + isa = PBXGroup; + children = ( + DCFE3BBE27A1E7E200939415 /* OCYAMLParser.m */, + DCFE3BBD27A1E7E200939415 /* OCYAMLParser.h */, + DCFE3BC127A1E84600939415 /* OCYAMLNode.m */, + DCFE3BC027A1E84600939415 /* OCYAMLNode.h */, + ); + path = "YAML Parser"; + sourceTree = ""; + }; + DCFE3BC727A1FB5B00939415 /* Code Generation */ = { + isa = PBXGroup; + children = ( + DCFE3BD827A2AB4B00939415 /* Generator */, + DCFE3BD727A2AB3400939415 /* Schema */, + ); + path = "Code Generation"; + sourceTree = ""; + }; + DCFE3BD727A2AB3400939415 /* Schema */ = { + isa = PBXGroup; + children = ( + DCFE3BC427A1FB4C00939415 /* OCSchema.m */, + DCFE3BC327A1FB4C00939415 /* OCSchema.h */, + DCFE3BC927A1FBB200939415 /* OCSchemaProperty.m */, + DCFE3BC827A1FBB200939415 /* OCSchemaProperty.h */, + DCFE3BCC27A1FC5F00939415 /* OCSchemaConstraint.m */, + DCFE3BCB27A1FC5F00939415 /* OCSchemaConstraint.h */, + ); + path = Schema; + sourceTree = ""; + }; + DCFE3BD827A2AB4B00939415 /* Generator */ = { + isa = PBXGroup; + children = ( + DCFE3BCF27A2081900939415 /* OCCodeGenerator.m */, + DCFE3BCE27A2081900939415 /* OCCodeGenerator.h */, + DCFE3BD227A2988600939415 /* OCCodeFile.m */, + DCFE3BD127A2988600939415 /* OCCodeFile.h */, + DCFE3BDB27A2D38B00939415 /* OCCodeFileSegment.m */, + DCFE3BDA27A2D38B00939415 /* OCCodeFileSegment.h */, + DCFE3BD927A2AB5600939415 /* ObjC */, + ); + path = Generator; + sourceTree = ""; + }; + DCFE3BD927A2AB5600939415 /* ObjC */ = { + isa = PBXGroup; + children = ( + DCFE3BD527A2AA2B00939415 /* OCCodeGeneratorObjC.m */, + DCFE3BD427A2AA2B00939415 /* OCCodeGeneratorObjC.h */, + ); + path = ObjC; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + DCFE3BAF27A1E7B900939415 /* ocapigen */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCFE3BB727A1E7B900939415 /* Build configuration list for PBXNativeTarget "ocapigen" */; + buildPhases = ( + DCFE3BAC27A1E7B900939415 /* Sources */, + DCFE3BAD27A1E7B900939415 /* Frameworks */, + DCFE3BAE27A1E7B900939415 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ocapigen; + productName = ocapigen; + productReference = DCFE3BB027A1E7B900939415 /* ocapigen */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + DCFE3BA827A1E7B900939415 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1320; + ORGANIZATIONNAME = "ownCloud GmbH"; + TargetAttributes = { + DCFE3BAF27A1E7B900939415 = { + CreatedOnToolsVersion = 13.2; + }; + }; + }; + buildConfigurationList = DCFE3BAB27A1E7B900939415 /* Build configuration list for PBXProject "ocapigen" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = DCFE3BA727A1E7B900939415; + productRefGroup = DCFE3BB127A1E7B900939415 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + DCFE3BAF27A1E7B900939415 /* ocapigen */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + DCFE3BAC27A1E7B900939415 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCFE3BCD27A1FC5F00939415 /* OCSchemaConstraint.m in Sources */, + DCFE3BD627A2AA2B00939415 /* OCCodeGeneratorObjC.m in Sources */, + DCFE3BBF27A1E7E200939415 /* OCYAMLParser.m in Sources */, + DCFE3BC227A1E84600939415 /* OCYAMLNode.m in Sources */, + DCFE3BDC27A2D38B00939415 /* OCCodeFileSegment.m in Sources */, + DCFE3BC527A1FB4C00939415 /* OCSchema.m in Sources */, + DCFE3BD327A2988600939415 /* OCCodeFile.m in Sources */, + DCFE3BB427A1E7B900939415 /* main.m in Sources */, + DCFE3BD027A2081900939415 /* OCCodeGenerator.m in Sources */, + DCFE3BCA27A1FBB200939415 /* OCSchemaProperty.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + DCFE3BB527A1E7B900939415 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + DCFE3BB627A1E7B900939415 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + DCFE3BB827A1E7B900939415 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4AP2STM4H5; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + DCFE3BB927A1E7B900939415 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4AP2STM4H5; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DCFE3BAB27A1E7B900939415 /* Build configuration list for PBXProject "ocapigen" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCFE3BB527A1E7B900939415 /* Debug */, + DCFE3BB627A1E7B900939415 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DCFE3BB727A1E7B900939415 /* Build configuration list for PBXNativeTarget "ocapigen" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCFE3BB827A1E7B900939415 /* Debug */, + DCFE3BB927A1E7B900939415 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = DCFE3BA827A1E7B900939415 /* Project object */; +} diff --git a/tools/ocapigen/ocapigen.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/tools/ocapigen/ocapigen.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/tools/ocapigen/ocapigen.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/tools/ocapigen/ocapigen/main.m b/tools/ocapigen/ocapigen/main.m new file mode 100644 index 00000000..1d8636df --- /dev/null +++ b/tools/ocapigen/ocapigen/main.m @@ -0,0 +1,115 @@ +// +// main.m +// ocapigen +// +// Created by Felix Schwarz on 26.01.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 "OCYAMLParser.h" +#import "OCSchema.h" +#import "OCCodeGeneratorObjC.h" + +typedef NS_ENUM(NSInteger, CLParameter) +{ + CLParameterNone, + CLParameterSourceYAMLFile, + CLParameterTargetFolder, + CLParameterTargetGenerator +}; + +int main(int argc, const char * argv[]) { + @autoreleasepool { + NSError *error = nil; + NSArray *arguments = NSProcessInfo.processInfo.arguments; + CLParameter parameter = CLParameterNone; + Class generatorClass = Nil; + + NSURL *srcYamlURL = nil; + NSURL *targetFolderURL = nil; + + NSString *workingDirectoryPath = NSFileManager.defaultManager.currentDirectoryPath; + + NSLog(@"Working directory path: %@", workingDirectoryPath); + + for (NSString *argument in arguments) + { + if (parameter == CLParameterNone) + { + if ([argument isEqual:@"--yaml"]) { parameter = CLParameterSourceYAMLFile; } + if ([argument isEqual:@"--target"]) { parameter = CLParameterTargetFolder; } + if ([argument isEqual:@"--generator"]) { parameter = CLParameterTargetGenerator; } + } + else + { + switch (parameter) + { + case CLParameterNone: + break; + + case CLParameterSourceYAMLFile: + if ((srcYamlURL = [NSURL URLWithString:argument]) == nil) + { + srcYamlURL = [NSURL fileURLWithPath:argument]; + } + break; + + case CLParameterTargetFolder: + targetFolderURL = [NSURL fileURLWithPath:argument]; + break; + + case CLParameterTargetGenerator: + if ([argument isEqual:@"objc"]) + { + generatorClass = OCCodeGeneratorObjC.class; + } + break; + } + + parameter = CLParameterNone; + } + } + + if ((srcYamlURL == nil) || (targetFolderURL == nil) || (generatorClass == Nil)) + { + NSLog(@"error: missing parameters."); + return (-1); + } + + NSString *yamlFileContents = [NSString stringWithContentsOfURL:srcYamlURL encoding:NSUTF8StringEncoding error:&error]; + OCYAMLParser *parser = [[OCYAMLParser alloc] initWithFileContents:yamlFileContents]; + + [parser parse]; + + OCYAMLNode *schemasNode = [parser nodeForPath:@"#/components/schemas"]; + + OCCodeGenerator *generator = [[generatorClass alloc] initWithTargetFolder:targetFolderURL]; + + for (OCYAMLNode *schemaNode in schemasNode.children) + { + OCSchema *schema; + + if ((schema = [[OCSchema alloc] initWithYAMLNode:schemaNode]) != nil) + { + [generator addSchema:schema]; + } + + // NSLog(@"%@", schemaNode.childrenByName[@"properties"].childrenByName.allKeys); + } + + [generator generate]; + } + + return 0; +}