@naanlang/naan 1.0.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.
Files changed (98) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +46 -0
  3. package/dist/env_web.js +431 -0
  4. package/dist/naan.min.js +73 -0
  5. package/frameworks/browser/browser.nlg +30 -0
  6. package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
  7. package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
  8. package/frameworks/browser/sworker.js +192 -0
  9. package/frameworks/browser/terminals.nlg +787 -0
  10. package/frameworks/browser/workers.nlg +386 -0
  11. package/frameworks/client/apiclient.nlg +183 -0
  12. package/frameworks/client/client.nlg +29 -0
  13. package/frameworks/client/psm_client.nlg +572 -0
  14. package/frameworks/common/common.nlg +464 -0
  15. package/frameworks/common/preferences.nlg +131 -0
  16. package/frameworks/common/repltools.nlg +100 -0
  17. package/frameworks/common/watching.nlg +97 -0
  18. package/frameworks/node/apiserver.nlg +399 -0
  19. package/frameworks/node/filesystem.nlg +1012 -0
  20. package/frameworks/node/gitter.nlg +234 -0
  21. package/frameworks/node/node.nlg +81 -0
  22. package/frameworks/node/psm_server.nlg +465 -0
  23. package/frameworks/node/worker.nlg +560 -0
  24. package/frameworks/project/build.nlg +1229 -0
  25. package/frameworks/project/jszip.js +30 -0
  26. package/frameworks/project/jszip.nlg +87 -0
  27. package/frameworks/project/proj_cliser.nlg +139 -0
  28. package/frameworks/project/proj_console.nlg +137 -0
  29. package/frameworks/project/proj_folder.nlg +143 -0
  30. package/frameworks/project/proj_lambda.nlg +137 -0
  31. package/frameworks/project/proj_static.nlg +138 -0
  32. package/frameworks/project/project.nlg +29 -0
  33. package/frameworks/project/projects.nlg +587 -0
  34. package/frameworks/project/uglifyjs.js +1 -0
  35. package/frameworks/project/uglifyjs.nlg +79 -0
  36. package/frameworks/running/debugcom.nlg +163 -0
  37. package/frameworks/running/debugnub.nlg +565 -0
  38. package/frameworks/running/debugutil.nlg +510 -0
  39. package/frameworks/running/executors.nlg +767 -0
  40. package/frameworks/running/running.nlg +151 -0
  41. package/frameworks/running/sourcecode.nlg +346 -0
  42. package/frameworks/running/taskexec.nlg +196 -0
  43. package/frameworks/storage/dbt_pouch.nlg +773 -0
  44. package/frameworks/storage/file_manager.nlg +772 -0
  45. package/frameworks/storage/psm.nlg +326 -0
  46. package/frameworks/storage/psm_dbtables.nlg +814 -0
  47. package/frameworks/storage/resources.nlg +445 -0
  48. package/frameworks/storage/storage.nlg +52 -0
  49. package/lib/browser/env_web.js +431 -0
  50. package/lib/browser/env_webworker.js +146 -0
  51. package/lib/browser/require.js +1653 -0
  52. package/lib/core/naanlib.js +73 -0
  53. package/lib/env_node.js +282 -0
  54. package/lib/env_nodeworker.js +165 -0
  55. package/lib/node_repl.js +25 -0
  56. package/lib/node_repl_init.nlg +52 -0
  57. package/package.json +13 -0
  58. package/plugins/gitClient/clientops.nlg +317 -0
  59. package/plugins/gitClient/gitClient.nlg +63 -0
  60. package/plugins/gitClient/gitutils.nlg +1331 -0
  61. package/plugins/gitClient/minimatch.js +945 -0
  62. package/plugins/gitClient/minimatch_license.txt +15 -0
  63. package/plugins/gitClient/minimatch_readme.md +208 -0
  64. package/plugins/nodeLib/nodeLib.nlg +26 -0
  65. package/plugins/nodeLib/node_cp.nlg +65 -0
  66. package/plugins/nodeLib/node_fs.nlg +137 -0
  67. package/plugins/nodeLib/node_https.nlg +172 -0
  68. package/plugins/nodeLib/node_util.nlg +180 -0
  69. package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
  70. package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
  71. package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
  72. package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
  73. package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
  74. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
  75. package/plugins/serviceAws/aws_cognito.nlg +76 -0
  76. package/plugins/serviceAws/aws_dynamo.nlg +509 -0
  77. package/plugins/serviceAws/aws_lambda.nlg +95 -0
  78. package/plugins/serviceAws/aws_s3.nlg +134 -0
  79. package/plugins/serviceAws/aws_utils.nlg +86 -0
  80. package/plugins/serviceAws/dbt_aws.nlg +769 -0
  81. package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
  82. package/plugins/serviceAws/psm_aws.nlg +258 -0
  83. package/plugins/serviceAws/serviceAws.nlg +38 -0
  84. package/plugins/serviceGitHub/gitops.nlg +522 -0
  85. package/plugins/serviceGitHub/psm_github.nlg +205 -0
  86. package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
  87. package/plugins/serviceGitLab/gitops.nlg +528 -0
  88. package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
  89. package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
  90. package/test/harness.nlg +312 -0
  91. package/test/node_test.js +33 -0
  92. package/test/test_01_core.nlg +768 -0
  93. package/test/test_02_context.nlg +269 -0
  94. package/test/test_03_jsinterop.nlg +137 -0
  95. package/test/test_04_numerics.nlg +450 -0
  96. package/test/test_05_strings.nlg +63 -0
  97. package/test/test_06_lingoparse.nlg +500 -0
  98. package/test/test_07_lingo.nlg +623 -0
