SwiftSynology is a Swift library that facilitates interaction with Synology NAS devices, making it easy to integrate various Synology NAS functionalities into applications. The library is designed with a structure optimized for concurrency handling by leveraging Swift Concurrencyβs actor and async/await paradigms. This ensures safe and efficient asynchronous operations while minimizing concurrency-related issues.
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.
Once you have your Swift package set up, adding SwiftSynology as a dependency is as easy as adding it to the dependencies value of your Package.swift or the Package list in Xcode.
import PackageDescription
let package = Package(
name: "YourProject",
dependencies: [
.package(url: "https://github.com/Jaesung-Jung/SwiftSynology.git", .upToNextMajor(from: "0.2.0"))
],
targets: [
.target(
name: "YourProject",
dependencies: [
.product(name: "Synology", package: "SwiftSynology")
]
)
]
)The first step is to create a DiskStation object. DiskStation is an abstraction layer for interacting with DSM. To create a DiskStation, you can either use QuickConnect to automatically find the URL, or explicitly provide the URL.
QuickConnect allows client applications to connect to DSM over the internet without the need to configure port forwarding rules. SynologySwift provides a QuickConnect API that offers an easy way to discover devices.
do {
let quickConnect = QuickConnect()
let diskStation = try await quickConnect.connect(id: <#QuickConnectID#>)
} catch {
switch error {
case QuickConnectError.availableServerNotFound:
print("Available server not found")
default:
print("Network or other errors")
}
}QuickConnect attempts multiple connection methods and connects to the one with the highest priority.
<QuickConnect Priority>
- https
- Connection Type
Smart DNS LAN IPv4
Smart DNS LAN IPv6
LAN IPv4
LAN IPv6
FQDN
DDNS
Smart DNS Host
Smart DNS WAN IPv6
Smart DNS WAN IPv4
WAN IPv6
WAN IPv4
- Dynamic Port
QuickConnect supports both HTTPS and HTTP connections, but HTTP connections may fail due to NSAppTransportSecurity configuration.
You can create a DiskStation object by providing a URL.
let diskStation = DiskStation(serverURL: <#url#>)Unlike QuickConnect, when using a URL, you need to manually check the connection availability to the DSM. To test the DSM connection status, the PingPong API is provided.
let pingPong = PingPoing()
let pong = try await pingPong.ping(to: <#url#>)
print(pong.success) // true or false (Bool)DSM supports multiple languages, and by passing a CodePage value, the strings are configured according to the selected language. By default, the SwiftSynology package automatically configures the CodePage by reading the system language settings.
The supported languages are as follows, and you can pass the CodePage value when creating a DiskStation instance.
public enum CodePage {
case englishUS // English (US)
case chineseTraditional // Chinese (Traditional)
case chineseSimplified // Chinese (Simplified)
case korean // Korean
case german // German
case french // French
case italian // Italian
case spanish // Spanish
case japanese // Japanese
case danish // Danish
case norwegian // Norwegian
case swedish // Swedish
case dutch // Dutch
case russian // Russian
case polish // Polish
case portugueseBrazil // PortugueseBrazil
case portuguesePortugal // PortuguesePortugal
case hungarian // Hungarian
case turkish // Turkish
case czech // Czech
}
// Use QuickConnect
let diskStation = QuickConnect().connect(id: <#QuickConnectID#>, codePage: .englishUS)
// Use URL
let diskStation = DiskStation(serverURL: <#url#>, codePage: .englishUS)The auth() function in DiskStation provides APIs for authentication.
SynologySwift stores the sessionID internally upon a successful login and automatically includes the authentication token when making API requests.
π‘ Since the
sessionIDis lost when the program terminates, it should be stored in a persistent storage likeKeychainto maintain the login session. Further details are explained below.
You can perform a login using the auth().login(account:password:) API.
do {
let authorization = try await diskStation.auth().login(
account: <#account#>,
password: <#password#>
)
} catch let error as AuthError where error == .noSuchAccountOrIncorrectPassword {
// No such account or incorrect password
} catch {
// Network or other errors
}When the connection is successful, an Authorization is generated, which contains the sessionID for authentication. To maintain the authentication, store this value and provide it when creating the DiskStation object.
let diskStation = DiskStation(
serverURL: <#url#>,
sessionID: <#sessionID#> // π
)DSM supports two-factor authentication and provides an API for it. To determine if the account requires two-factor authentication, you should attempt to log in without an OTP value first.
do {
let authorization = try await diskStation.auth().login(
account: <#account#>,
password: <#password#>
)
} catch let error as AuthError where error == .requiredTwoFactorAuthenticationCode {
// Required two factor authentication code
}If a requiredTwoFactorAuthenticationCode error occurs, you need to call login(account:password:otp:) and provide the Auth.OTP value.
do {
let authorization = try await diskStation.auth().login(
account: <#account#>,
password: <#password#>,
otp: Auth.OTP(code: <#otp#>, enableDeviceToken: true)
)
} catch let error as AuthError where error == .incorrectTwoFactorAuthenticationCode {
// Incorrect two factor authentication code
}enableDeviceToken is a value used to set a trusted device. By passing true and storing the deviceID from the Authorization, you can skip OTP authentication on subsequent logins.
let authorization = try await diskStation.auth().login(
account: <#account#>,
password: <#password#>,
deviceID: <#deviceID#> // π
)You can explicitly log out from the device using the logout() API. Performing this action will invalidate the sessionID issued by DSM.
try await diskStation.logout()system() provides an API to retrieve the DSM system status information.
You can use system().health() to retrieve basic information and the status of the DSM.
let health = try await diskStation.system().health()| Property | Type | Description |
|---|---|---|
| hostname | String | Host name of the DSM |
| interfaces | Array<System.Health.Interface> | Network interface of the device |
| status | System.Health.Status | Device status (danger|attention|normal) |
| upTime | TimeInterval | Elapsed time since the device was booted |
| Property | Type | Description |
|---|---|---|
| id | String | Interface ID |
| ip | String | IP Address |
| type | String | Interface Type |
You can use system().info() to retrieve detailed information about the device.
let info = try await diskStation.system().info()| Property | Type | Description |
|---|---|---|
| model | String | Device model name |
| serial | String | Device serial number |
| cpu | System.Info.CPU | CPU info |
| ram | Int | RAM capacity |
| firmwareVersion | String | Firmware version |
| supportsESATA | Bool | ESATA support status |
| ntpEnabled | Bool | NTP usage status |
| ntpServer | String | NTP server name |
| temperature | Int | Device temperature |
| temperatureWarning | Bool | Temperature warning status |
| upTime | TimeInterval | Elapsed time since the device was booted |
| usbDevices | Array<System.Info.USB> | Connected USB devices |
| Property | Type | Description |
|---|---|---|
| clockSpeed | Int | CPU clock speed (hz) |
| coreCount | Int | Number of cpu cores |
| vendor | String | CPU vendor |
| family | String | CPU brand |
| series | String | CPU model |
| Property | Type | Description |
|---|---|---|
| cls | String | Device class |
| pid | String | Device ID |
| vendor | String | Device vendor |
| product | String | Device name |
| rev | String | Revision |
| vid | String |
You can use system().storageInfo() to retrieve detailed information about the connected storage devices.
let storageInfo = try await diskStation.system().storageInfo()| Property | Type | Description |
|---|---|---|
| drives | Array<System.StorageInfo.Drive> | Drive info |
| volumes | Array<System.StorageInfo.Volume> | Volume info |
| Property | Type | Description |
|---|---|---|
| order | Int | Order |
| no | String | Disk number |
| path | String | Disk path |
| type | String | Disk type |
| capacity | UInt64 | Disk capacity |
| model | String | Disk model name |
| status | String | Disk status |
| temp | Int | Disk temperature |
| Property | Type | Description |
|---|---|---|
| name | String | Volume name |
| volumeName | String | Volume name |
| type | String | Volume type |
| status | String | Volume status |
| usedSize | UInt64 | Volume used capacity |
| totalSize | UInt64 | Volume capacity |
Provides an API to retrieve personal settings.
Retrieves the background image set in DSM.
let wallpaperImage = try await diskStation.personalSettings().wallpaper()
// Returns either [UIImage] or [NSImage] depending on the platform.Provides an API to read messages from the Notification Center. The messages are localized based on the configured CodePage value.
π Configure CodePage
let messages = try await diskStation.notification().messages()| Property | Type | Description |
|---|---|---|
| className | String | Message class |
| level | SystemnNotification.Level | Message level(info|warning|error|unknown) |
| date | Date | Receive date |
| title | String | Message title |
| detail | String | Message content |
The FileStation API provides an interface with offset and limit parameters for handling a large number of files. In SwiftSynology, this is represented by a Page structure.
public struct Page<Element> {
public let offset: Int
public let totalCount: Int
public let elements: [Element]
public var isAtEnd: Bool
}Page implements Sequence, Collection, and BidirectionalCollection, allowing you to leverage the functionalities provided by Swift Collection.
fileStation().info() provides overall information about the FileStation service.
let info = try await diskStation.fileStation().info()| Property | Type | Description |
|---|---|---|
| hostname | String | Host name of the DSM |
| isManager | Bool | Administrator status |
| supportFileRequest | Bool | File request support status |
| supportFileSharing | Bool | File share support status |
| supportVirtualProtocols | Array | List of supported Virtual Protocols |
| systemCodepage | CodePage? | System code page |
A shared folder is the primary directory in DSM for storing and managing files and folders. You can retrieve information about shared folders using fileStation().sharedFolders().
// Fetch all shared folders
let sharedFolders = try await diskStation.fileStation().sharedFolders()
// Fetch shared folders, limited to 10
let sharedFolders = try await diskStation.fileStation().sharedFolders(offset: 0, limit: 10)
// Next Page
let sharedFolders = try await diskStation.fileStation().sharedFolders(offset: 10, limit: 10)
// Fetch shared folders, sort by name (ascending)
let sharedFolders = try await diskStation.fileStation().sharedFolders(sortBy: .ascending(.name))
// Fetch shared folders, sort by name (dscdescending)
let sharedFolders = try await diskStation.fileStation().sharedFolders(sortBy: .dscdescending(.name))
// Fetch shared folders with additional info
let sharedFolders = try await diskStation.fileStation().sharedFolders(additionalInfo: [.time, .volumeStatus])| name | type | default | description |
|---|---|---|---|
| offset | Int? | nil | Request offset |
| limit | Int? | nil | Maximum count of request |
| sortBy | SortBy<FileStation.SharedFolderSortAttribute>? | nil | Sorting method |
| additionalInfo | Set<FileStation.SharedFolderAdditionalInfo>? | nil | Additional info |
| onlyWritable | Bool | false | Filter only folders with write permissions |
| Property | Type | Description |
|---|---|---|
| name | String | Name of shared folder |
| path | String | Relative path |
| absolutePath | String? | Absolute path (SharedFolderAdditionalInfo.absolutePath is required when making a request.) |
| mountPointType | String? | Mount point type (SharedFolderAdditionalInfo.mountPointType is required when making a request.) |
| owner | FileStation.Owner? | File owner (SharedFolderAdditionalInfo.owner is required when making a request.) |
| dates | FileStation.Dates? | File creation, modification, change, and access time info (SharedFolderAdditionalInfo.time is required when making a request.) |
| permission | FileStation.Permission? | File permission info (SharedFolderAdditionalInfo.permission is required when making a request.) |
| isReadOnly | Bool? | Read-only status (SharedFolderAdditionalInfo.volumeStatus is required when making a request.) |
| freeSpace | UInt64? | Available space (SharedFolderAdditionalInfo.volumeStatus is required when making a request.) |
| totalSpace | UInt64? | Total space (SharedFolderAdditionalInfo.volumeStatus is required when making a request.) |
| usesSpace | UInt64? | Used space (SharedFolderAdditionalInfo.volumeStatus is required when making a request.) |
In SynologySwift, files and directories are represented as FileStation.File. The package supports operations such as file listing, directory creation, renaming, moving, copying, deleting, directory size calculation, and MD5 hash computation.
The following is a simple example of retrieving a file list.
// Fetch all files
let files = try await diskStation.fileStation().files(at: <#path#>)
// Fetch files, limited to 10
let files = try await diskStation.fileStation().files(at: <#path#>, offset: 0, limit: 10)
// Next Page
let files = try await diskStation.fileStation().files(at: <#path#>, offset: 10, limit: 10)
// Fetch files, sort by name (ascending)
let files = try await diskStation.fileStation().files(at: <#path#>, sortBy: .ascending(.name))
// Fetch files, sort by name (dscdescending)
let files = try await diskStation.fileStation().files(at: <#path#>, sortBy: .dscdescending(.name))
// Fetch shared folders with additional info
let files = try await diskStation.fileStation().files(at: <#path#>, additionalInfo: [.time, .size])| name | type | default | description |
|---|---|---|---|
| path | String | - | path |
| pattern | String? | nil | filter pattern(Glob Pattern) |
| offset | Int? | nil | Request offset |
| limit | Int? | nil | Maximum count of request |
| sortBy | SortBy<FileStation.FileSortAttribute>? | nil | Sorting method |
| additionalInfo | Set<FileStation.FileAdditionalInfo>? | nil | Additional info |
| type | FileStation.FileTypeFilter | nil | .fileOnly | .directoryOnly |
| Property | Type | Description |
|---|---|---|
| name | String | Name of the file |
| path | String | Relative path |
| isDirectory | Bool | Directory status |
| isValid | Bool | Validation status |
| fileExtension | String | File extension |
| size | UInt64? | File size (FileAdditionalInfo.size is required when making a request.) |
| absolutePath | String? | Absolute path (FileAdditionalInfo.absolutePath is required when making a request.) |
| mountPointType | String? | Mount point type (FileAdditionalInfo.mountPointType is required when making a request.) |
| owner | FileStation.Owner? | File owner (FileAdditionalInfo.owner is required when making a request.) |
| dates | FileStation.Dates? | File creation, modification, change, and access time info (FileAdditionalInfo.time is required when making a request.) |
| permission | FileStation.Permission? | File permission info (FileAdditionalInfo.permission is required when making a request.) |
ShareLink is a service that allows you to easily share files or folders stored on a Synology NAS. By sharing the URL or QR code of the ShareLink with others, they can download the selected files or folders regardless of whether they have a DSM account.
This package supports operations such as listing, creating, editing, and deleting ShareLinks.
The following is a simple example of creating a ShareLink.
let shareLink = try await diskStation.fileStation().createShareLink(path: <#filePath#>)| name | type | default | description |
|---|---|---|---|
| path | String | - | File path |
| password | String | nil | Share password |
| availableDate | Date | nil | Availabel date |
| expiredDate | Date | nil | Expired date |
| Property | Type | Description |
|---|---|---|
| id | String | Link ID |
| url | URL | Link URL |
| qrcode | String | QR Code(Base64 encoded) |
| name | String | File name |
| path | String | File path |
| isDirectory | Bool | Directory status |
| status | FileStation.ShareLink.Status | Lin status |
| hasPassword | Bool | Password requirement status |
| owner | String | File owner |
| availableDate | String | Available date (yyyy-MM-dd HH:mm:ss) |
| expiredDate | String | Expired date (yyyy-MM-dd HH:mm:ss) |
Operations such as copying, compression, or MD5 hash computation in FileStation can take a long time. These tasks are classified as BackgroundTasks and managed internally by DSM. In this package, the BackgroundTask<Completed, Processing> is abstracted as an actor, allowing continuous status monitoring using a polling method. BackgroundTask.status() returns the status via an AsyncThrowingStream at intervals specified by pollingInterval until the task is completed.
The following is an example of using BackgroundTask for the copy.
let task = try await diskStation.fileStation()
.copy(
filesPaths: [<#filePath#>],
destinationFilePath: <#destinationFilePath#>,
overwrite: true
)
for try await status in try await task.status(pollingInterval: .seconds(1)) {
switch status {
case .processing(let progress):
print("π \(progress)")
case .completed:
print("π completed")
}
}MIT license. See LICENSE for details.