11import Swifter
22import Foundation
33import Logging
4- import System
4+ import PMTiles
55
66/// ServeProtomapsOptions defines runtime options for serving Protomaps tiles
77public struct ServeProtomapsOptions {
@@ -28,8 +28,6 @@ public struct ServeProtomapsOptions {
2828}
2929
3030/// ServeProtomapsTiles will serve HTTP range requests for zero or more Protomaps tile databases in a directory.
31- @available ( iOS 14 . 0 , * )
32- @available ( macOS 11 . 0 , * )
3331public func ServeProtomapsTiles( _ opts: ServeProtomapsOptions ) -> ( ( HttpRequest ) -> HttpResponse ) {
3432
3533 return { r in
@@ -45,42 +43,7 @@ public func ServeProtomapsTiles(_ opts: ServeProtomapsOptions) -> ((HttpRequest)
4543 if opts. StripPrefix != " " {
4644 rel_path = rel_path. replacingOccurrences ( of: opts. StripPrefix, with: " " )
4745 }
48-
49- let uri = opts. Root. appendingPathComponent ( rel_path)
50- let path = uri. absoluteString
51-
52- // https://developer.apple.com/documentation/foundation/filehandle
53-
54- var fh : FileHandle ?
55- var fd : FileDescriptor ?
56-
57- do {
58-
59- if opts. UseFileDescriptor {
60- let fp = FilePath ( uri. absoluteString. replacingOccurrences ( of: " file:// " , with: " " ) )
61- fd = try FileDescriptor . open ( fp, . readOnly)
62- } else {
63- fh = try FileHandle ( forReadingFrom: uri)
64- }
65-
66- } catch {
67- opts. Logger? . error ( " Failed to open path ( \( path) ) for reading \( error) " )
68- return . raw( 404 , " Not found " , rsp_headers, { _ in } )
69- }
70-
71- defer {
72- do {
73- if opts. UseFileDescriptor {
74- try fd? . close ( )
75- } else {
76- try fh? . close ( )
77- }
7846
79- } catch ( let error) {
80- opts. Logger? . warning ( " Failed to close \( path) , \( error) " )
81- }
82- }
83-
8447 guard var range_h = r. headers [ " range " ] else {
8548 rsp_headers [ " Access-Control-Allow-Origin " ] = opts. AllowOrigins
8649 rsp_headers [ " Access-Control-Allow-Headers " ] = opts. AllowHeaders
@@ -107,7 +70,7 @@ public func ServeProtomapsTiles(_ opts: ServeProtomapsOptions) -> ((HttpRequest)
10770 return . raw( 400 , " Bad Request " , rsp_headers, { _ in } )
10871 }
10972
110- guard let stop = Int ( positions [ 1 ] ) else {
73+ guard let stop = UInt64 ( positions [ 1 ] ) else {
11174 rsp_headers [ " X-Error " ] = " Invalid stopping range "
11275 return . raw( 400 , " Bad Request " , rsp_headers, { _ in } )
11376 }
@@ -117,74 +80,59 @@ public func ServeProtomapsTiles(_ opts: ServeProtomapsOptions) -> ((HttpRequest)
11780 return . raw( 400 , " Bad Request " , rsp_headers, { _ in } )
11881 }
11982
120- opts. Logger? . debug ( " Read data from \( path) start: \( start) stop: \( stop) " )
121-
12283 let next = stop + 1
12384
124- let body : Data !
85+ // Fetch data
86+
87+ let db_url = opts. Root. appendingPathComponent ( rel_path)
88+
89+ var pmtiles_reader : PMTilesReader
12590
12691 do {
12792
128- opts. Logger? . debug ( " Seek \( path) to \( start) " )
93+ var reader_opts = PMTilesReaderOptions ( db_url, use_file_descriptor: opts. UseFileDescriptor)
94+ reader_opts. Logger = opts. Logger
12995
130- if opts. UseFileDescriptor {
131- try fd? . seek ( offset: Int64 ( start) , from: FileDescriptor . SeekOrigin. start)
132- } else {
133- fh? . seek ( toFileOffset: start)
134- }
96+ pmtiles_reader = try PMTilesReader ( reader_opts)
13597
13698 } catch {
137- opts. Logger? . error ( " Failed to seek to \( start ) for \( path ) , \( error) " )
138- rsp_headers [ " X-Error " ] = " Failed to read from Protomaps tile "
99+ opts. Logger? . error ( " Failed to instantiate PMTiles reader \( error) " )
100+ rsp_headers [ " X-Error " ] = " Failed to instantiate PMTiles reader "
139101 return . raw( 500 , " Internal Server Error " , rsp_headers, { _ in } )
140102 }
141103
142- opts. Logger? . debug ( " Read data from \( path) to \( next) " )
143-
144- if opts. UseFileDescriptor {
145-
146- let read_len = Int ( UInt64 ( next) - start)
147- opts. Logger? . debug ( " Read \( read_len) bytes from \( path) " )
148-
149- guard let data = readData ( from: fd!. rawValue, length: Int ( read_len) ) else {
150- opts. Logger? . error ( " Failed to read to \( next) for \( path) " )
151- rsp_headers [ " X-Error " ] = " Failed to read from Protomaps tile "
152- return . raw( 500 , " Internal Server Error " , rsp_headers, { _ in } )
104+ defer {
105+ if case . failure( let error) = pmtiles_reader. Close ( ) {
106+ opts. Logger? . error ( " Failed to close PMTiles reader \( error) " )
153107 }
154-
108+ }
109+
110+ opts. Logger? . debug ( " Read data from \( db_url. absoluteString) start: \( start) stop: \( stop) " )
111+
112+ let body : Data !
113+
114+ let read_rsp = pmtiles_reader. Read ( from: start, to: stop)
115+
116+ switch read_rsp {
117+ case . success( let data) :
155118 body = data
156-
157- } else {
158-
159- do {
160- body = try fh? . read ( upToCount: next)
161- } catch ( let error) {
162- opts. Logger? . error ( " Failed to read to \( next) for \( path) , \( error) " )
163- rsp_headers [ " X-Error " ] = " Failed to read from Protomaps tile "
164- return . raw( 500 , " Internal Server Error " , rsp_headers, { _ in } )
165- }
166-
119+ case . failure( let error) :
120+ opts. Logger? . error ( " Failed to read data from PMTiles reader, \( error) " )
121+ rsp_headers [ " X-Error " ] = " Failed to read from Protomaps tile "
122+ return . raw( 500 , " Internal Server Error " , rsp_headers, { _ in } )
167123 }
168124
169125 // https://httpwg.org/specs/rfc7233.html#header.accept-ranges
170126
171127 var filesize = " * "
172128
173- do {
174- if opts. UseFileDescriptor {
175-
176- let size = try fd!. seek ( offset: 0 , from: FileDescriptor . SeekOrigin. end)
177- filesize = String ( size)
178-
179- opts. Logger? . debug ( " filesize \( filesize) " )
180-
181- } else {
182-
183- let size = try fh!. seekToEnd ( )
184- filesize = String ( size)
185- }
186- } catch ( let error) {
187- opts. Logger? . warning ( " Failed to determine filesize for \( path) , \( error) " )
129+ let size_rsp = pmtiles_reader. Size ( )
130+
131+ switch size_rsp {
132+ case . success( let sz) :
133+ filesize = String ( sz)
134+ case . failure( let error) :
135+ opts. Logger? . warning ( " Failed to determine size from PMTiles reader \( error) " )
188136 }
189137
190138 let length = UInt64 ( next) - start
@@ -198,6 +146,8 @@ public func ServeProtomapsTiles(_ opts: ServeProtomapsOptions) -> ((HttpRequest)
198146 rsp_headers [ " Content-Range " ] = content_range
199147 rsp_headers [ " Accept-Ranges " ] = " bytes "
200148
149+ opts. Logger? . debug ( " Return 206 \( content_range) " )
150+
201151 return . raw( 206 , " Partial Content " , rsp_headers, { writer in
202152
203153 do {
@@ -208,23 +158,3 @@ public func ServeProtomapsTiles(_ opts: ServeProtomapsOptions) -> ((HttpRequest)
208158 } )
209159 }
210160}
211-
212- internal func readData( from fileDescriptor: Int32 , length: Int ) -> Data ? {
213- // Create a Data buffer of the desired length
214- var data = Data ( count: length)
215-
216- // Read the data into the Data buffer
217- let bytesRead = data. withUnsafeMutableBytes { buffer -> Int in
218- guard let baseAddress = buffer. baseAddress else { return - 1 }
219- return read ( fileDescriptor, baseAddress, length)
220- }
221-
222- // Handle errors or end-of-file
223- guard bytesRead > 0 else {
224- return nil // Return nil if no bytes were read
225- }
226-
227- // Resize the Data object to the actual number of bytes read
228- data. removeSubrange ( bytesRead..< data. count)
229- return data
230- }
0 commit comments