@@ -0,0 +1,814 @@
1
+ /*
2
+ * psm_dbtables.nlg
3
+ * frameworks/storage
4
+ *
5
+ * PSM Connector for file systems on databases.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2020-2021 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * FSview
16
+ *
17
+ * Filesystem services via the specified DBtables instance. This view object allows access to an
18
+ * underlying database using filesystem semantics. Naming provided by this filesystem adheres to the
19
+ * following rules:
20
+ * - terminology: a generic file or directory is a "node"
21
+ * - the path separator is "/"
22
+ * - filenames are non-empty string not containing "/"
23
+ * - directory names are non-empty strings with the sole "/" at the end
24
+ * - the names ".", "..", and anything containing ":" are forbidden
25
+ * - every directory has (or should have) an associated database record for itself
26
+ * - files lacking a valid directory parent are "orphans" listed during tree enumeration
27
+ * - pathnames are relative to the root of the view an d consist of a string with:
28
+ * - zero or more directory names joined by "/", then
29
+ * - either "", "/" for a directory, or "/" with a filename
30
+ * - the empty pathname "" refers to the root of the view
31
+ * - the underlying database uses records named with "/" characters for the directories
32
+ * - e.g. "a/" is the directory "a", and "a/b" is the file "b" in directory "a"
33
+ * - record names starting with "/" or having sequential "/" characters are no-name directories
34
+ * - no-name directories can be enumerated and accessed, but not created
35
+ * - pathnames starting with "/" are within a no-name directory in the root of the view
36
+ *
37
+ */
38
+
39
+ closure FSview(table, rootpath, local view, error) {
40
+ if !table
41
+ return (list(Error("invalid arguments")))
42
+ view = new(object, this)
43
+ view.table = table
44
+ view.rootpath = rootpath
45
+ view.path = JSpath.posix // for view.path.join(), etc.
46
+
47
+ // dirName
48
+ //
49
+ // Return the directory portion of the specified path.
50
+
51
+ function dirName(path, local slashdex) {
52
+ if path == "" || (slashdex = path.lastIndexOf("/")) < 0
53
+ "" // empty parent directory
54
+ else if path.slice(-1) != "/"
55
+ path.substring(0, slashdex+1) // file at end
56
+ else if path == "/" || (slashdex = path.lastIndexOf("/", slashdex-1)) < 0
57
+ "" // empty parent directory of directory
58
+ else
59
+ path.substring(0, slashdex+1) // directory at end
60
+ }
61
+
62
+ // baseName
63
+ //
64
+ // Return the final segment of the specified path, with no terminal slash.
65
+
66
+ function baseName(path, local slashdex) {
67
+ if path == "" || (slashdex = path.lastIndexOf("/")) < 0
68
+ path // no parent directory
69
+ else if path.slice(-1) != "/"
70
+ path.slice(slashdex+1) // file at end
71
+ else if path == "/" || (slashdex = path.lastIndexOf("/", slashdex-1)) < 0
72
+ path.slice(0, -1) // directory with empty parent
73
+ else
74
+ path.slice(slashdex+1, -1) // directory at end
75
+ }
76
+
77
+ // validName
78
+ //
79
+ // Test whether the specified file or directory name is valid. If isPath is true then we look at
80
+ // the basename of the assumed path.
81
+
82
+ function validName(name, isPath) {
83
+ if isPath {
84
+ if name == ""
85
+ return (true)
86
+ else if name.substring(0, 1) == "/" || path.indexOf("//") >= 0
87
+ return (false)
88
+ else
89
+ name = baseName(name) }
90
+ name != "" && name != "." && name != ".." && name.indexOf("/") < 0 && name.indexOf(":") < 0
91
+ }
92
+
93
+ // checkPath
94
+ //
95
+ // Execute callback with error if path invalid, otherwise return false.
96
+
97
+ function checkPath(path, callback, msg) {
98
+ if !string(path) {
99
+ if !msg
100
+ msg = "invalid path"
101
+ if !string(path)
102
+ path = "<".concat(typeof(path), ">") // e.g. "<xobject>"
103
+ asyncResult(callback, Error(msg, typeof(content), {
104
+ status: 400
105
+ name: "invalid_arguments"
106
+ path: path
107
+ }))
108
+ true }
109
+ }
110
+
111
+ // checkPathName
112
+ //
113
+ // Return a `(path, name) tuple for a directory, or false if callback executed due to error.
114
+
115
+ function checkPathName(path, callback, local name, slashdex) {
116
+ if path == ""
117
+ name = ""
118
+ else {
119
+ if checkPath(path, callback)
120
+ return (false) // non-string
121
+ if path.slice(-1) != "/"
122
+ path = path.concat("/") // force to be directory
123
+ slashdex = path.lastIndexOf("/", path.length-2)
124
+ if path == "/" || slashdex < 0
125
+ name = path
126
+ else
127
+ name = path.slice(slashdex+1, -1) // omit trailing "/"
128
+ }
129
+ list(path, name)
130
+ }
131
+
132
+ // dbDirRecords
133
+ //
134
+ // Return all the node records in the directory, optionally with transitive children.
135
+
136
+ closure dbDirRecords(path, deep, callback, local options) {
137
+ if !callback
138
+ return (syncAdapter(dbDirRecords, path, deep))
139
+ if checkPath(path, callback)
140
+ return
141
+ options = {
142
+ prefix: path
143
+ head: true
144
+ }
145
+ if deep
146
+ options.deep = true
147
+ view.table.records(options, callback)
148
+ }
149
+
150
+ // dbReadRecord
151
+ //
152
+ // Read a record at the specified path. If nodata is true then no content is included. If the
153
+ // path does not end with a slash and it's not found, then we try again with a slash.
154
+
155
+ closure dbReadRecord(path, nodata, callback, local proc, error, data) {
156
+ if !callback
157
+ return (syncAdapter(dbReadRecord, path, nodata))
158
+ if checkPath(path, callback)
159
+ return
160
+ if nodata
161
+ proc = view.table.head
162
+ else
163
+ proc = view.table.get
164
+ `(error, data) = proc(path)
165
+ if error && error.status == 404 && path.slice(-1) != "/"
166
+ `(error, data) = proc(path.concat("/"))
167
+ callback(error, data)
168
+ }
169
+
170
+ // dbWriteRecord
171
+ //
172
+ // Write a record at the specified path. If necessary this first reads the record. If the table
173
+ // has conflict detection then this updates to avoid conflict with the existing file. If options
174
+ // is non-false then it is a dictionary with optional keys:
175
+ // exclusive: true -- file must not already exist
176
+ // emptyok: true -- allow empty directory names (leading "/" or embdedded "//")
177
+ // md5: <hash> -- existing file content must match hash
178
+ // writenew: true -- write as a new file, with updated create time
179
+
180
+ closure dbWriteRecord(path, content, options, callback) {
181
+ if !callback
182
+ return (syncAdapter(dbWriteRecord, path, content, options))
183
+
184
+ function dbwriter(error, doc, local nowMs, nowstr, data, metadata) {
185
+ nowMs = Date.now()
186
+ nowstr = Date(nowMs).toISOString()
187
+ if error {
188
+ if error.status != 404
189
+ return (callback(error)) // failed, but not due to missing file
190
+ if !options.emptyok && !validName(path, true)
191
+ return (callback(Error("empty directory in path", { // invalid pathname
192
+ status: 403
193
+ name: "forbidden"
194
+ path: path
195
+ })))
196
+ metadata = {
197
+ finfo: { // new node
198
+ birthtimeMs: nowMs
199
+ birthtime: nowstr
200
+ }
201
+ }
202
+ if path.slice(-1) == "/"
203
+ metadata.finfo.type = "directory"
204
+ else
205
+ metadata.finfo.type = "file"
206
+ } else if options.exclusive
207
+ return (callback(Error("file already exists", { // only write when creating new file
208
+ status: 409
209
+ name: "conflict"
210
+ path: path
211
+ })))
212
+ else if options.md5 && options.md5 != doc._md5
213
+ return (callback(Error("file changed before overwrite", { // existing file didn't match expected hash
214
+ status: 412
215
+ name: "precondition_failed"
216
+ path: path
217
+ check: options.md5
218
+ file: doc._md5
219
+ })))
220
+ else { // update for new information
221
+ metadata = doc
222
+ doc.content = content }
223
+ metadata.finfo.mtimeMs = nowMs // update timestamps
224
+ metadata.finfo.mtime = nowstr
225
+ if options.writenew {
226
+ metadata.finfo.birthtimeMs = nowMs
227
+ metadata.finfo.birthtime = nowstr
228
+ }
229
+ if string(content) {
230
+ if options.encoding
231
+ metadata.finfo.encoding = options.encoding // caller-specified string encoding
232
+ else
233
+ metadata.finfo.encoding = "utf8" } // default string encoding
234
+ else if content && !xobject(content)
235
+ return (callback(Error("unsupported content type", typeof(content), {
236
+ status: 400
237
+ name: "invalid_arguments"
238
+ path: path
239
+ })))
240
+ if doc
241
+ `(error, data) = view.table.update(doc) // update existing record
242
+ else
243
+ `(error, data) = view.table.add(path, content, metadata) // create new record
244
+ if error
245
+ callback(error)
246
+ else
247
+ callback(false, { ok: true })
248
+ }
249
+
250
+ if checkPath(path, callback)
251
+ return
252
+ if table.revcheck || options.exclusive || options.md5 || !options.writenew || options.overwriteDirCheck
253
+ view.table.head(path, dbwriter.proc) // get head first, check, then write
254
+ else
255
+ future(function dbGetMissing() { dbwriter({ status: 404 }) }).run() // write with simulated missing record
256
+ }
257
+
258
+ // dbMakePathDirs
259
+ //
260
+ // Generate all the directories in the specified path, if they do not exist. This will fail if a
261
+ // file exists where a directory is needed.
262
+
263
+ closure dbMakePathDirs(path, callback) {
264
+ if !callback
265
+ return (syncAdapter(dbMakePathDirs, path))
266
+ if path == ""
267
+ return (asyncResult(callback, false, { created: false })) // root always already exists
268
+ if checkPath(path, callback)
269
+ return
270
+ if path.slice(-1) != "/"
271
+ path = path.concat("/")
272
+ view.table.get(path, function(error, data, local pathdirs, buildpath, item) {
273
+ if !error
274
+ return (callback(false, { created: false })) // fastest: already exists
275
+ pathdirs = path.split("/")
276
+ buildpath = ""
277
+ for item in pathdirs
278
+ if item.length > 0 {
279
+ buildpath = buildpath.concat(item)
280
+ `(error, data) = view.table.get(buildpath) // check for file in the way
281
+ if !error
282
+ return (callback(Error("file in the way while building directory path",
283
+ {
284
+ status: 409
285
+ name: "conflict"
286
+ path: buildpath
287
+ })))
288
+ if buildpath.slice(-1) != "/"
289
+ buildpath = buildpath.concat("/")
290
+ `(error, data) = view.table.get(buildpath) // get desired directory
291
+ if error.status == 404
292
+ `(error, data) = dbWriteRecord(buildpath) // create desired directory
293
+ if error
294
+ return (callback(Error("failed building directory path", path, "on", buildpath, error)))
295
+ }
296
+ else
297
+ buildpath = buildpath.concat("/") // had an extra slash here
298
+ if !error
299
+ data = { created: true } // leaf directory newly-created
300
+ callback(error, data)
301
+ })
302
+ }
303
+
304
+ // copyFile
305
+ //
306
+ // Copy a file given the source file's document, with optional move.
307
+
308
+ closure copyFile(doc, dest, options, local srcpath, newpath, error, data) {
309
+ srcpath = doc.key
310
+ if dest.indexOf("/") < 0
311
+ newpath = dirName(srcpath).concat(dest) // renaming within same directory
312
+ else {
313
+ if dest.slice(-1) == "/" // moving to new directory location:
314
+ newpath = dest.concat(baseName(srcpath)) // new path is new pathname
315
+ else { // moving and renaming:
316
+ newpath = dest // new path is new pathname
317
+ dest = dirName(dest) } // dest is new parent directory path
318
+ `(error, data) = dbMakePathDirs(dest) // ensure parent directories exist
319
+ if error
320
+ return (list(Error("copyFile: can't make destination", {
321
+ source: srcpath
322
+ destination: dest
323
+ }, error)))
324
+ else if !data.created {
325
+ `(error, data) = view.table.get(newpath.concat("/")) // check for directory in the way
326
+ if !error
327
+ return (callback(Error("copyFile: would overwrite directory",
328
+ {
329
+ status: 409
330
+ name: "conflict"
331
+ source: srcpath
332
+ destination: dest
333
+ existing: newpath.concat("/")
334
+ }))) } }
335
+ `(error, data) = view.table.copy(srcpath, newpath)
336
+ if error
337
+ return (list(Error("copyFile: copy failed", {
338
+ source: srcpath
339
+ destination: newpath
340
+ }, error)))
341
+ if options.move {
342
+ `(error, data) = view.table.delete(doc)
343
+ if error
344
+ error = Error("copyFile: couldn't remove original", {
345
+ source: srcpath
346
+ destination: newpath
347
+ }, error) }
348
+ if !error
349
+ data = { ok: true }
350
+ list(error, data)
351
+ }
352
+
353
+ // copyDirectory
354
+ //
355
+ // Copy a directory by its source path, with optional move.
356
+
357
+ closure copyDirectory(srcpath, dest, options, local error, nodes, data, destpath, errors, docpath, newpath, srcroot) {
358
+ `(error, nodes) = view.table.records({ prefix: srcpath })
359
+ if error
360
+ return (list(Error("copyDirectory: can't enumerate source directory", {
361
+ source: srcpath
362
+ destination: dest
363
+ }, error)))
364
+ if nodes.rows.length == 0 || nodes.rows[0].key != srcpath
365
+ return (list(Error("copyDirectory: source directory not found", {
366
+ status: 404 // shouldn't happen (sanity check)
367
+ name: "not_found"
368
+ source: srcpath
369
+ destination: dest
370
+ })))
371
+ if dest.indexOf("/") < 0 {
372
+ destpath = dirName(srcpath).concat(dest) // new directory name in same parent directory
373
+ dest = dirName(srcpath) } // dest is new parent pathname
374
+ else {
375
+ if dest.slice(-1) == "/"
376
+ destpath = dest.concat(baseName(srcpath)) // same name; new location
377
+ else {
378
+ destpath = dest // new full pathname
379
+ dest = dirName(dest) } // dest is new parent pathname
380
+ }
381
+ if destpath != "" && destpath.slice(-1) != "/"
382
+ destpath = destpath.concat("/") // get actual path of new directory
383
+ //
384
+ // Change srcpath to destpath for all records. Note that dest is the name of our new parent
385
+ // directory without a terminal slash, while destpath is the name of our new directory under
386
+ // which any children will be stored and does end in slash.
387
+ //
388
+ if srcpath == destpath
389
+ return (list(false, { ok: true })) // no change
390
+ if destpath.indexOf(srcpath) == 0
391
+ return (list(Error("copyDirectory: destination within source", {
392
+ status: 403 // trying to move within itself
393
+ name: "forbidden"
394
+ source: srcpath
395
+ destination: destpath
396
+ })))
397
+ `(error, data) = dbMakePathDirs(dest) // ensure future parent directories exist
398
+ if error
399
+ return (list(Error("copyDirectory: can't create destination", {
400
+ source: srcpath
401
+ destination: dest
402
+ }, error)))
403
+ if !data.created {
404
+ errors = [] // existing directory - check for overwrites
405
+ for item in nodes.rows {
406
+ docpath = item.key
407
+ newpath = destpath.concat(docpath.slice(srcpath.length))
408
+ `(error, data) = view.table.md5(newpath)
409
+ if !error
410
+ errors.push(newpath) // something exists there
411
+ else if error.status != 404
412
+ return (list(Error("copyDirectory: preflight failed", { // unexpected error
413
+ source: srcpath
414
+ destination: newpath
415
+ }, error))) }
416
+ if errors.length > 0
417
+ return (list(Error("copyDirectory: would overwrite", {
418
+ status: 409 // destination files already exist
419
+ name: "conflict"
420
+ source: srcpath
421
+ destination: destpath
422
+ existing: errors
423
+ }))) }
424
+ for item in nodes.rows {
425
+ docpath = item.key
426
+ newpath = destpath.concat(docpath.slice(srcpath.length))
427
+ `(error, data) = view.table.copy(docpath, newpath)
428
+ if error
429
+ errors.push(Error("copyDirectory: can't create copy", { // failed to add copy
430
+ source: docpath
431
+ destination: newpath
432
+ }, error))
433
+ else if options.move {
434
+ if !srcroot
435
+ srcroot = item // delete source root later
436
+ else {
437
+ `(error, data) = view.table.delete(item.doc) // delete original
438
+ if error
439
+ errors.push(Error("copyDirectory: can't remove original", {
440
+ source: docpath
441
+ destination: newpath
442
+ }, error)) } } }
443
+ if errors.length > 0
444
+ list(Error("copyDirectory: failures occurred", {
445
+ source: srcpath
446
+ destination: destpath
447
+ failures: errors
448
+ }, errors))
449
+ else {
450
+ if srcroot {
451
+ `(error, data) = view.table.delete(srcroot.doc) // delete original root
452
+ if error
453
+ return (list(Error("copyDirectory: can't remove original root", {
454
+ source: srcpath
455
+ destination: destpath
456
+ }, error))) }
457
+ list(false, { ok: true }) }
458
+ }
459
+
460
+ // info
461
+ //
462
+ // Get file/directory info at the specified path.
463
+
464
+ view.info = closure info(path, callback) {
465
+ if !callback
466
+ return (syncAdapter(info, path))
467
+ dbReadRecord(path, true, function(error, data, local finfo) {
468
+ if data {
469
+ finfo = new(data.finfo)
470
+ if !finfo
471
+ finfo = { }
472
+ if data._md5
473
+ finfo.md5 = data._md5
474
+ if data._length
475
+ finfo.length = data._length }
476
+ else if path == "" {
477
+ error = false // we were asked for info on superoot
478
+ finfo = {
479
+ md5: HashMD5("")
480
+ length: 0
481
+ type: "directory"
482
+ } }
483
+ callback(error, finfo)
484
+ })
485
+ }
486
+
487
+ // read
488
+ //
489
+ // Read file content at the specified path.
490
+
491
+ view.read = closure read(path, options, callback) {
492
+ if !callback
493
+ return (syncAdapter(read, path, options))
494
+ dbReadRecord(path, false, function(error, data) {
495
+ if error || data.finfo.type != "directory"
496
+ callback(error, data.content)
497
+ else
498
+ callback(Error("cannot read a directory", { source: path }))
499
+ })
500
+ }
501
+
502
+ // readFile
503
+ //
504
+ // Write a file at the specified path
505
+
506
+ view.readFile = function readFile(path, options, callback) {
507
+ read(path, options, callback)
508
+ }
509
+
510
+ // mkdir
511
+ //
512
+ // Make a directory at the specified path. The data value is a dictionary with the single
513
+ // key created having value true if we created the directory or false if the directory
514
+ // already exists.
515
+
516
+ view.mkdir = closure mkdir(path, callback) {
517
+ if !callback
518
+ return (syncAdapter(mkdir, path))
519
+ if !validName(path, true)
520
+ return (asyncResult(callback, Error("invalid directory", path)))
521
+ dbMakePathDirs(path, callback)
522
+ }
523
+
524
+ // write
525
+ //
526
+ // Write a file at the specified path, creating if needed by default. The following options are
527
+ // available:
528
+ // exclusive: true -- file must not already exist
529
+ // md5: <hash> -- existing file content must match hash
530
+
531
+ view.write = closure write(path, content, options, callback) {
532
+ if !callback
533
+ return (syncAdapter(write, path, content, options))
534
+ if checkPath(path, callback)
535
+ return
536
+ if !content
537
+ return (asyncResult(callback, Error("content required")))
538
+ if path.slice(-1) == "/"
539
+ return (asyncResult(callback, Error("cannot write to a directory", { path: path })))
540
+ if !validName(path, true)
541
+ return (asyncResult(callback, Error("invalid filename", { path: path })))
542
+ dbMakePathDirs(dirName(path), function(error, data) { // ensure parent directories exist
543
+ if error
544
+ return (callback(error))
545
+ if !data.created {
546
+ `(error, data) = view.table.head(path.concat("/")) // don't write file over direcctory
547
+ if !error
548
+ return (callback(list(Error("cannot overwrite a directory", { path: path }))))
549
+ }
550
+ dbWriteRecord(path, content, options, callback) })
551
+ }
552
+
553
+ // writeFile
554
+ //
555
+ // Write a file at the specified path
556
+
557
+ view.writeFile = function writeFile(path, data, options, callback) {
558
+ write(path, data, options, callback)
559
+ }
560
+
561
+ // copy
562
+ //
563
+ // Copy / move / rename a file or directory. The source is the full path of the item. The
564
+ // destination is:
565
+ // - a new name if it has no embedded slashes, or
566
+ // - a new location if it ends in a slash, otherwise
567
+ // - a new pathname for the source if it does not end in a slash.
568
+ // This works by creating a new file or directory of files in the new location and then
569
+ // optionally deleting the files in the existing location. If options.move is true then we
570
+ // delete the source after the copy.
571
+
572
+ view.copy = closure copy(srcpath, dest, options, callback) {
573
+ if !callback
574
+ return (syncAdapter(copy, srcpath, dest, options))
575
+ if checkPath(srcpath, callback, "invalid source path")
576
+ return
577
+ if checkPath(dest, callback, "invalid destination path")
578
+ return
579
+ if !validName(dest, true)
580
+ return (asyncResult(callback, Error("invalid destination", { destination: dest })))
581
+ dbReadRecord(srcpath, true, function(error, data, local result) {
582
+ if error
583
+ callback(list(Error("copy can't access source", {
584
+ source: srcpath
585
+ destination: dest
586
+ }, error)))
587
+ else if srcpath.slice(-1) == "/"
588
+ result = copyDirectory(srcpath, dest, options) // hard case: directory with possibly many children
589
+ else
590
+ result = copyFile(data, dest, options) // easy case: single file
591
+ apply(callback, result)
592
+ })
593
+ }
594
+
595
+ // delete
596
+ //
597
+ // Delete the file at the specified path, or a directory if the options dictionary includes:
598
+ // recursive: true -- "recursively" delete directory and children
599
+
600
+ view.delete = closure delete(path, options, callback) {
601
+ if !callback
602
+ return (syncAdapter(delete, path, options))
603
+ dbReadRecord(path, true, function(error, data, local errors, item, basedir) {
604
+ if error
605
+ return (callback(error))
606
+ if data.key.slice(-1) != "/" { // just delete the file
607
+ `(error, data) = view.table.delete(data)
608
+ if !error
609
+ data = { ok: true }
610
+ return (callback(error, data)) }
611
+ if !options.recursive
612
+ return (callback(Error("delete directory must specify recursive option", {
613
+ status: 403
614
+ name: "forbidden"
615
+ path: path
616
+ })))
617
+ if path.slice(-1) != "/"
618
+ path = path.concat("/") // avoid deleting longer filenames with same parent and prefix
619
+ `(error, data) = dbDirRecords(path, true)
620
+ if error
621
+ return (callback(Error("delete directory enumeration failed", {
622
+ path: path
623
+ }, error)))
624
+ errors = []
625
+ for item in data.rows {
626
+ if item.key == path {
627
+ basedir = item // delete base directory at end
628
+ continue
629
+ }
630
+ `(error, data) = view.table.delete(item.doc)
631
+ if error
632
+ errors.push(error) }
633
+ if errors.length != 0
634
+ error = Error("some directory children not deleted", errors)
635
+ else {
636
+ `(error, data) = view.table.delete(basedir.doc)
637
+ if error
638
+ error = Error("delete directory failed", {
639
+ path: path
640
+ }, error)
641
+ }
642
+ if !error
643
+ data = { ok: true }
644
+ callback(error, data)
645
+ })
646
+ }
647
+
648
+ // dirList
649
+ //
650
+ // Read a directory at the specified path
651
+ // ### options currently ignored, for better or woise
652
+
653
+ view.dirList = closure dirList(path, options, callback, local name, dirdict) {
654
+ if !callback
655
+ return (syncAdapter(readir, path))
656
+ `(path, name) = checkPathName(path, callback)
657
+ if !path
658
+ return // already executed callback with error
659
+ dirdict = {
660
+ name: name.slice(0,-1)
661
+ path: path
662
+ data: {
663
+ platform: table.type
664
+ semantics: "dbfs"
665
+ pathsep: "/"
666
+ }
667
+ children: []
668
+ }
669
+ dbDirRecords(path, false, function(error, data, local item, result, slashdex, offset, finfo) {
670
+ if error
671
+ return (callback(error))
672
+ if data.rows.length == 0 && path != ""
673
+ return (callback(Error("directory not found", // should be something there
674
+ {
675
+ status: 404
676
+ name: "not_found"
677
+ path: path
678
+ })))
679
+ offset = path.length
680
+ for item in data.rows {
681
+ slashdex = item.key.indexOf("/", offset)
682
+ if slashdex < 0 || slashdex == item.key.length-1 { // file, or child directory
683
+ finfo = new(item.doc.finfo)
684
+ if !finfo
685
+ finfo = { }
686
+ name = item.key.slice(offset)
687
+ if name.slice(-1) == "/"
688
+ name = name.slice(0,-1) // remove trailing "/" from directory
689
+ if item.doc._md5
690
+ finfo.md5 = item.doc._md5
691
+ if item.doc._length
692
+ finfo.length = item.doc._length
693
+ if item.key.length > offset // not the directory itself
694
+ dirdict.children.push({
695
+ name: name
696
+ info: finfo }) } }
697
+ callback(false, dirdict)
698
+ })
699
+ }
700
+
701
+ // readir
702
+ //
703
+ // Read a directory at the specified path
704
+
705
+ view.readir = closure readir(path, callback, local name, dirdict) {
706
+ dirList(path, { head: true }, callback)
707
+ }
708
+
709
+ // tree
710
+ //
711
+ // Return a tree for the specified path. The path is relative to the root of the view.
712
+
713
+ view.tree = closure tree(path, options, callback, local name, treedict, item, offset) {
714
+ if !callback
715
+ return (syncAdapter(tree, path, options))
716
+ `(path, name) = checkPathName(path, callback)
717
+ if !path
718
+ return // already executed callback with error
719
+ options = { // ### might want to allow some options
720
+ prefix: path
721
+ deep: true
722
+ }
723
+ treedict = {
724
+ name: name.slice(0,-1)
725
+ data: {
726
+ platform: table.type
727
+ semantics: "dbfs"
728
+ pathsep: "/"
729
+ }
730
+ parents: []
731
+ children: []
732
+ }
733
+
734
+ for item in path.split("/").slice(0,-1)
735
+ treedict.parents.push(item)
736
+ offset = path.length // offset to path's child names (after any slash)
737
+ view.table.records(options, function(error, data, local index, leaf, parent, slashdex) {
738
+ if error
739
+ return (callback(error))
740
+ if data.rows.length == 0 && path != ""
741
+ return (callback(Error("directory not found",
742
+ {
743
+ status: 404
744
+ name: "not_found"
745
+ path: path
746
+ })))
747
+ index = { }
748
+ index[""] = treedict // treedict is parent of children in top level
749
+ for item in data.rows {
750
+ name = item.key.slice(offset) // the node's pathname starting with slash
751
+ if name == ""
752
+ continue // root already exists
753
+ slashdex = name.lastIndexOf("/")
754
+ if slashdex < 0 { // file child in root
755
+ parent = ""
756
+ child = { name: name } }
757
+ else if slashdex == name.length-1 { // directory child
758
+ if name == "/" {
759
+ parent = "" // "no-name" directory in root
760
+ leaf = "" }
761
+ else { // named or non-root directory
762
+ slashdex = name.lastIndexOf("/", name.length-2) // penultimate slash
763
+ parent = name.substring(0, slashdex+1) // includes parent's ending slash
764
+ leaf = name.slice(slashdex+1, -1) } // omits child's ending slash
765
+ child = index[name] = {
766
+ name: leaf
767
+ children: [] } }
768
+ else { // file child not in root
769
+ parent = name.substring(0, slashdex+1)
770
+ leaf = name.slice(slashdex+1)
771
+ child = { name: leaf } }
772
+ if index[parent]
773
+ index[parent].children.push(child)
774
+ else { // orphaned child doesn't have <parent>/ record
775
+ if !treedict.orphans
776
+ treedict.orphans = []
777
+ child.key = item.key
778
+ treedict.orphans.push(child)
779
+ }
780
+ }
781
+ callback(false, treedict)
782
+ })
783
+ }
784
+
785
+ // util
786
+ //
787
+ // Utility operations for the filesystem
788
+
789
+ view.util = MakePSMutil(view)
790
+
791
+ // finis
792
+ list(false, view)
793
+ };
794
+
795
+
796
+ /*
797
+ * psmdInit
798
+ *
799
+ * Initialize the module.
800
+ *
801
+ */
802
+
803
+ function psmdInit(local manifest) {
804
+
805
+ manifest = `(FSview, psmdInit)
806
+
807
+ Naan.module.build(module.id, "psm_dbtables", function(modobj, compobj) {
808
+ require("./storage.nlg")
809
+ require("./psm.nlg")
810
+ compobj.manifest = manifest
811
+ modobj.exports.FSview = FSview
812
+ })
813
+
814
+ } ();