synology 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +8 -0
- data/lib/synology/client.rb +286 -0
- data/lib/synology/configuration.rb +19 -0
- data/lib/synology/error.rb +140 -0
- data/lib/synology/version.rb +5 -0
- data/lib/synology.rb +17 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 691973268b923bf903f848351db9525ed49d3b856e844a98dd705cc6d0d135f4
|
4
|
+
data.tar.gz: 8098dcffd55f22c2ed5b1c614109bdf7e1b2b4c123010c46dfd9a60521553c03
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d512713deba8261ea8eb57f7db8c94ac4ab15ec08d9973f4df726df168adf82530c5ecf5ff135833e68104de0ed86fc7c1a9ae85d9c63558b806e5595408e2aa
|
7
|
+
data.tar.gz: '08d006a8c22509960393172d0d640a4958aaa135ffb085260476434b5b45446d66511ecd8ff986124d8d15a72df9ed40dc8cb5d9aa8d8a4f8206979d64667962'
|
data/.rspec
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Henry Maestu
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# Synology
|
2
|
+
|
3
|
+
Synology NAS API client written in Ruby.
|
4
|
+
|
5
|
+
Full implantation of Synology API based on this documentation: https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/FileStation/All/enu/Synology_File_Station_API_Guide.pdf
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'synology'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Have some other Synology Service exposed define endpoints in config.
|
21
|
+
```
|
22
|
+
Synology.configure do |config|
|
23
|
+
|
24
|
+
....
|
25
|
+
# Define your own endpoints
|
26
|
+
# May break code with POST request...
|
27
|
+
# Example: lib/synology/client.rb
|
28
|
+
config.api_endpoint = {
|
29
|
+
info: { api: 'SYNO.FileStation.Info', version: 2, method: 'get', http_method: 'GET' },
|
30
|
+
# SYNO.API.Auth
|
31
|
+
login: { api: 'SYNO.API.Auth', version: 3, method: 'login', http_method: 'GET' },
|
32
|
+
logout: { api: 'SYNO.API.Auth', version: 1, method: 'logout', http_method: 'GET' }
|
33
|
+
...
|
34
|
+
}
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### Basic usages
|
39
|
+
|
40
|
+
```
|
41
|
+
Synology.configure do |config|
|
42
|
+
config.host = 'https://finds.synology.com'
|
43
|
+
config.username = 'admin'
|
44
|
+
config.password = 'password'
|
45
|
+
config.https = true
|
46
|
+
end
|
47
|
+
|
48
|
+
client = Synology::Client.new
|
49
|
+
# Login
|
50
|
+
client.login(account: client.username, passwd: client.password)
|
51
|
+
|
52
|
+
# List files in folder
|
53
|
+
client.list_folder(folder_path: '/my_folder', additional: '["real_path", "size", "owner"]')
|
54
|
+
|
55
|
+
# Upload a file
|
56
|
+
client.upload(path: '/my_folder', file: {file_name:'testing', file_content: 'Testing123'})
|
57
|
+
# Download a file body Base64.strict_encoded
|
58
|
+
client.download(path: '/my_folder/testing')
|
59
|
+
|
60
|
+
client.close
|
61
|
+
|
62
|
+
```
|
63
|
+
|
64
|
+
Authentication Methods
|
65
|
+
|
66
|
+
client.login(account: , passwd:)# Authenticate with Synology NAS
|
67
|
+
client.logout # End the current session
|
68
|
+
|
69
|
+
#### File and Folder Operations
|
70
|
+
|
71
|
+
List & Info
|
72
|
+
|
73
|
+
client.list_share # List all shared folders
|
74
|
+
client.list_folder(folder_path:, additional:) # List contents of a folder
|
75
|
+
client.list_get_info # Get information about files/folders
|
76
|
+
|
77
|
+
Search Operations
|
78
|
+
|
79
|
+
client.search_start(folder_path:, pattern:) # Start a search operation
|
80
|
+
client.search_list(taskid:) # List search results
|
81
|
+
client.search_stop(taskid:) # Stop a search operation
|
82
|
+
client.search_clean(taskid:) # Clean up search results
|
83
|
+
|
84
|
+
Virtual Folder Operations
|
85
|
+
|
86
|
+
client.list_all_mount_points(type:, additional:) # List mount points of virtual file systems
|
87
|
+
|
88
|
+
Favorite Management
|
89
|
+
|
90
|
+
client.favorite_list # List favorite folders
|
91
|
+
client.favorite_add(path:, name:) # Add folder to favorites
|
92
|
+
client.favorite_edit(path:, name:) # Edit favorite folder
|
93
|
+
client.favorite_delete(path:) # Remove from favorites
|
94
|
+
client.favorite_clear_broken # Clear broken favorite links
|
95
|
+
|
96
|
+
Thumbnail Operations
|
97
|
+
|
98
|
+
client.get_thumbnail(path:) # Get thumbnail of a file
|
99
|
+
|
100
|
+
Directory Operations
|
101
|
+
|
102
|
+
client.dir_start(path:) # Start directory size calculation
|
103
|
+
client.dir_status(taskid:) # Check directory size calculation status
|
104
|
+
client.dir_stop(taskid:) # Stop directory size calculation
|
105
|
+
|
106
|
+
MD5 Operations
|
107
|
+
|
108
|
+
client.md5_start(file_path:) # Start MD5 calculation
|
109
|
+
client.md5_status(taskid:) # Check MD5 calculation status
|
110
|
+
client.md5_stop(taskid:) # Stop MD5 calculation
|
111
|
+
|
112
|
+
#### File Management
|
113
|
+
|
114
|
+
Basic Operations
|
115
|
+
|
116
|
+
client.check_permission(path:, filename:) # Check file/folder permissions
|
117
|
+
client.upload(path:, file:{file_name:, file_content:}) # Upload files
|
118
|
+
client.download(path:) # Download files
|
119
|
+
client.create_folder(folder_path:, name:) # Create new folder
|
120
|
+
client.rename(path:, name:) # Rename file/folder
|
121
|
+
client.delete(path:) # Delete file/folder immediately
|
122
|
+
|
123
|
+
Sharing Operations
|
124
|
+
|
125
|
+
client.sharing_get_info(id:) # Get sharing link info
|
126
|
+
client.sharing_links_list # List all sharing links
|
127
|
+
client.sharing_link_create(path:) # Create sharing link
|
128
|
+
client.sharing_link_delete(id:) # Delete sharing link
|
129
|
+
client.sharing_link_edit(id:) # Edit sharing link
|
130
|
+
client.sharing_link_clear_invalid # Clear invalid sharing links
|
131
|
+
|
132
|
+
Asynchronous Operations
|
133
|
+
|
134
|
+
client.copy_move_start(path:, dest_folder_path:) # Start copy/move operation
|
135
|
+
client.copy_move_status(taskid:) # Check copy/move status
|
136
|
+
client.copy_move_stop(taskid:) # Stop copy/move operation
|
137
|
+
|
138
|
+
Delete Operations
|
139
|
+
|
140
|
+
client.delete_async_start(path:) # Start asynchronous delete
|
141
|
+
client.delete_async_status(taskid:) # Check delete status
|
142
|
+
client.delete_async_stop(taskid:) # Stop delete operation
|
143
|
+
|
144
|
+
Archive Operations
|
145
|
+
|
146
|
+
client.extract_start(file_path:, dest_folder_path:) # Start extraction
|
147
|
+
client.extract_status(taskid:) # Check extraction status
|
148
|
+
client.extract_stop(taskid:) # Stop extraction
|
149
|
+
client.extract_list(file_path:) # List archive contents
|
150
|
+
client.compress_start(path:, dest_file_path:) # Start compression
|
151
|
+
client.compress_status(taskid:) # Check compression status
|
152
|
+
client.compress_stop(taskid:) # Stop compression
|
153
|
+
client.compress_list(file_path:) # List compressed file contents
|
154
|
+
|
155
|
+
Background Tasks
|
156
|
+
|
157
|
+
client.background_task_list # List background tasks
|
158
|
+
client.background_task_clear_finished(taskid:) # Clear finished background tasks
|
159
|
+
|
160
|
+
## Contributing
|
161
|
+
|
162
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dragonwebeu/synology.
|
163
|
+
|
164
|
+
## License
|
165
|
+
|
166
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'uri'
|
6
|
+
require 'openssl'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
module Synology
|
10
|
+
class Client
|
11
|
+
API_ENDPOINT = 'webapi/entry.cgi'
|
12
|
+
|
13
|
+
# API endpoints definition using metaprogramming
|
14
|
+
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/FileStation/All/enu/Synology_File_Station_API_Guide.pdf
|
15
|
+
API_ENDPOINTS = {
|
16
|
+
info: { api: 'SYNO.FileStation.Info', version: 2, method: 'get', http_method: 'GET' },
|
17
|
+
# SYNO.API.Auth
|
18
|
+
login: { api: 'SYNO.API.Auth', version: 3, method: 'login', http_method: 'GET' },
|
19
|
+
logout: { api: 'SYNO.API.Auth', version: 1, method: 'logout', http_method: 'GET' },
|
20
|
+
# SYNO.FileStation.List
|
21
|
+
list: {
|
22
|
+
#client.list_share
|
23
|
+
share: { api: 'SYNO.FileStation.List', version: 2, method: 'list_share', http_method: 'GET' },
|
24
|
+
# client.list_folder(folder_path: '/my_folder', additional: '["real_path", "size", "owner"]')
|
25
|
+
folder: { api: 'SYNO.FileStation.List', version: 2, method: 'list', http_method: 'GET' },
|
26
|
+
get_info: { api: 'SYNO.FileStation.List', version: 2, method: 'getinfo', http_method: 'GET' }
|
27
|
+
},
|
28
|
+
# SYNO.FileStation.Search
|
29
|
+
search: {
|
30
|
+
# client.search_start(folder_path: '/my_folder', pattern: 'test')
|
31
|
+
start: { api: 'SYNO.FileStation.Search', version: 2, method: 'start', http_method: 'GET' },
|
32
|
+
# client.search_list(taskid: '173126991043E83CB8')
|
33
|
+
list: { api: 'SYNO.FileStation.Search', version: 2, method: 'list', http_method: 'GET' },
|
34
|
+
# client.search_stop(taskid: '173126991043E83CB8')
|
35
|
+
stop: { api: 'SYNO.FileStation.Search', version: 2, method: 'stop', http_method: 'GET' },
|
36
|
+
# client.search_clean(taskid: '173126991043E83CB8')
|
37
|
+
clean: { api: 'SYNO.FileStation.Search', version: 2, method: 'clean', http_method: 'GET' }
|
38
|
+
},
|
39
|
+
# SYNO.FileStation.VirtualFolder
|
40
|
+
# List all mount point folders of virtual file system, e.g., CIFS or ISO
|
41
|
+
# client.list_all_mount_points(type: 'cifs', additional: '["real_path","owner","time","perm","mount_point_type","volume_status"]')
|
42
|
+
list_all_mount_points: { api: 'SYNO.FileStation.VirtualFolder', version: 2, method: 'list', http_method: 'GET' },
|
43
|
+
favorite: {
|
44
|
+
# client.favorite_list
|
45
|
+
list: { api: 'SYNO.FileStation.Favorite', version: 2, method: 'list', http_method: 'GET' },
|
46
|
+
# client.favorite_add(path: '/my_folder', name: 'Henry')
|
47
|
+
add: { api: 'SYNO.FileStation.Favorite', version: 2, method: 'add', http_method: 'GET' },
|
48
|
+
# client.favorite_edit(path: '/my_folder', name: 'Henry2')
|
49
|
+
edit: { api: 'SYNO.FileStation.Favorite', version: 2, method: 'add', http_method: 'GET' },
|
50
|
+
# client.favorite_delete(path: '/my_folder')
|
51
|
+
delete: { api: 'SYNO.FileStation.Favorite', version: 2, method: 'delete', http_method: 'GET' },
|
52
|
+
# client.favorite_clear_broken
|
53
|
+
clear_broken: { api: 'SYNO.FileStation.Favorite', version: 2, method: 'clear_broken', http_method: 'GET' }
|
54
|
+
},
|
55
|
+
# SYNO.FileStation.Thumb
|
56
|
+
# Get a thumbnail of a file.
|
57
|
+
# client.get_thumbnail(path:'/my_folder')
|
58
|
+
get_thumbnail: { api: 'SYNO.FileStation.Thumb', version: 2, method: 'get', http_method: 'GET' },
|
59
|
+
# SYNO.FileStation.DirSize
|
60
|
+
dir: {
|
61
|
+
# client.dir_start(path: '/my_folder')
|
62
|
+
start: { api: 'SYNO.FileStation.DirSize', version: 2, method: 'start', http_method: 'GET' },
|
63
|
+
# client.dir_status(taskid: '1731273375F0B260B3')
|
64
|
+
status: { api: 'SYNO.FileStation.DirSize', version: 2, method: 'status', http_method: 'GET' },
|
65
|
+
# client.dir_stop(taskid: '1731273375F0B260B3')
|
66
|
+
stop: { api: 'SYNO.FileStation.DirSize', version: 2, method: 'status', http_method: 'GET' }
|
67
|
+
},
|
68
|
+
# SYNO.FileStation.MD5
|
69
|
+
md5: {
|
70
|
+
# client.md5_start(file_path: '/my_folder/some_move.mov')
|
71
|
+
start: { api: 'SYNO.FileStation.MD5', version: 2, method: 'start', http_method: 'GET' },
|
72
|
+
# client.md5_status(taskid: '1731274183A791CF18')
|
73
|
+
status: { api: 'SYNO.FileStation.MD5', version: 2, method: 'status', http_method: 'GET' },
|
74
|
+
# client.md5_stop(taskid: '1731274183A791CF18')
|
75
|
+
stop: { api: 'SYNO.FileStation.MD5', version: 2, method: 'stop', http_method: 'GET' }
|
76
|
+
},
|
77
|
+
# SYNO.FileStation.CheckPermission
|
78
|
+
# client.check_permission(path: '', filename: '')
|
79
|
+
check_permission: { api: 'SYNO.FileStation.CheckPermission', version: 3, method: 'write', http_method: 'GET' },
|
80
|
+
#SYNO.FileStation.Upload
|
81
|
+
# upload = client.upload(path: '/my_folder', file: {file_name:'testing', file_content: 'Testing123'})
|
82
|
+
upload: { api: 'SYNO.FileStation.Upload', version: 2, method: 'upload', http_method: 'POST' }, # Only POST method in the API
|
83
|
+
# download_file = client.download(path: '/my_folder/testing')
|
84
|
+
download: { api: 'SYNO.FileStation.Download', version: 2, method: 'download', http_method: 'GET' },
|
85
|
+
# SYNO.FileStation.Sharing
|
86
|
+
sharing: {
|
87
|
+
# sharing_link = client.sharing_get_info(id: '12345')
|
88
|
+
get_info: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'getinfo', http_method: 'GET' },
|
89
|
+
# client.sharing_links_list
|
90
|
+
links_list: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'list', http_method: 'GET' },
|
91
|
+
# client.sharing_link_create(path: '/my_folder')
|
92
|
+
link_create: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'create', http_method: 'GET' },
|
93
|
+
# client.sharing_link_delete(id: '12345')
|
94
|
+
link_delete: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'delete', http_method: 'GET' },
|
95
|
+
# # client.sharing_link_edit(id: '12345')
|
96
|
+
link_edit: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'edit', http_method: 'GET' },
|
97
|
+
# client.sharing_link_clear_invalid
|
98
|
+
link_clear_invalid: { api: 'SYNO.FileStation.Sharing', version: 3, method: 'clear_invalid', http_method: 'GET' }
|
99
|
+
},
|
100
|
+
# SYNO.FileStation.CreateFolder
|
101
|
+
# client.create_folder(folder_path: '/my_folder', name: 'testing1')
|
102
|
+
create_folder: { api: 'SYNO.FileStation.CreateFolder', version: 2, method: 'create', http_method: 'GET' },
|
103
|
+
# SYNO.FileStation.Rename
|
104
|
+
# client.rename(path: '/my_folder/Testing1', name: 'Testing2')
|
105
|
+
rename: { api: 'SYNO.FileStation.Rename', version: 2, method: 'rename', http_method: 'GET' },
|
106
|
+
# SYNO.FileStation.CopyMove
|
107
|
+
copy_move: {
|
108
|
+
# client.copy_move_start(path: '/my_folder/testing', dest_folder_path: '/my_folder/Testing2')
|
109
|
+
start: { api: 'SYNO.FileStation.CopyMove', version: 3, method: 'start', http_method: 'GET' },
|
110
|
+
# client.copy_move_status(taskid: 'FileStation_51D00B7912CDE0B0')
|
111
|
+
status: { api: 'SYNO.FileStation.CopyMove', version: 3, method: 'status', http_method: 'GET' },
|
112
|
+
# client.copy_move_stop(taskid: 'FileStation_51D00B7912CDE0B0')
|
113
|
+
stop: { api: 'SYNO.FileStation.CopyMove', version: 3, method: 'stop', http_method: 'GET' },
|
114
|
+
},
|
115
|
+
# SYNO.FileStation.Delete
|
116
|
+
#
|
117
|
+
delete_async: {
|
118
|
+
# client.delete_async_start(path: '/my_folder/testing')
|
119
|
+
start: { api: 'SYNO.FileStation.Delete', version: 2, method: 'start', http_method: 'GET' },
|
120
|
+
# client.delete_async_status(taskid: 'FileStation_51CEC9C979340E5A')
|
121
|
+
status: { api: 'SYNO.FileStation.Delete', version: 2, method: 'status', http_method: 'GET' },
|
122
|
+
# client.delete_async_stop(taskid: 'FileStation_51CEC9C979340E5A')
|
123
|
+
stop: { api: 'SYNO.FileStation.Delete', version: 2, method: 'stop', http_method: 'GET' }
|
124
|
+
},
|
125
|
+
# client.delete(path: '/my_folder/testing')
|
126
|
+
delete: { api: 'SYNO.FileStation.Delete', version: 2, method: 'delete', http_method: 'GET' },
|
127
|
+
# SYNO.FileStation.Extract
|
128
|
+
extract: {
|
129
|
+
# client.extract_start(file_path: '/my_folder/testing.zip', dest_folder_path: '/my_folder')
|
130
|
+
start: { api: 'SYNO.FileStation.Extract', version: 2, method: 'start', http_method: 'GET' },
|
131
|
+
# client.extract_status(taskid: 'FileStation_51CBB59C68EFE6A3')
|
132
|
+
status: { api: 'SYNO.FileStation.Extract', version: 2, method: 'status', http_method: 'GET' },
|
133
|
+
# client.extract_stop(taskid: 'FileStation_51CBB59C68EFE6A3')
|
134
|
+
stop: { api: 'SYNO.FileStation.Extract', version: 2, method: 'stop', http_method: 'GET' },
|
135
|
+
# client.extract_list(file_path: '/my_folder/testing.zip')
|
136
|
+
list: { api: 'SYNO.FileStation.Extract', version: 2, method: 'list', http_method: 'GET' }
|
137
|
+
},
|
138
|
+
#SYNO.FileStation.Compress
|
139
|
+
compress: {
|
140
|
+
# client.compress_start(path: '/my_folder/testing', dest_file_path: '/my_folder')
|
141
|
+
start: { api: 'SYNO.FileStation.Compress', version: 3, method: 'start', http_method: 'GET' },
|
142
|
+
# client.compress_status(taskid: 'FileStation_51CBB25CC31961FD')
|
143
|
+
status: { api: 'SYNO.FileStation.Compress', version: 3, method: 'status', http_method: 'GET' },
|
144
|
+
# client.compress_stop(taskid: 'FileStation_51CBB25CC31961FD')
|
145
|
+
stop: { api: 'SYNO.FileStation.Compress', version: 3, method: 'stop', http_method: 'GET' },
|
146
|
+
# client.compress_list(file_path: '/my_folder/testing.zip')
|
147
|
+
list: { api: 'SYNO.FileStation.Compress', version: 3, method: 'status', http_method: 'GET' }
|
148
|
+
},
|
149
|
+
background_task: {
|
150
|
+
# client.background_task_list
|
151
|
+
list: { api: 'SYNO.FileStation.BackgroundTask', version: 3, method: 'list', http_method: 'GET' },
|
152
|
+
# client.background_task_clear_finished(taskid: 'FileStation_51D530978633C014')
|
153
|
+
clear_finished: { api: 'SYNO.FileStation.BackgroundTask', version: 3, method: 'clear_finished', http_method: 'GET' }
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
|
158
|
+
attr_reader :username, :password
|
159
|
+
attr_accessor :sid
|
160
|
+
|
161
|
+
def initialize
|
162
|
+
@config = Synology.configuration
|
163
|
+
@username = @config.username
|
164
|
+
@password = @config.password
|
165
|
+
@sid = nil
|
166
|
+
@api_endpoint = @config.api_endpoint || API_ENDPOINT
|
167
|
+
# API_ENDPOINTS
|
168
|
+
@api_endpoints = @config.api_endpoints || API_ENDPOINTS
|
169
|
+
generate_api_methods
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def generate_api_methods
|
175
|
+
@api_endpoints.each do |method_name, endpoints|
|
176
|
+
unless endpoints.dig(:api)
|
177
|
+
endpoints.each do |method_name2, endpoints2|
|
178
|
+
nested_method_name = "#{method_name}_#{method_name2}"
|
179
|
+
define_singleton_method(nested_method_name) do |params = {}|
|
180
|
+
execute_request(endpoints2, params)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
define_singleton_method(method_name) do |params = {}|
|
185
|
+
execute_request(endpoints, params)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def execute_request(endpoint_info, params)
|
192
|
+
uri = URI("#{@config.base_url}/#{API_ENDPOINT}/#{endpoint_info[:path]}")
|
193
|
+
request_params = {
|
194
|
+
api: endpoint_info[:api],
|
195
|
+
version: endpoint_info[:version],
|
196
|
+
method: endpoint_info[:method]
|
197
|
+
}.merge(params)
|
198
|
+
|
199
|
+
# Todo: cleanup or get sid other way
|
200
|
+
request_params[:_sid] = @sid if @sid && @use_cookies
|
201
|
+
|
202
|
+
response = make_request(uri, request_params, endpoint_info[:http_method])
|
203
|
+
handle_response(response, request_params)
|
204
|
+
end
|
205
|
+
|
206
|
+
def make_request(uri, params, request_type='GET')
|
207
|
+
# Todo: find better solution for this
|
208
|
+
if params[:method] == 'upload'
|
209
|
+
file = params[:file]
|
210
|
+
params.delete(:file)
|
211
|
+
|
212
|
+
if file[:file_content].nil?
|
213
|
+
raise "File content can't be nil!"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
uri.query = URI.encode_www_form(params)
|
217
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
218
|
+
if @config.https
|
219
|
+
https.use_ssl = true
|
220
|
+
# https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
221
|
+
end
|
222
|
+
|
223
|
+
request = Net::HTTP::Get.new(uri) if request_type.upcase == 'GET'
|
224
|
+
if request_type.upcase == 'POST'
|
225
|
+
request = Net::HTTP::Post.new(uri)
|
226
|
+
boundary = rand(100000)
|
227
|
+
request.body = upload_body(params, file, boundary).join
|
228
|
+
request["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
|
229
|
+
end
|
230
|
+
unless @sid.nil?
|
231
|
+
request['Cookie'] = "#{@sid}"
|
232
|
+
end
|
233
|
+
response = https.request(request)
|
234
|
+
if response.header['set-cookie']
|
235
|
+
@sid = response.header['set-cookie']
|
236
|
+
end
|
237
|
+
response
|
238
|
+
end
|
239
|
+
|
240
|
+
def handle_response(response, params)
|
241
|
+
# Todo: Cleanup this later and find better solution
|
242
|
+
if params[:method] == 'download' && response.code == '200'
|
243
|
+
response.body = {body: Base64.strict_encode64(response.body), content_type: response['Content-Type'], success: true}.to_json
|
244
|
+
end
|
245
|
+
if response.is_a?(Net::HTTPSuccess)
|
246
|
+
return parse_json(response.body, params)
|
247
|
+
end
|
248
|
+
|
249
|
+
raise Error, "HTTP Request failed: #{response.code} #{response.message}"
|
250
|
+
end
|
251
|
+
|
252
|
+
def parse_json(body, params)
|
253
|
+
json_body = JSON.parse(body)
|
254
|
+
# Todo: Change is so it runnable by config settings
|
255
|
+
unless json_body['error'].nil?
|
256
|
+
Synology::Error.raise_error(json_body['error']['code'], params[:api])
|
257
|
+
end
|
258
|
+
json_body
|
259
|
+
rescue JSON::ParserError
|
260
|
+
raise Error, "Invalid JSON response"
|
261
|
+
end
|
262
|
+
|
263
|
+
def upload_body(params, file, boundary)
|
264
|
+
post_body = []
|
265
|
+
post_body << upload_params_to_form_data(params, boundary)
|
266
|
+
post_body << "--#{boundary}\r\n"
|
267
|
+
post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{file[:file_name]}\"\r\n"
|
268
|
+
post_body << "Content-Type: application/octet-stream\r\n\r\n"
|
269
|
+
post_body << file[:file_content]
|
270
|
+
post_body << "\r\n--#{boundary}--\r\n"
|
271
|
+
post_body
|
272
|
+
end
|
273
|
+
|
274
|
+
def upload_params_to_form_data(params, boundary)
|
275
|
+
params_body = []
|
276
|
+
params.each do |param|
|
277
|
+
params_body << "--#{boundary}\r\n"
|
278
|
+
params_body << "Content-Disposition: form-data; name=\"#{param[0].to_s}\"\r\n\r\n"
|
279
|
+
params_body << "#{param[1].to_s}\r\n"
|
280
|
+
end
|
281
|
+
params_body
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Synology
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :host, :port, :username, :password, :use_cookies, :https, :api_endpoints, :api_endpoint, :parse_errors
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@api_endpoints = nil
|
7
|
+
@api_endpoint = nil
|
8
|
+
# Syno default port
|
9
|
+
@port = 5000
|
10
|
+
@https = false
|
11
|
+
@use_cookies = true
|
12
|
+
@parse_errors = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_url
|
16
|
+
"#{https ? 'https' : 'http'}://#{host}:#{port}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Synology
|
2
|
+
class Error < StandardError
|
3
|
+
|
4
|
+
COMMON_ERRORS = {
|
5
|
+
100 => "Unknown error",
|
6
|
+
101 => "No parameter of API, method or version",
|
7
|
+
102 => "The requested API does not exist",
|
8
|
+
103 => "The requested method does not exist",
|
9
|
+
104 => "The requested version does not support the functionality",
|
10
|
+
105 => "The logged in session does not have permission",
|
11
|
+
106 => "Session timeout",
|
12
|
+
107 => "Session interrupted by duplicate login",
|
13
|
+
119 => "SID not found",
|
14
|
+
400 => "Invalid parameter of file operation",
|
15
|
+
401 => "Unknown error of file operation",
|
16
|
+
402 => "System is too busy",
|
17
|
+
403 => "Invalid user does this file operation",
|
18
|
+
404 => "Invalid group does this file operation",
|
19
|
+
405 => "Invalid user and group does this file operation",
|
20
|
+
406 => "Can't get user/group information from the account server",
|
21
|
+
407 => "Operation not permitted",
|
22
|
+
408 => "No such file or directory",
|
23
|
+
409 => "Non-supported file system",
|
24
|
+
410 => "Failed to connect internet-based file system (e.g., CIFS)",
|
25
|
+
411 => "Read-only file system",
|
26
|
+
412 => "Filename too long in the non-encrypted file system",
|
27
|
+
413 => "Filename too long in the encrypted file system",
|
28
|
+
414 => "File already exists",
|
29
|
+
415 => "Disk quota exceeded",
|
30
|
+
416 => "No space left on device",
|
31
|
+
417 => "Input/output error",
|
32
|
+
418 => "Illegal name or path",
|
33
|
+
419 => "Illegal file name",
|
34
|
+
420 => "Illegal file name on FAT file system",
|
35
|
+
421 => "Device or resource busy",
|
36
|
+
599 => "No such task of the file operation"
|
37
|
+
}
|
38
|
+
|
39
|
+
AUTH_ERRORS = {
|
40
|
+
400 => "No such account or incorrect password",
|
41
|
+
401 => "Account disabled",
|
42
|
+
402 => "Permission denied",
|
43
|
+
403 => "2-step verification code required",
|
44
|
+
404 => "Failed to authenticate 2-step verification code"
|
45
|
+
}
|
46
|
+
|
47
|
+
# SYNO.FileStation.Favorite
|
48
|
+
FAVORITE_ERRORS = {
|
49
|
+
800 => "A folder path of favorite folder is already added to user's favorites",
|
50
|
+
801 => "A name of favorite folder conflicts with an existing folder path in the user's favorites",
|
51
|
+
802 => "There are too many favorites to be added"
|
52
|
+
}
|
53
|
+
|
54
|
+
UPLOAD_ERRORS = {
|
55
|
+
1800 => "There is no Content-Length information in the HTTP header or the received size doesn't match the value of Content-Length information in the HTTP header",
|
56
|
+
1801 => "Wait too long, no date can be received from client (Default maximum wait time is 3600 seconds)",
|
57
|
+
1802 => "No filename information in the last part of file content",
|
58
|
+
1803 => "Upload connection is cancelled",
|
59
|
+
1804 => "Failed to upload oversized file to FAT file system",
|
60
|
+
1805 => "Can't overwrite or skip the existing file, if no overwrite parameter is given"
|
61
|
+
}
|
62
|
+
|
63
|
+
SHARING_ERRORS = {
|
64
|
+
2000 => "Sharing link does not exist",
|
65
|
+
2001 => "Cannot generate sharing link because too many sharing links exist",
|
66
|
+
2002 => "Failed to access sharing links"
|
67
|
+
}
|
68
|
+
|
69
|
+
CREATE_FOLDER_ERRORS = {
|
70
|
+
1100 => "Failed to create a folder. More information in <errors> object",
|
71
|
+
1101 => "The number of folders to the parent folder would exceed the system limitation"
|
72
|
+
}
|
73
|
+
|
74
|
+
RENAME_ERRORS = {
|
75
|
+
1200 => "Failed to rename it. More information in <errors> object"
|
76
|
+
}
|
77
|
+
|
78
|
+
COPY_MOVE_ERRORS = {
|
79
|
+
1000 => "Failed to copy files/folders. More information in <errors> object",
|
80
|
+
1001 => "Failed to move files/folders. More information in <errors> object",
|
81
|
+
1002 => "An error occurred at the destination. More information in <errors> object",
|
82
|
+
1003 => "Cannot overwrite or skip the existing file because no overwrite parameter is given",
|
83
|
+
1004 => "File cannot overwrite a folder with the same name, or folder cannot overwrite a file with the same name",
|
84
|
+
1006 => "Cannot copy/move file/folder with special characters to a FAT32 file system",
|
85
|
+
1007 => "Cannot copy/move a file bigger than 4G to a FAT32 file system"
|
86
|
+
}
|
87
|
+
|
88
|
+
DELETE_ERRORS = {
|
89
|
+
900 => "Failed to delete file(s)/folder(s). More information in <errors> object"
|
90
|
+
}
|
91
|
+
|
92
|
+
EXTRACT_ERRORS = {
|
93
|
+
1400 => "Failed to extract files",
|
94
|
+
1401 => "Cannot open the file as archive",
|
95
|
+
1402 => "Failed to read archive data error",
|
96
|
+
1403 => "Wrong password",
|
97
|
+
1404 => "Failed to get the file and dir list in an archive",
|
98
|
+
1405 => "Failed to find the item ID in an archive file"
|
99
|
+
}
|
100
|
+
|
101
|
+
COMPRESS_ERRORS = {
|
102
|
+
1300 => "Failed to compress files/folders",
|
103
|
+
1301 => "Cannot create the archive because the given archive name is too long"
|
104
|
+
}
|
105
|
+
|
106
|
+
class << self
|
107
|
+
|
108
|
+
attr_reader :code
|
109
|
+
|
110
|
+
def initialize(message = nil, code = nil)
|
111
|
+
@code = code
|
112
|
+
super(message)
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_error_message(code, operation = nil)
|
116
|
+
# Todo: Find cleaner better way, We dont merge because of duplicate keys.
|
117
|
+
error_map = case operation
|
118
|
+
when 'SYNO.API.Auth' then AUTH_ERRORS
|
119
|
+
when 'SYNO.FileStation.Favorite' then FAVORITE_ERRORS.merge(COMMON_ERRORS)
|
120
|
+
when 'SYNO.FileStation.Upload' then UPLOAD_ERRORS.merge(COMMON_ERRORS)
|
121
|
+
when 'SYNO.FileStation.Sharing' then SHARING_ERRORS.merge(COMMON_ERRORS)
|
122
|
+
when 'SYNO.FileStation.CreateFolder' then CREATE_FOLDER_ERRORS.merge(COMMON_ERRORS)
|
123
|
+
when 'SYNO.FileStation.Rename' then RENAME_ERRORS.merge(COMMON_ERRORS)
|
124
|
+
when 'SYNO.FileStation.CopyMove' then COPY_MOVE_ERRORS.merge(COMMON_ERRORS)
|
125
|
+
when 'SYNO.FileStation.Delete' then DELETE_ERRORS.merge(COMMON_ERRORS)
|
126
|
+
when 'SYNO.FileStation.Extract' then EXTRACT_ERRORS.merge(COMMON_ERRORS)
|
127
|
+
when 'SYNO.FileStation.Compress' then COMPRESS_ERRORS.merge(COMMON_ERRORS)
|
128
|
+
else COMMON_ERRORS
|
129
|
+
end
|
130
|
+
|
131
|
+
error_map[code] || COMMON_ERRORS[code] || "Unknown error (Code: #{code})"
|
132
|
+
end
|
133
|
+
|
134
|
+
def raise_error(code, operation = nil)
|
135
|
+
message = get_error_message(code, operation)
|
136
|
+
raise new("#{message} (Error Code: #{code})")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/synology.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "synology/configuration"
|
4
|
+
require_relative "synology/client"
|
5
|
+
require_relative "synology/version"
|
6
|
+
require_relative "synology/error"
|
7
|
+
|
8
|
+
module Synology
|
9
|
+
class << self
|
10
|
+
attr_accessor :configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configure
|
14
|
+
self.configuration ||= Configuration.new
|
15
|
+
yield(configuration) if block_given?
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: synology
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Henry Maestu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Synology NAS API client written in Ruby
|
14
|
+
email:
|
15
|
+
- dragonwebeu@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".rspec"
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- lib/synology.rb
|
25
|
+
- lib/synology/client.rb
|
26
|
+
- lib/synology/configuration.rb
|
27
|
+
- lib/synology/error.rb
|
28
|
+
- lib/synology/version.rb
|
29
|
+
homepage: https://github.com/dragonwebeu/synology
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata:
|
33
|
+
homepage_uri: https://github.com/dragonwebeu/synology
|
34
|
+
source_code_uri: https://github.com/dragonwebeu/synology
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.3.1
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.0.9
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Synology API Client in Ruby
|
54
|
+
test_files: []
|