Skip to content

Commit 6b14c1f

Browse files
Merge pull request #471 from appwrite/feat-swift-input-file
2 parents 1856801 + b64604c commit 6b14c1f

File tree

9 files changed

+274
-49
lines changed

9 files changed

+274
-49
lines changed

src/SDK/Language/Swift.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public function getFiles()
155155
[
156156
'scope' => 'default',
157157
'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Models/File.swift',
158-
'template' => 'swift/Sources/Models/File.swift.twig',
158+
'template' => 'swift/Sources/Models/InputFile.swift.twig',
159159
'minify' => false,
160160
],
161161
[
@@ -188,6 +188,12 @@ public function getFiles()
188188
'template' => 'swift/Sources/Extensions/HTTPClientRequest+Cookies.swift.twig',
189189
'minify' => false,
190190
],
191+
[
192+
'scope' => 'default',
193+
'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Extensions/String+MimeTypes.swift',
194+
'template' => 'swift/Sources/Extensions/String+MimeTypes.swift.twig',
195+
'minify' => false,
196+
],
191197
[
192198
'scope' => 'default',
193199
'destination' => '/Sources/{{ spec.title | caseUcfirst}}/StreamingDelegate.swift',
@@ -301,7 +307,7 @@ public function getTypeName($type)
301307
case self::TYPE_STRING:
302308
return 'String';
303309
case self::TYPE_FILE:
304-
return 'File';
310+
return 'InputFile';
305311
case self::TYPE_BOOLEAN:
306312
return 'Bool';
307313
case self::TYPE_ARRAY:
@@ -390,7 +396,7 @@ public function getParamExample(array $param)
390396
if(empty($example) && $example !== 0 && $example !== false) {
391397
switch ($type) {
392398
case self::TYPE_FILE:
393-
$output .= 'File(name: "image.jpg", buffer: yourByteBuffer)';
399+
$output .= 'InputFile.fromPath("file.png")';
394400
break;
395401
case self::TYPE_NUMBER:
396402
case self::TYPE_INTEGER:

templates/swift/Sources/Client.swift.twig

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,21 @@ open class Client {
338338
converter: (([String: Any]) -> T)? = nil,
339339
onProgress: ((UploadProgress) -> Void)? = nil
340340
) async throws -> T {
341-
let file = params[paramName] as! File
342-
let size = file.buffer.readableBytes
341+
let input = params[paramName] as! InputFile
342+
343+
switch(input.sourceType) {
344+
case "path":
345+
input.data = ByteBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: input.path)))
346+
case "data":
347+
input.data = ByteBuffer(data: input.data as! Data)
348+
default:
349+
break
350+
}
351+
352+
let size = (input.data as! ByteBuffer).readableBytes
343353

344354
if size < Client.chunkSize {
355+
params[paramName] = input
345356
return try await call(
346357
method: "POST",
347358
path: path,
@@ -351,7 +362,6 @@ open class Client {
351362
)
352363
}
353364

354-
let input = file.buffer
355365
var offset = 0
356366
var result = [String:Any]()
357367

@@ -369,14 +379,10 @@ open class Client {
369379
}
370380

371381
while offset < size {
372-
let slice = input.getSlice(at: offset, length: Client.chunkSize)
373-
?? input.getSlice(at: offset, length: Int(size - offset))
382+
let slice = (input.data as! ByteBuffer).getSlice(at: offset, length: Client.chunkSize)
383+
?? (input.data as! ByteBuffer).getSlice(at: offset, length: Int(size - offset))
374384

375-
params[paramName] = File(
376-
name: file.name,
377-
buffer: slice!
378-
)
379-
385+
params[paramName] = InputFile.fromBuffer(slice!, filename: input.filename, mimeType: input.mimeType)
380386
headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size))/\(size)"
381387

382388
result = try await call(
@@ -429,12 +435,15 @@ open class Client {
429435
bodyBuffer.writeString(CRLF)
430436
bodyBuffer.writeString("Content-Disposition: form-data; name=\"\(name)\"")
431437

432-
if let file = value as? File {
433-
bodyBuffer.writeString("; filename=\"\(file.name)\"")
438+
if let file = value as? InputFile {
439+
bodyBuffer.writeString("; filename=\"\(file.filename)\"")
434440
bodyBuffer.writeString(CRLF)
435441
bodyBuffer.writeString("Content-Length: \(bodyBuffer.readableBytes)")
436442
bodyBuffer.writeString(CRLF+CRLF)
437-
bodyBuffer.writeBuffer(&file.buffer)
443+
444+
var buffer = file.data! as! ByteBuffer
445+
446+
bodyBuffer.writeBuffer(&buffer)
438447
bodyBuffer.writeString(CRLF)
439448
return
440449
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import Foundation
2+
3+
fileprivate let DEFAULT_MIME_TYPE = "application/octet-stream"
4+
5+
fileprivate let mimeTypes = [
6+
"md": "text/markdown",
7+
"html": "text/html",
8+
"htm": "text/html",
9+
"shtml": "text/html",
10+
"css": "text/css",
11+
"xml": "text/xml",
12+
"gif": "image/gif",
13+
"jpeg": "image/jpeg",
14+
"jpg": "image/jpeg",
15+
"js": "application/javascript",
16+
"atom": "application/atom+xml",
17+
"rss": "application/rss+xml",
18+
"mml": "text/mathml",
19+
"txt": "text/plain",
20+
"jad": "text/vnd.sun.j2me.app-descriptor",
21+
"wml": "text/vnd.wap.wml",
22+
"htc": "text/x-component",
23+
"png": "image/png",
24+
"tif": "image/tiff",
25+
"tiff": "image/tiff",
26+
"wbmp": "image/vnd.wap.wbmp",
27+
"ico": "image/x-icon",
28+
"jng": "image/x-jng",
29+
"bmp": "image/x-ms-bmp",
30+
"svg": "image/svg+xml",
31+
"svgz": "image/svg+xml",
32+
"webp": "image/webp",
33+
"woff": "application/font-woff",
34+
"jar": "application/java-archive",
35+
"war": "application/java-archive",
36+
"ear": "application/java-archive",
37+
"json": "application/json",
38+
"hqx": "application/mac-binhex40",
39+
"doc": "application/msword",
40+
"pdf": "application/pdf",
41+
"ps": "application/postscript",
42+
"eps": "application/postscript",
43+
"ai": "application/postscript",
44+
"rtf": "application/rtf",
45+
"m3u8": "application/vnd.apple.mpegurl",
46+
"xls": "application/vnd.ms-excel",
47+
"eot": "application/vnd.ms-fontobject",
48+
"ppt": "application/vnd.ms-powerpoint",
49+
"wmlc": "application/vnd.wap.wmlc",
50+
"kml": "application/vnd.google-earth.kml+xml",
51+
"kmz": "application/vnd.google-earth.kmz",
52+
"7z": "application/x-7z-compressed",
53+
"cco": "application/x-cocoa",
54+
"jardiff": "application/x-java-archive-diff",
55+
"jnlp": "application/x-java-jnlp-file",
56+
"run": "application/x-makeself",
57+
"pl": "application/x-perl",
58+
"pm": "application/x-perl",
59+
"prc": "application/x-pilot",
60+
"pdb": "application/x-pilot",
61+
"rar": "application/x-rar-compressed",
62+
"rpm": "application/x-redhat-package-manager",
63+
"sea": "application/x-sea",
64+
"swf": "application/x-shockwave-flash",
65+
"sit": "application/x-stuffit",
66+
"tcl": "application/x-tcl",
67+
"tk": "application/x-tcl",
68+
"der": "application/x-x509-ca-cert",
69+
"pem": "application/x-x509-ca-cert",
70+
"crt": "application/x-x509-ca-cert",
71+
"xpi": "application/x-xpinstall",
72+
"xhtml": "application/xhtml+xml",
73+
"xspf": "application/xspf+xml",
74+
"zip": "application/zip",
75+
"bin": "application/octet-stream",
76+
"exe": "application/octet-stream",
77+
"dll": "application/octet-stream",
78+
"deb": "application/octet-stream",
79+
"dmg": "application/octet-stream",
80+
"iso": "application/octet-stream",
81+
"img": "application/octet-stream",
82+
"msi": "application/octet-stream",
83+
"msp": "application/octet-stream",
84+
"msm": "application/octet-stream",
85+
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
86+
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
87+
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
88+
"mid": "audio/midi",
89+
"midi": "audio/midi",
90+
"kar": "audio/midi",
91+
"mp3": "audio/mpeg",
92+
"ogg": "audio/ogg",
93+
"m4a": "audio/x-m4a",
94+
"ra": "audio/x-realaudio",
95+
"3gpp": "video/3gpp",
96+
"3gp": "video/3gpp",
97+
"ts": "video/mp2t",
98+
"mp4": "video/mp4",
99+
"mpeg": "video/mpeg",
100+
"mpg": "video/mpeg",
101+
"mov": "video/quicktime",
102+
"webm": "video/webm",
103+
"flv": "video/x-flv",
104+
"m4v": "video/x-m4v",
105+
"mng": "video/x-mng",
106+
"asx": "video/x-ms-asf",
107+
"asf": "video/x-ms-asf",
108+
"wmv": "video/x-ms-wmv",
109+
"avi": "video/x-msvideo"
110+
]
111+
112+
fileprivate func mimeFromExt(ext: String?) -> String {
113+
let lower: String = ext!.lowercased()
114+
if ext != nil && mimeTypes.contains(where: { $0.0 == lower }) {
115+
return mimeTypes[lower]!
116+
}
117+
return DEFAULT_MIME_TYPE
118+
}
119+
120+
extension NSString {
121+
public func mimeType() -> String {
122+
return mimeFromExt(ext: self.pathExtension)
123+
}
124+
}
125+
126+
extension String {
127+
public func mimeType() -> String {
128+
return (self as NSString).mimeType()
129+
}
130+
}

templates/swift/Sources/Models/File.swift.twig

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Foundation
2+
import NIO
3+
4+
open class InputFile {
5+
6+
public var path: String = ""
7+
public var filename: String = ""
8+
public var mimeType: String = ""
9+
public var sourceType: String = ""
10+
public var data: Any? = nil
11+
12+
internal init() {
13+
}
14+
15+
public static func fromPath(_ path: String) -> InputFile {
16+
let instance = InputFile()
17+
instance.path = path
18+
instance.filename = URL(fileURLWithPath: path, isDirectory: false).lastPathComponent
19+
instance.mimeType = path.mimeType()
20+
instance.sourceType = "path"
21+
return instance
22+
}
23+
24+
public static func fromData(_ data: Data, filename: String, mimeType: String) -> InputFile {
25+
let instance = InputFile()
26+
instance.filename = filename
27+
instance.mimeType = mimeType
28+
instance.sourceType = "data"
29+
instance.data = data
30+
return instance
31+
}
32+
33+
public static func fromBuffer(_ buffer: ByteBuffer, filename: String, mimeType: String) -> InputFile {
34+
let instance = InputFile()
35+
instance.filename = filename
36+
instance.mimeType = mimeType
37+
instance.sourceType = "buffer"
38+
instance.data = buffer
39+
return instance
40+
}
41+
}

tests/SwiftClient55Test.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class SwiftClient55Test extends Base
1818
...Base::BAR_RESPONSES,
1919
...Base::GENERAL_RESPONSES,
2020
...Base::LARGE_FILE_RESPONSES,
21+
...Base::LARGE_FILE_RESPONSES,
22+
...Base::LARGE_FILE_RESPONSES,
2123
...Base::EXCEPTION_RESPONSES,
2224
...Base::REALTIME_RESPONSES,
2325
...Base::COOKIE_RESPONSES,

tests/SwiftServer55Test.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class SwiftServer55Test extends Base
1818
...Base::BAR_RESPONSES,
1919
...Base::GENERAL_RESPONSES,
2020
...Base::LARGE_FILE_RESPONSES,
21+
...Base::LARGE_FILE_RESPONSES,
22+
...Base::LARGE_FILE_RESPONSES,
2123
...Base::EXCEPTION_RESPONSES,
2224
];
2325
}

tests/languages/swift-client/Tests.swift

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,41 @@ class Tests: XCTestCase {
7878
let result = try await general.redirect()
7979
print((result as! [String: Any])["result"] as! String)
8080

81-
var url = URL(fileURLWithPath: "\(FileManager.default.currentDirectoryPath)/../../resources/file.png")
82-
var buffer = ByteBuffer(data: try! Data(contentsOf: url))
83-
var file = File(name: "file.png", buffer: buffer)
84-
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
85-
print(mock.result)
81+
do {
82+
var file = InputFile.fromPath("\(FileManager.default.currentDirectoryPath)/../../resources/file.png")
83+
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
84+
print(mock.result)
85+
} catch let error as AppwriteError {
86+
print(error.message)
87+
}
88+
89+
do {
90+
var file = InputFile.fromPath("\(FileManager.default.currentDirectoryPath)/../../resources/large_file.mp4")
91+
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
92+
print(mock.result)
93+
} catch let error as AppwriteError {
94+
print(error.message)
95+
}
8696

87-
url = URL(fileURLWithPath: "\(FileManager.default.currentDirectoryPath)/../../resources/large_file.mp4")
88-
buffer = ByteBuffer(data: try! Data(contentsOf: url))
89-
file = File(name: "large_file.mp4", buffer: buffer)
90-
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
91-
print(mock.result)
97+
do {
98+
var url = URL(fileURLWithPath: "\(FileManager.default.currentDirectoryPath)/../../resources/file.png")
99+
var buffer = ByteBuffer(data: try! Data(contentsOf: url))
100+
var file = InputFile.fromBuffer(buffer, filename: "file.png", mimeType: "image/png")
101+
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
102+
print(mock.result)
103+
} catch let error as AppwriteError {
104+
print(error.message)
105+
}
106+
107+
do {
108+
var url = URL(fileURLWithPath: "\(FileManager.default.currentDirectoryPath)/../../resources/large_file.mp4")
109+
var buffer = ByteBuffer(data: try! Data(contentsOf: url))
110+
var file = InputFile.fromBuffer(buffer, filename: "large_file.mp4", mimeType: "video/mp4")
111+
mock = try await general.upload(x: "string", y: 123, z: ["string in array"], file: file, onProgress: nil)
112+
print(mock.result)
113+
} catch let error as AppwriteError {
114+
print(error.message)
115+
}
92116

93117
do {
94118
try await general.error400()

0 commit comments

Comments
 (0)