Closed CPiersigilli closed 4 years ago
I did other tests and noticed that if the loading time of the image exceeds 10 seconds, the NAS will not load the image even though it does not return any errors. I don't know how to modify the code so that it doesn't happen that an image is not loaded in the NAS, without having returned an error message. I hope you can figure out how to solve this problem.
I tried in every way, for example, to verify that the image had been transferred, I also used the function client.md5 (ofFile :, but I discovered that it does not always work, since several times it did not calculate the MD5, even if the image had been transferred. Only when it manages to calculate the MD5, then it is certain that the image was transferred. Could you tell me what you think and if what is happening to me happens to you too? Have you idea how to solve the problem?
I will try it in the example.
Please check out the example for details.
UploadImage()
of BrowserViewController
.
I tested your SynologyKitExample
and it works fine, but if I add two image (image_1920.jpg
and image_HR.jpg
) with difference resolution and upload one of them, your SynologyKitExample
doesn't work anymore. The output shows no error, but the image hasn't been uploaded.
Here is my modified code for your tests.
SynologyKit-master_Rev_CPiersigilli.zip
I hope you will be able to modify the code to make it work and be sure that when it is without an error, the image has been uploaded.
Thank you.
Your image format is jpg
, but you get data with pngData()
, I think you should use .jpegData(compressionQuality: 1.0)
.
You're right, sorry, unfortunately your excellent SynologyKit
doesn't always work.
I am attaching the output of the same image uploaded (image_1920.jpg)
The only difference is that the image that has not been loaded has:
**(Duration) 10.517915 seconds**
(Request Header Bytes) 89
**(Request Body Transfer Bytes) 1933320**
(Request Body Bytes) 1932051
(Response Header Bytes) 180
**(Response Body Transfer Bytes) 56**
**(Response Body Bytes) 38**
and the image that was uploaded has:
**(Duration) 0.502085 seconds**
(Request Header Bytes) 89
**(Request Body Transfer Bytes) 1933194**
(Request Body Bytes) 1932051
(Response Header Bytes) 180
**(Response Body Transfer Bytes) 106**
**(Response Body Bytes) 88**
The problem concerns the loading time, but I don't know how to avoid it and I don't understand why if the time is over 10 seconds the image is not loaded. I hope you can understand and solve.
I changed your private func uploadImage() {
like this:
private func uploadImage() {
let imageName = "image_HR"
guard let img = UIImage(named: imageName), let jpgdata = img.jpegData(compressionQuality: 1), let folder = folderPath else {
print("Error from uploadImage: \(imageName) - \(folderPath)")
return
}
var options = SynologyClient.UploadOptions()
options.overwrite = true
options.modificationTime = Int64(Date().timeIntervalSince1970*1000)
client.upload(data: jpgdata, filename: "\(imageName).jpg", destinationFolderPath: folder, createParents: true, options: options) { (result) in
switch result {
case .success(let request, let isStream, let streamFileURL):
print("isStream: \(isStream) - streamFileURL: \(String(describing: streamFileURL?.absoluteURL))")
request.uploadProgress { progress in
print("progress: \(progress.fractionCompleted)")
}.response { response in
print("------------")
print("Status Code: \(response.response!.statusCode)")
print("------------")
print("Duration: \(response.metrics?.taskInterval.duration ?? -1) sec")
print("------------")
print("TimeOutInterval: \(response.request?.timeoutInterval ?? -1) sec")
if response.error == nil && response.metrics!.taskInterval.duration <= 10.0 {
print("\(imageName) uploaded")
print("------------")
} else {
print("\(imageName) not uploaded, but no error.")
print("------------")
}
}
case .failure(let error):
print("error: \(error.localizedDescription)")
}
}
}
I added the upload duration check with: if response.error == nil && response.metrics!.taskInterval.duration <= 10.0 {
.
I don't know for which reason, but now it seems to work.
Do you have any ideas about this?
You didn't tell me if you too experienced my own problems.
You may have a look at how Alamofire upload data. https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server
SynologyKit upload function just wraps that.
To carry out further tests to upload file or image in Synology NAS
, I would need you to tell me how to get what is indicated in the "Synology File Station
" manual.
What I expect to find is:
POST /webapi/FileStation/api_upload.cgi ...
Content-Length:20326728
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="api"
SYNO.FileStation.Upload
--AaB03x
content-disposition: form-data; name="version"
1
--AaB03x
content-disposition: form-data; name="method"
upload
--AaB03x
content-disposition: form-data; name="dest_folder_path"
/upload/test
--AaB03x
content-disposition: form-data; name="create_parents"
true
--AaB03x
content-disposition: form-data; name="file"; filename="file1.txt" Content-Type: application/octet-stream
... contents of file1.txt ...
--AaB03x--
I would like to try to use, only for uploading, a routine other than Alamofire
, like the one shown here or here.
I am convinced that by trying other ways we will be able to solve the problem.
Thank you.
You catch the network traffic by Charles, and check out what’s going wrong
I downloaded and installed CharlesProxi
, but the results do not seem different from those obtained by printing the response variable.
Can you tell me how to look for something that helps me understand more?
Did it also happen to you that, without receiving any error, the image was not uploaded?
I keep testing on your SynologyKit and I realized you used this:
parameters.append (UploadParam (key: "path", value: destinationFolderPath))
instead of
parameters.append (UploadParam (key: "dest_folder_path", value: destinationFolderPath))
as stated in the Synology_File_Station_API_Guide
at page 64. I'm wondering why is that?
The funny thing is that using dest_folder_path
it does not work at all and it doesn't show any errors either. It seems that when duration
is more than 10 seconds it doesn't show any errors.
Where did you find the key you used to upload the file correctly? Why did you use path
instead of dest_folder_path
? There may be some other wrong keys.
Best Regards, Cesare Piersigilli
Can you provide a demo ?
This is a demo app in SwiftUI
.
SynologyKit-SwiftUIExample.zip
I have added in your SynologyClient.swift
two methods: upload1
and upload2
.
In upload1
I have changed only parameters.append (UploadParam (key: "path", value: destinationFolderPath))
with parameters.append (UploadParam (key: "dest_folder_path", value: destinationFolderPath))
as stated in the Synology_File_Station_API_Guide
at page 64.
In upload2
I tried to write a routine to upload an image without using Alamofire
, but I can't make it work.
I hope that with this demo you will be able to understand why your upload
sometimes does not work, while not giving any errors.
I deleted some images, otherwise I would not have been able to upload the file, because it would have exceeded the limit of 10 Mb.
I downloaded Postman and found the way to properly upload an image to a Synologuy NAS. Here is the screenshot of how I did it after logging in.
Now I have to figure out how to translate it into Swift.
Hope you can do it.
I confirm that path
must be used and not dest_folder_path
.
I just change dest_folder_path
to path
, and change the /Test/Prova
to my path "/Downloads". And all three upload test pass.
So I can not figure out what's going wrong...
The video above shows how the first three images were uploaded without problems (see folder "Test
"), the fourth one was not (see folder "Test"), without giving any errors (status code: 200
- see output).
I don't know what the problem is, but sometimes the image doesn't load.
I tried to use the Swift code produced by Postman, but it doesn't work.
Could you post the video of the upload of the 5 images contained in my app, with my app, using the button 3 Upload Image?
I have found the solution. I added two methods in your SynologyClient.swift
: public func uploadFido (
and func createBody (
.
I extracted the two methods from here.
Please use it and tell me if you can convert the two methods for use with Alamofire
, because I have not succeeded.
If you press Upload Image 3
, obtain, for example this error:
L'immagine non esiste in Preview Assets.
or this result:
L'immagine image_01.jpg è stata salvata nel NAS con server_response: {
data = {
blSkip = 0;
file = "image_01.jpg";
pid = 24722;
progress = 1;
};
success = 1;
}
and the image was actually saved to the NAS. I take this opportunity to wish you a happy new year.
I have replicated the problem. Sometimes NAS response with
{"success": false, "error": { "code": 401 } }
Sometimes NAS response with successful results. I do not know what's wrong.
I have updated my code to report such error.
I have done many tests and even to me, sometimes, for no reason, the server responds with the error 401, which is not Account disabled
but Unknown error of file operation
(see pag. 9 of Synology File Station - Official API
).
In my opinion, the problem continues to be Alamofire, but I have not been able to understand how Alamofire constructs the body of the request and all the other parameters.
What we should get is the one shown below and that I get from the routine that I inserted in the app that I attached in the previous post.
POST https://www.mydomain.com:5001/webapi/entry.cgi
Content-Length:20326728
Content-type: multipart/form-data, boundary=AaB03x
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="api"
SYNO.FileStation.Upload
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="version"
2
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="method"
upload
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="_sid"
Fwtsg4okP8pxI18C0PDN731510
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="path"
/Test/Prova
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="create_parents"
true
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D
content-disposition: form-data; name="image_640.jpg";filename="image_640.jpg"
Content-Type: application/octet-stream
----contents of image data----
--Boundary-50B54811-BBF4-40E5-B687-A9DE4E58963D--
Do you know how to print the parameters that Alamofire creates in order to compare them with those listed above? I do not give up and continue to test, since I think the use of Alamofire is convenient because with it many parameters can be controlled, otherwise very difficult to control.
You may use charles to catch the internet traffic.
Can you tell me how to use CharlesProxi to check internet traffic? I tried but I was unable to extract the request body, to check if it is correct.
With Alamofire 4.9.1 the code below works perfectly, but I was unable to implement the 'progress' variable. If you can, I'd be happy.
public func uploadCustomAFRequest(imageData: Data, filename: String, destinationFolderPath: String, createParents: Bool, options: UploadOptions? = nil, progressHandler: UploadRequest.ProgressHandler? = nil, completion: @escaping SynologyCompletion<UploadResponse>) {
var parameters: [UploadParam] = []
parameters.append(UploadParam(key: "api", value: SynologyAPI.upload.rawValue))
parameters.append(UploadParam(key: "version", value: "2"))
parameters.append(UploadParam(key: "method", value: SynologyMethod.upload.rawValue))
if let sid = self.sessionid {
parameters.append(UploadParam(key: "_sid", value: sid))
}
parameters.append(UploadParam(key: "path", value: destinationFolderPath))
parameters.append(UploadParam(key: "create_parents", value: String(createParents)))
if let options = options {
if let overwrite = options.overwrite {
parameters.append(UploadParam(key: "overwrite", value: String(overwrite)))
}
if let mtime = options.modificationTime {
parameters.append(UploadParam(key: "mtime", value: String(mtime)))
}
if let crtime = options.createTime {
parameters.append(UploadParam(key: "crtime", value: String(crtime)))
}
if let atime = options.accessTime {
parameters.append(UploadParam(key: "atime", value: String(atime)))
}
}
let url = URL(string: baseURLString().appending("webapi/entry.cgi"))!
var folderPath = destinationFolderPath
if(folderPath == ""){
folderPath = "/"
}
let boundary = "Boundary-\(UUID().uuidString)"
var urlRequest = URLRequest(url: url,cachePolicy: URLRequest.CachePolicy.useProtocolCachePolicy,timeoutInterval: 60)
urlRequest.httpMethod = "POST"
urlRequest.httpShouldHandleCookies = true
let body = self.createBody(parameters: parameters, contentFile: imageData, folderPath, filename, boundary)
urlRequest.httpBody = body as Data
urlRequest.addValue(String(describing: body.length), forHTTPHeaderField: "Content-Length")
urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
Alamofire.request(urlRequest)
.response { response in
guard let data = response.data else {
completion(.failure(.invalidResponse(response)))
return
}
do {
let decodedRes = try JSONDecoder().decode(UploadResponse.self, from: data)
if decodedRes.success {
completion(.success(decodedRes))
} else {
let code = decodedRes.error?.code ?? -1
let msg = SynologyErrorMapper[code] ?? "Unknow error"
completion(.failure(.serverError(code, msg, response)))
}
} catch {
let text = String(data: data, encoding: .utf8)
completion(.failure(.decodeDataError(response, text)))
}
}
}
func createBody(parameters: [UploadParam], contentFile: Data,_ folderPath: String,_ filename: String,_ boundary: String) -> NSMutableData {
let body = NSMutableData()
for param in parameters {
body.append(String("--\(boundary)\r\n").data(using: .utf8)!)
body.append(String("content-disposition: form-data; name=\"\(param.key)\"\r\n\r\n\(param.value)\r\n").data(using: .utf8)!)
}
body.append(String("--\(boundary)\r\n").data(using: .utf8)!)
body.append(String("content-disposition: form-data; name=\"\(filename)\";filename=\"\(filename)\"\r\n").data(using: .utf8)!)
body.append(String("Content-Type: application/octet-stream\r\n\r\n").data(using: .utf8)!)
print(String(decoding: body, as: UTF8.self)+"----image data----"+"\r\n--\(boundary)--\r\n")
body.append(contentFile)
body.append(String("\r\n--\(boundary)--\r\n").data(using: .utf8)!)
return body
}
PROBLEM SOLVED
I solved the problem by modifying your files to be compatible with Alamofire 5.0.0.beta.1
and now public func uploadManualRequest (
works very well with progress
variable.
Source-SynologyKit.zip
To make them work, the download functions and some other parts of the code remain to be modified.
Thank you for your SynologyKit.
Cesare Piersigilli
Here is your SynologyKit
updated to Alamofire 5.0.0.beta.1
in all its components.
I also tested public func upload (
and it seems to be working fine now.
Source-SynologyKit.zip
Please update your SynologyKit.
Thank you.
Cesare Piersigilli
@CPiersigilli Hi,I have upgrade Alamofire to 5.1.0
Can you test that whether the upload issue still exists. Thanks.
I test your SynologyKit
, but don't work, because in your Package.swift
is write:
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.1"),
],
Please upgrade your Package.swift
, to download Alamofire 5.1
.
I test your
SynologyKit
, but don't work, because in yourPackage.swift
is write:dependencies: [ // Dependencies declare other packages that this package depends on. .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.1"), ],
Please upgrade your
Package.swift
, to downloadAlamofire 5.1
.
Updated.
I tested your SynologyKit
and now work fine. Remember to update the branch, because I had to import the source manually.
Try 1.0.0
Now your SynologyKit
works well in iOS, but, the same code, in MacOS with Catalyst, it doesn't work and I don't understand why.
The error from your SynologyError
is: Unknown Error
This is the code:
fileprivate func clientLogin() {
client.login(account: self.preferences.username, passwd: self.preferences.password) { response in
switch response {
case .success(let authRes):
client.updateSessionID(authRes.sid)
self.showFolderListView = true
self.hideNavigationBar = false
print(authRes.sid)
case .failure(let error):
print("error.description: \(error)")
self.showFolderListView = false
self.showAlert = true
self.hideNavigationBar = true
self.title = "\(error as SynologyError)"
}
}
}
Can you tell me what I need to change?
I was wrong. SynologyKit
also works on Mac OS with Catalyst.
I have this code to upload image to Synology NAS.
In this video I have not error (see output) but the image don't upload to NAS (see folder). In this video I have no errors (see output) but the upload of the image to the NAS (see folder) has been made, even if after a while that the loading is finished (progress = 1 * 100).
How can I change the code to make sure the image has been uploaded to the NAS? Thank you.