Skip to content

Commit

Permalink
Merge pull request #33 from Isvvc/v3-documentation
Browse files Browse the repository at this point in the history
v3.0 Documentation
  • Loading branch information
skjiisa authored Apr 22, 2021
2 parents afcd830 + bd6d0f7 commit 7f4aa0b
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 70 deletions.
154 changes: 110 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

WebDAV communication library for Swift

## Table of contents

+ [Install](#install)
+ [Usage](#usage)
+ [Account](#account)
+ [Making requests](#making-requests)
+ [Listing files](#listing-files)
+ [Data cache](#data-cache)
+ [Images](#images)
+ [Thumbnails](#thumbnails)
+ [Theming](#theming)
+ [Upgrading](#upgrading)
+ [Contribution](#contribution)
+ [Adding a WebDAV account](#adding-a-webdav-account)

## Install

Install using Swift Package Manager.
Expand Down Expand Up @@ -58,11 +73,11 @@ Use [URLCredentialStorage](https://developer.apple.com/documentation/foundation/

The path passed into functions should be the path to the file or directory relative to the `baseURL` in your account.

For fuctions that read from or write to a file, this path should include the file name and extension.
For functions that read from or write to a file, this path should include the file name and extension.

#### Functions

The functions currently available include
The basic WebDAV functions currently available include

+ `listFiles`
+ `upload`
Expand All @@ -83,84 +98,121 @@ let path = "file.txt"
let data = "File contents".data(using: .utf8)

webDAV.upload(data: data, toPath: path, account: account, password: password) { error in
// Handle the error
// Check the error
}
```

### Listing Files
### Listing files

The `listFiles` function, if successful, will complete with a `WebDAVFile` array, which will be cached to memory and disk for quick retrieval later.
By default, subsequest calls of `listFiles` on the same path with the same account will give the cached results instead of making a network request.
You can use the `caching options` parameter to change this behavior.
For example, if you want to force a request instead of accessing the cache, you can use `.doNotReturnCachedResult`.
By default, subsequent calls of `listFiles` on the same path with the same account will give the cached results instead of making a network request.
See [Caching behavior](#caching-behavior) for how to change this behavior.

Another useful option is `.requestEvenIfCached`:
A useful option for listing files in particular is `.requestEvenIfCached`:

```swift
webDAV.listFiles(atPath: path, account: account, password: password, caching: .requestEvenIfCached) { files, error in
// Handle the cached files immediately.
// Handle the newly fetched files list after the request is complete.
// Show the cached files immediately.
// Update to show the newly fetched files list after the request is complete.
}
```

In this case, if there are cached files, the completion closure will run immediately with those cached files.
Then a network request will be made to get an updated files list.
If the files list from the server is unchaged from the cache, the function ends here and nothing else is called.
If the files list from the server is unchanged from the cache, the function ends here and nothing else is called.
If the files list from the server is different from the cache, the completion closure will run a second time with the new files list.

### Image cache
The files cache can be cleared using `clearFilesCache`, `clearFilesDiskCache`, or `clearFilesMemoryCache`.

Included is functionality for downloading and caching images.
This is based on [3lvis/Networking](https://github.com/3lvis/Networking).
### Data cache

You can download an image like you would any other file using `downloadImage`.
This will download the image and save it to both an memory and disk cache.
Included is functionality for downloading and caching data, images, and thumbnails (on Nextcloud servers).

#### Functions
Data downloaded using the `download` function, or the various image and thumbnail fetching functions, will cache to both memory and disk.
The memory function is based off of [NSCache](https://developer.apple.com/documentation/foundation/nscache),
meaning it uses Apple's own auto-eviction policies to clear up system memory.
The disk cache data is stored in the `app.lyons.webdav-swift` folder in the [caches directory](https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/cachesdirectory).

Image cache functions include
Data cache functions include

+ `downloadImage`
+ `getCachedData`
+ `deleteCachedData`
+ `getCachedDataURL`
+ `getCachedImage`
+ `deleteAllCachedData`
+ `cancelRequest`
+ `getCacheByteCount`
+ `getCacheSize`
+ `cachedDataURL`
+ `cachedDataURLIfExists`
+ `deleteAllDiskCachedData`

#### Caching behavior

**Default caching behavior** is to return a cached result instead of making a request if there is one.
Otherwise, make a request and cache the result.

Use the `caching options` parameter in functions to change the caching behavior based on `WebDAVCachingOptions`.
Setting the caching options to an empty set will use default behavior.

Caching options include

+ `doNotCacheResult`
+ `doNotReturnCachedResult`
+ `removeExistingCache`
+ Removes the cached result after returning it, if it exists.
+ Use with `doNotReturnCachedResult` if you do not want the cached result returned.
+ `requestEvenIfCached`
+ The completion closure for the request will run once with the cached data,
then again with the newly fetched data, assuming it is different.

Convenience options include

+ `ignoreCache` will ignore the cached result if there is one, and won't cache the new result.
+ Same as `[.doNotCacheResult, .doNotReturnCachedResult]`.
+ `disableCache` disables all caching for the request and deletes any existing cache for it.
+ Same as `[.doNotCacheResult, .removeExistingCache, .doNotReturnCachedResult]`.

### Images

Images can be downloaded and cached using `downloadImage`. This will complete with a `UIImage` if available and caches the same way as the data cache.

Image functions include:

+ `downloadImage`
+ `getCachedImage`

_Why is there no `deleteCachedImage` or `cachedImageURL` function when there is `getCachedThumbnail` and `cachedThumbnailURL`?_
Images are stored in the disk cache the same way as data. The image-specific functions exist as a convenience for converting the data to UIImages and caching them in memory that way. Since the cached data URL does not change whether the data is an image or not, `deleteCachedData` and `cachedDataURL` can be used for images.

#### Thumbnails
### Thumbnails

Along with downloading full-sized images, you can download **thumbnails** from Nextcloud servers.
This currently only works with Nextcloud servers as thumbnail generation is not part of the WebDAV standard.

Thumbnail generation requires you to specify to render the thumbnail with aspect fill or aspect fit can include dimensions.
Thumbnail generation requires you to specify the properties for how the server should render the thumbnail.
These properties exist as `ThumbnailProperties` objects and include dimensions and content mode.
If no dimensions are specified, the server's default will be used (default is 64x64 on Nextcloud).
When getting the URL of or deleting a cached URL, you must also specify these arguments in order to access the correct specific thumbnail.
If you wish to access all thumbnails for a specific image, you can use `getAllCachedThumbnailURLs(forItemAtPath:, account:)` and `deleteAllCachedThumbnails(forItemAtPath:, account:)`.
When getting the URL of or deleting a cached URL, you must also specify these properties in order to access the correct specific thumbnail.
If you wish to access all thumbnails for a specific image at once, you can use `getAllCachedThumbnails` and `deleteAllCachedThumbnails`.

Example:

```swift
func downloadThumbnail<A: WebDAVAccount>(
path: String, account: A, password: String, with dimensions: CGSize?, aspectFill: Bool = true,
completion: @escaping (_ image: UIImage?, _ cachedImageURL: URL?, _ error: WebDAVError?) -> Void
) -> String?
webDAV.downloadThumbnail(path: imagePath, account: account, password: password, with: .init((width: 512, height: 512), contentMode: .fill)) { image, error in
// Check the error
// Display the image
}
```

Thumbnail Functions include
Note that `ThumbnailProperties` objects can also be initialized using a `CGSize`, but doing so will truncate the size to integer pixel counts.

Thumbnail functions include

+ `downloadThumbnail`
+ `getCachedThumbnail`
+ `getAllCachedThumbnails`
+ `deleteCachedThumbnail`
+ `deleteAllCachedThumbnails`
+ `getAllCachedThumbnailURLs`
+ `getAllCachedThumbnails`
+ `getCachedThumbnailURL`
+ `getCachedThumbnail`

#### Cancelling image requests

Unlike the other request functions, `downloadImage` and `downloadThumbnail` do not return a URLSessionTask.
This is because they are based on 3lvis/Networking.
Instead they return a request identifier that can be used to cancel the request using the `cancelRequest` function.
+ `cachedThumbnailURL`
+ `cachedThumbnailURLIfExists`

### Theming

Expand All @@ -173,14 +225,28 @@ Two functions exist for this:
`getNextcloudColorHex` will give the server's accent color as a hex code starting with '#' (eg `#0082c9`).
`getNextcloudTheme` will give the server's full theming information in the form of an `OCSTheme` object.

## Upgrading

Version 2.x used [3lvis/Networking](https://github.com/3lvis/Networking) for its image caching,
but this was replaced with a custom in-house caching solution in 3.0.
If upgrading from v2 to v3, run the function `clearV2Cache()` in order to remove previously cached data.

For example, you could run something like this on startup

```swift
if !UserDefaults.standard.bool(forKey: "webDAV-v3-upgrade") {
try? webDAV.clearV2Cache()
UserDefaults.standard.setValue(true, forKey: "webDAV-v3-upgrade")
}
```

## Contribution

This package depends on [drmohundro/SWXMLHash](https://github.com/drmohundro/SWXMLHash)
and [3lvis/Networking](https://github.com/3lvis/Networking).
which should automatically be fetched by Swift Package Manager in Xcode.

To test any contributions you make, make test functions in `WebDAVTests`.
In order to run tests, you need to pass account information in as environment variables.
In order to run tests, you need to add a WebDAV account to the environment variables as described below.

### Adding a WebDAV account

Expand All @@ -193,6 +259,6 @@ Under Arguments in Test, add the following environment variables:
+ `webdav_user`: The username for your WebDAV account to test with
+ `webdav_password`: The password for your WebDAV account
+ `webdav_url`: The URL of the WebDAV server your account is on
+ `image_path`: The path to an image file in the WebDAV storage
+ `image_path`: The path to an image file of type jpg/jpeg, png, or gif in the WebDAV storage

Note that running the tests will create files on your WebDAV server, though they should also be deleted, assuming all the tests pass.
46 changes: 45 additions & 1 deletion Sources/WebDAV/WebDAV+DiskCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ public extension WebDAV {

//MARK: Data

/// Get the local cached data URL for the item at the specified path.
///
/// Gives the URL the data would be cached at wether or not there is any data cached there.
/// - Parameters:
/// - path: The path that would be used to download the data.
/// - account: The WebDAV account that would be used to download the data.
/// - Returns: The URL where the data is or would be cached.
func cachedDataURL<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> URL? {
guard let encodedDescription = UnwrappedAccount(account: account)?.encodedDescription,
let caches = cacheFolder else { return nil }
Expand All @@ -21,16 +28,28 @@ public extension WebDAV {
.appendingPathComponent(path.trimmingCharacters(in: AccountPath.slash))
}

/// Get the local cached data URL for the item at the specified path if there is cached data there.
/// - Parameters:
/// - path: The path used to download the data.
/// - account: The WebDAV account used to download the data.
/// - Returns: The URL of the cached data, if it exists.
func cachedDataURLIfExists<A: WebDAVAccount>(forItemAtPath path: String, account: A) -> URL? {
guard let url = cachedDataURL(forItemAtPath: path, account: account) else { return nil }
return FileManager.default.fileExists(atPath: url.path) ? url : nil
}

/// Delete the cached data for the item at the specified path from the disk cache.
/// - Parameters:
/// - path: The path used to download the data.
/// - account: The WebDAV account used to download the data.
/// - Throws: An error if the file couldn't be deleted.
func deleteCachedDataFromDisk<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
guard let url = cachedDataURLIfExists(forItemAtPath: path, account: account) else { return }
try FileManager.default.removeItem(at: url)
}

/// Delete all cached data from the disk cache.
/// - Throws: An error if the files couldn't be deleted.
func deleteAllDiskCachedData() throws {
guard let url = cacheFolder else { return }
let fm = FileManager.default
Expand All @@ -42,10 +61,18 @@ public extension WebDAV {

//MARK: Thumbnails

/// Get the local cached thumbnail URL for the image at the specified path.
///
/// Gives the URL the thumbnail would be cached at wether or not there is any data cached there.
/// - Parameters:
/// - path: The path that would be used to download the thumbnail.
/// - account: The WebDAV account that would be used to download the thumbnail.
/// - properties: The properties of the thumbnail.
/// - Returns: The URL where the thumbnail is or would be cached.
func cachedThumbnailURL<A: WebDAVAccount>(forItemAtPath path: String, account: A, with properties: ThumbnailProperties) -> URL? {
guard let imageURL = cachedDataURL(forItemAtPath: path, account: account) else { return nil }

// If the query is stored in the URL as an actualy query, it won't be included when
// If the query is stored in the URL as an actual query, it won't be included when
// saving to a file, so we have to manually add the query to the filename here.
let directory = imageURL.deletingLastPathComponent()
var filename = imageURL.lastPathComponent
Expand All @@ -55,16 +82,33 @@ public extension WebDAV {
return directory.appendingPathComponent(filename)
}

/// Get the local cached thumbnail URL for the image at the specified path if there is a cached thumbnail there.
/// - Parameters:
/// - path: The path used to download the thumbnail.
/// - account: The WebDAV account used to download the thumbnail.
/// - properties: The properties of the thumbnail.
/// - Returns: The URL of the cached thumbnail, if it exists.
func cachedThumbnailURLIfExists<A: WebDAVAccount>(forItemAtPath path: String, account: A, with properties: ThumbnailProperties) -> URL? {
guard let url = cachedThumbnailURL(forItemAtPath: path, account: account, with: properties) else { return nil }
return FileManager.default.fileExists(atPath: url.path) ? url : nil
}

/// Delete the cached thumbnail for the image at the specified path from the disk cache.
/// - Parameters:
/// - path: The path used to download the thumbnail.
/// - account: The WebDAV account used to download the thumbnail.
/// - properties: The properties of the thumbnail.
/// - Throws: An error if the file couldn't be deleted.
func deleteCachedThumbnailFromDisk<A: WebDAVAccount>(forItemAtPath path: String, account: A, with properties: ThumbnailProperties) throws {
guard let url = cachedThumbnailURLIfExists(forItemAtPath: path, account: account, with: properties) else { return }
try FileManager.default.removeItem(at: url)
}

/// Delete the cached thumbnails for the image at the specified path from the disk cache.
/// - Parameters:
/// - path: The path used to download the thumbnails.
/// - account: The WebDAV account used to download the thumbnails.
/// - Throws: An error if the files couldn't be deleted.
func deleteAllCachedThumbnailsFromDisk<A: WebDAVAccount>(forItemAtPath path: String, account: A) throws {
let fm = FileManager.default
guard let url = cachedDataURL(forItemAtPath: path, account: account) else { return }
Expand Down
Loading

0 comments on commit 7f4aa0b

Please sign in to comment.