1 /// Provides implementation of file operation with asynchronous IO. 2 module dpromise.utils.file; 3 4 import dpromise.promise; 5 import std.range.primitives, std.traits; 6 import std.file; 7 import deimos.libuv.uv, dpromise.internal.libuv; 8 9 10 /** 11 Writes $(D buffer) to file $(D path) with asynchronous IO. 12 13 Creates a new file if it is not already exist. 14 If an error occurred, the promise will be rejected. 15 16 Params: 17 path = string repreesenting file path. 18 buffer = data to be written to file. 19 20 See_Also: $(HTTP https://dlang.org/phobos/std_file.html#.write, std.file.write) 21 */ 22 nothrow Promise!void writeAsync(string path, in void[] buffer) { 23 return promise!void((res, rej) { 24 writeAsyncWithCallback(path, buffer, (e) nothrow { 25 e is null ? res() : rej(e); 26 }); 27 }); 28 } 29 30 /// 31 @system unittest { 32 import std.file : exists, readText, remove; 33 import dpromise.utils : runEventloop; 34 35 writeAsync("hoge.txt", "hogehogepiyopiyo").then({ 36 assert(exists("hoge.txt")); 37 assert(readText("hoge.txt") == "hogehogepiyopiyo"); 38 remove("hoge.txt"); 39 }); 40 41 runEventloop(); 42 } 43 44 45 /** 46 Writes $(D buffer) to file $(D path) then calls the $(D callback) function with data as untyped array. 47 48 Creates a new file if it is not already exist. 49 If an error occurred, the $(D callback) function will be called with the error. 50 51 Params: 52 path = string repreesenting file path. 53 buffer = data to be written to file. 54 callback = a fuction called when operation finished or an error occurred. 55 */ 56 nothrow @safe void writeAsyncWithCallback(string path, in void[] buffer, void delegate(Exception) nothrow callback) 57 in { 58 assert(callback !is null); 59 } body { 60 struct Data { 61 int err; 62 int handle; 63 void delegate(Exception) nothrow callback; 64 void[] buf; 65 } 66 67 extern(C) @trusted nothrow static void ret(uv_fs_t* req) { 68 auto data = cast(Data*)req.data; 69 auto callback = data.callback; 70 callback(factory(data.err)); 71 72 scope(exit) { 73 uv_fs_req_cleanup(req); 74 import core.memory : GC; 75 import core.stdc.stdlib : free; 76 GC.removeRoot(callback.ptr); 77 free(req.data); 78 free(req); 79 } 80 } 81 82 extern(C) @trusted nothrow static void on_close(uv_fs_t* req) { 83 auto result = cast(int)req.result; 84 auto data = cast(Data*)req.data; 85 if(result < 0) { 86 data.err = result; 87 } 88 ret(req); 89 } 90 91 extern(C) @trusted nothrow static void on_write(uv_fs_t* req) { 92 auto result = cast(int)req.result; 93 auto data = cast(Data*)req.data; 94 if(result < 0) { 95 data.err = result; 96 ret(req); 97 return; 98 } 99 100 uv_fs_req_cleanup(req); 101 102 data.err = uv_fs_close(localLoop, req, data.handle, &on_close); 103 if(data.err != 0) ret(req); 104 } 105 106 extern(C) @trusted nothrow static void on_open(uv_fs_t* req) { 107 auto result = cast(int)req.result; 108 auto data = cast(Data*)req.data; 109 if(result < 0) { 110 data.err = result; 111 ret(req); 112 return; 113 } else { 114 data.handle = result; 115 } 116 117 uv_fs_req_cleanup(req); 118 119 auto iov = uv_buf_init(cast(char*)(data.buf.ptr), cast(uint)data.buf.length); 120 data.err = uv_fs_write(localLoop, req, data.handle, &iov, 1, 0, &on_write); 121 if(data.err != 0) ret(req); 122 } 123 124 () @trusted nothrow { 125 import core.memory : GC; 126 import std..string : toStringz; 127 auto req = castMalloc!(uv_fs_t); 128 auto data = castMalloc!Data; 129 GC.addRoot(callback.ptr); 130 data.callback = callback; 131 data.buf = cast(void[])buffer; 132 req.data = data; 133 134 data.err = uv_fs_open(localLoop, req, path.toStringz, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR | S_IRGRP | S_IROTH, &on_open); 135 if(data.err != 0) ret(req); 136 }(); 137 } 138 139 /// 140 @safe unittest { 141 import std.file : exists, readText, remove; 142 import dpromise.utils : runEventloop; 143 writeAsyncWithCallback("hoge.txt", "hogehogepiyopiyo", (e) nothrow { 144 try { 145 assert(e is null); 146 assert(exists("hoge.txt")); 147 assert(readText("hoge.txt") == "hogehogepiyopiyo"); 148 149 scope(exit) { 150 remove("hoge.txt"); 151 } 152 } catch(Exception e) {} 153 }); 154 155 runEventloop(); 156 } 157 158 159 /** 160 Read file $(D path) with asynchronous IO and returns data read as a promise of untyped array. 161 If file size is larger than $(D up_to), only $(D up_to) bytes read. 162 163 If an error occurred, the promise will be rejected. 164 165 Params: 166 path = string repreesenting file path. 167 up_to = max buffer size. default value is $(D size_t.max). 168 169 Returns: Promise of untyped array of bytes read 170 171 See_Also: $(HTTP https://dlang.org/phobos/std_file.html#.read, std.file.read) 172 */ 173 nothrow Promise!(void[]) readAsync(string path, size_t up_to = size_t.max) { 174 return promise!(void[])((res, rej) nothrow { 175 readAsyncWithCallback(path, up_to, (data, e) nothrow { 176 e is null ? res(data) : rej(e); 177 }); 178 }); 179 } 180 181 /// 182 @system unittest { 183 import std.file : write, remove; 184 import dpromise.utils : runEventloop; 185 186 write("hoge.txt", "hogehogepiyopiyo"); 187 scope(exit) remove("hoge.txt"); 188 189 readAsync("hoge.txt").then( 190 (data) { 191 assert(data !is null); 192 assert(cast(string)data == "hogehogepiyopiyo"); 193 } 194 ); 195 196 readAsync("hoge.txt", 8).then( 197 (data) { 198 assert(data !is null); 199 assert(cast(string)data == "hogehoge"); 200 } 201 ); 202 203 runEventloop(); 204 } 205 206 207 /** 208 Read file $(D path) with asynchronous IO then calls the $(D callback) function. 209 If file size is larger than $(D up_to), only $(D up_to) bytes read. 210 211 If an error occurred, the $(D callback) function will be called with the error. 212 213 Params: 214 path = string repreesenting file path. 215 up_to = max buffer size. default value is $(D size_t.max). 216 callback = a fuction called when operation finished or an error occurred. 217 */ 218 nothrow @safe void readAsyncWithCallback(string path, size_t up_to, void delegate(void[], Exception) nothrow callback) 219 in { 220 assert(callback !is null); 221 } body { 222 struct Data { 223 int err; 224 int handle; 225 void delegate(void[], Exception) nothrow callback; 226 size_t up_to; 227 ubyte[] buf; 228 } 229 230 extern(C) nothrow @trusted static void ret(uv_fs_t* req) { 231 auto data = cast(Data*)(req.data); 232 auto e = factory(data.err); 233 234 if(e !is null) data.buf = null; 235 236 data.callback(cast(void[])(data.buf), e); 237 238 scope(exit) { 239 import core.memory : GC; 240 import core.stdc.stdlib : free; 241 GC.removeRoot(data.callback.ptr); 242 uv_fs_req_cleanup(req); 243 free(data); 244 free(req); 245 } 246 } 247 248 extern(C) nothrow @trusted static void on_read(uv_fs_t* req) { 249 auto result = cast(int)req.result; 250 auto data = cast(Data*)req.data; 251 if(result < 0) { 252 data.err = result; 253 ret(req); 254 return; 255 } 256 257 uv_fs_req_cleanup(req); 258 259 data.err = uv_fs_close(localLoop, req, data.handle, &ret); 260 if(data.err != 0) ret(req); 261 } 262 263 extern(C) nothrow @trusted static void on_stat(uv_fs_t* req) { 264 auto result = cast(int)req.result; 265 auto data = cast(Data*)req.data; 266 immutable size = req.statbuf.st_size; 267 if(result < 0) { 268 data.err = result; 269 ret(req); 270 return; 271 } 272 273 uv_fs_req_cleanup(req); 274 275 auto buf = new ubyte[](data.up_to < size ? data.up_to : size); 276 data.buf = buf; 277 auto iov = uv_buf_init(cast(char*)(buf.ptr), cast(uint)buf.length); 278 279 data.err = uv_fs_read(localLoop, req, data.handle, &iov, 1, 0, &on_read); 280 if(data.err != 0) ret(req); 281 } 282 283 extern(C) nothrow @trusted static void on_open(uv_fs_t* req) { 284 auto result = cast(int)req.result; 285 auto data = cast(Data*)req.data; 286 if(result >= 0) { 287 data.handle = result; 288 } else { 289 data.err = result; 290 ret(req); 291 return; 292 } 293 294 uv_fs_req_cleanup(req); 295 296 data.err = uv_fs_fstat(localLoop, req, data.handle, &on_stat); 297 if(data.err != 0) ret(req); 298 } 299 300 () @trusted nothrow { 301 import core.memory : GC; 302 GC.addRoot(callback.ptr); 303 auto data = castMalloc!Data; 304 data.callback = callback; 305 data.up_to = up_to; 306 data.buf = null; 307 308 auto req = castMalloc!uv_fs_t; 309 req.data = data; 310 import std..string : toStringz; 311 data.err = uv_fs_open(localLoop, req, path.toStringz, O_RDONLY, 0, &on_open); 312 if(data.err != 0) ret(req); 313 }(); 314 } 315 316 /// 317 @system unittest { 318 import std.file : write, remove; 319 import dpromise.utils : runEventloop; 320 write("hoge.txt", "hogehogepiyopiyo"); 321 scope(exit) remove("hoge.txt"); 322 323 readAsyncWithCallback("hoge.txt", size_t.max, (data, e) nothrow { 324 try { 325 assert(e is null); 326 assert(data !is null); 327 328 assert(cast(string)data == "hogehogepiyopiyo"); 329 } catch(Exception e) {} 330 }); 331 332 readAsyncWithCallback("hoge.txt", 8, (data, e) nothrow { 333 try { 334 assert(e is null); 335 assert(data !is null); 336 337 assert(cast(string)data == "hogehoge"); 338 } catch(Exception e) {} 339 }); 340 341 runEventloop(); 342 } 343 344 ///ditto 345 nothrow @safe void readAsyncWithCallback(string path, void delegate(void[], Exception) nothrow callback) { 346 readAsyncWithCallback(path, size_t.max, callback); 347 } 348 349 /// 350 @system unittest { 351 import std.file : write, remove; 352 import dpromise.utils : runEventloop; 353 354 write("hoge.txt", "hogehogepiyopiyo"); 355 scope(exit) remove("hoge.txt"); 356 357 readAsyncWithCallback("hoge.txt", (data, e) nothrow { 358 try { 359 assert(e is null); 360 assert(data !is null); 361 362 assert(cast(string)data == "hogehogepiyopiyo"); 363 } catch(Exception e) {} 364 }); 365 366 runEventloop(); 367 } 368 369 370 /** 371 Read file $(D path) with asynchronous IO and return data read as a promise of string(validate with $(HTTP https://dlang.org/phobos/std_utf.html#.validate, std.utf.validate)) 372 373 If an error occurred, the promise will be rejected. 374 375 Params: 376 path = string representing file path. 377 378 Returns: A promise of string read. 379 380 See_Also: $(HTTP https://dlang.org/phobos/std_file.html#.readText, std.file.readText) 381 */ 382 nothrow Promise!S readTextAsync(S = string)(string path) if(isSomeString!S) { 383 return promise!S((res, rej) nothrow { 384 readTextAsyncWithCallback!S(path, (s, e) { 385 e is null ? res(s) : rej(e); 386 }); 387 }); 388 } 389 390 /// 391 @system unittest { 392 import std.file : write, remove; 393 import dpromise.utils : runEventloop; 394 395 write("hoge.txt", "hogehogepiyopiyo"); 396 scope(exit) remove("hoge.txt"); 397 398 readTextAsync("hoge.txt").then( 399 (s) { 400 assert(s == "hogehogepiyopiyo"); 401 } 402 ); 403 404 runEventloop(); 405 } 406 407 408 /** 409 Read file $(D path) with asynchronous IO then calls the $(D callback) function with data as string(validate with $(HTTP https://dlang.org/phobos/std_utf.html#.validate, std.utf.validate)). 410 411 If an error occurred, the $(D callback) function will be called with the error. 412 413 Params: 414 path = string representing file path. 415 callback = a fuction called when operation finished or an error occurred. 416 */ 417 nothrow @safe void readTextAsyncWithCallback(S = string)(string path, void delegate(S, Exception) nothrow callback) 418 if(isSomeString!S) 419 in { 420 assert(callback !is null); 421 } body { 422 readAsyncWithCallback(path, (data, err) nothrow { 423 try { 424 if(err !is null) throw err; 425 426 import std.utf : validate; 427 auto result = cast(S)data; 428 validate(result); 429 callback(result, null); 430 } catch(Exception e) { 431 callback(null, e); 432 } 433 }); 434 } 435 436 /// 437 @safe unittest { 438 import std.file : write, remove; 439 import dpromise.utils : runEventloop; 440 441 write("hoge.txt", "hogehogepiyopiyo"); 442 scope(exit) remove("hoge.txt"); 443 444 readTextAsyncWithCallback!string("hoge.txt", (s, e) nothrow { 445 assert(e is null); 446 assert(s == "hogehogepiyopiyo"); 447 }); 448 449 runEventloop(); 450 }