@naanlang/naan 1.0.1 → 1.0.4
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.
- package/LICENSE.md +1 -1
- package/README.md +5 -5
- package/bin/index.js +18 -15
- package/dist/env_web.js +146 -16
- package/dist/naan.min.js +5 -5
- package/frameworks/browser/sworker.js +299 -91
- package/frameworks/browser/terminals.nlg +23 -10
- package/frameworks/browser/workers.nlg +26 -12
- package/frameworks/client/apiclient.nlg +17 -4
- package/frameworks/client/psm_client.nlg +178 -81
- package/frameworks/common/common.nlg +14 -4
- package/frameworks/node/apiserver.nlg +31 -15
- package/frameworks/node/filesystem.nlg +230 -46
- package/frameworks/node/gitter.nlg +27 -6
- package/frameworks/node/psm_server.nlg +113 -37
- package/frameworks/project/build.nlg +40 -23
- package/frameworks/project/projects.nlg +50 -27
- package/frameworks/running/debugnub.nlg +2 -5
- package/frameworks/storage/csv.nlg +120 -0
- package/frameworks/storage/dbt_pouch.nlg +41 -25
- package/frameworks/storage/psm.nlg +108 -28
- package/frameworks/storage/psm_dbtables.nlg +7 -3
- package/frameworks/storage/resources.nlg +30 -2
- package/lib/browser/env_web.js +148 -18
- package/lib/browser/env_webworker.js +1 -1
- package/lib/browser/require.js +1 -1
- package/lib/core/naanlib.js +5 -5
- package/lib/env_node.js +9 -1
- package/package.json +2 -1
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +1 -1
- package/plugins/serviceAws/aws_dynamo.nlg +625 -141
- package/plugins/serviceAws/aws_dynextra.nlg +294 -0
- package/plugins/serviceAws/dbt_aws.nlg +31 -11
- package/plugins/serviceAws/psm_aws.nlg +42 -26
- package/plugins/serviceAws/serviceAws.nlg +1 -0
- package/plugins/serviceGitHub/psm_github.nlg +41 -25
- package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* csv.nlg
|
|
3
|
+
* Naanlib/frameworks/storage
|
|
4
|
+
*
|
|
5
|
+
* CSV file parsing and generation.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2022 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* csvParse
|
|
16
|
+
*
|
|
17
|
+
* Parse text lines in CSV format into a row array of column arrays. The delimiter defaults to
|
|
18
|
+
* comma, but can be replaced with anything that's not crazy. Normally missing columns are replaced
|
|
19
|
+
* with false, but the empty parameter can be used to override this. For example, specify "" to
|
|
20
|
+
* use an empty string for the empty column.
|
|
21
|
+
* The parse regex is derived from the following Stack Overflow article:
|
|
22
|
+
* https://stackoverflow.com/questions/1293147/example-javascript-code-to-parse-csv-data
|
|
23
|
+
* Please see the answer by Joshua McLauchlan.
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
function csvParse(text, delim, empty, local res, parseRx, quoteRx, result, matches) {
|
|
28
|
+
if !string(text)
|
|
29
|
+
return (false) // don't hang or be stupid
|
|
30
|
+
res = '(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))'
|
|
31
|
+
if delim
|
|
32
|
+
res = res.replace(RegExp(',', 'g'), delim)
|
|
33
|
+
else
|
|
34
|
+
delim = ','
|
|
35
|
+
parseRx = RegExp(res, 'gi')
|
|
36
|
+
quoteRx = RegExp('""', 'g')
|
|
37
|
+
result = [[]]
|
|
38
|
+
while !!(matches = parseRx.exec(text)) {
|
|
39
|
+
if !matches[0]
|
|
40
|
+
break // should never happen, but don't hang
|
|
41
|
+
if matches[1].length > 0 && matches[1] !== delim
|
|
42
|
+
result.push([]) // first column in row
|
|
43
|
+
result[result.length-1].push(cond(
|
|
44
|
+
if matches[2] !== undefined
|
|
45
|
+
matches[2].replace(quoteRx, '"') // quoted string, including ""
|
|
46
|
+
else if matches[3] !== ""
|
|
47
|
+
matches[3] // unquoted, non-empty
|
|
48
|
+
else
|
|
49
|
+
empty // empty column
|
|
50
|
+
))
|
|
51
|
+
}
|
|
52
|
+
result
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* csvPrint
|
|
58
|
+
*
|
|
59
|
+
* Print an array of row arrays of columns to a string, reversing csvParse.
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
function csvPrint(rows, local output, quoter, row, line, delim, col) {
|
|
64
|
+
quoter = csvQuoteGenerator()
|
|
65
|
+
output = ""
|
|
66
|
+
for row in rows {
|
|
67
|
+
delim = line = ""
|
|
68
|
+
for col in row {
|
|
69
|
+
line = line.concat(delim, quoter(col))
|
|
70
|
+
delim = ","
|
|
71
|
+
}
|
|
72
|
+
output = output.concat(line, "\n")
|
|
73
|
+
}
|
|
74
|
+
output
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* csvQuoteGenerator
|
|
80
|
+
*
|
|
81
|
+
* Return a function that quotes a string as needed for a CSV file with the given delimiter. This
|
|
82
|
+
* is a generator because it will likely be needed frequently. Comma is the default delimeter, just
|
|
83
|
+
* like God intended for a *C*sv file.
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
closure csvQuoteGenerator(delimiter, local rxComma, rxQuote) {
|
|
88
|
+
if !delimiter
|
|
89
|
+
delimiter = ','
|
|
90
|
+
rxDelimiter = RegExp('['.concat('"\\', delimiter, ']'))
|
|
91
|
+
rxQuote = RegExp("\"", "g")
|
|
92
|
+
|
|
93
|
+
// csvQuote
|
|
94
|
+
// Quote a string for a CSV file as specified.
|
|
95
|
+
function csvQuote(str) {
|
|
96
|
+
if rxDelimiter.test(str)
|
|
97
|
+
return ("\"".concat(str.replace(rxQuote, "\"\""), "\""))
|
|
98
|
+
return (str)
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
* csvInit
|
|
105
|
+
*
|
|
106
|
+
* Initialize the component.
|
|
107
|
+
*
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
function csvInit(local manifest) {
|
|
111
|
+
manifest = `(csvParse, csvPrint, csvQuoteGenerator, csvInit)
|
|
112
|
+
|
|
113
|
+
Naan.module.build(module.id, "csv", function(modobj, compobj) {
|
|
114
|
+
require("./storage.nlg")
|
|
115
|
+
compobj.manifest = manifest
|
|
116
|
+
module.exports.csvParse = csvParse
|
|
117
|
+
module.exports.csvPrint = csvPrint
|
|
118
|
+
module.exports.csvQuoteGenerator = csvQuoteGenerator
|
|
119
|
+
})
|
|
120
|
+
}();
|
|
@@ -540,36 +540,26 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
540
540
|
type: "password" },
|
|
541
541
|
]
|
|
542
542
|
}
|
|
543
|
-
|
|
544
|
-
// hashResourceID
|
|
545
|
-
//
|
|
546
|
-
// Make a stable hash based on the resource credentials that we can persist or share between
|
|
547
|
-
// different domains accessing the same resource.
|
|
548
|
-
connector.hashResourceID = function hashResourceID(resource, local ident) {
|
|
549
|
-
ident = resource.urlName
|
|
550
|
-
if resource.label
|
|
551
|
-
ident = ident.concat(resource.label)
|
|
552
|
-
HashSHA256(ident)
|
|
553
|
-
}
|
|
554
543
|
|
|
555
544
|
// findResource
|
|
556
545
|
//
|
|
557
|
-
// Find a resource with the specified contents, returning the resID or false.
|
|
558
|
-
// is: if added, would the specified contents be redundant to an existing resource?
|
|
546
|
+
// Find a resource with the specified contents, returning the resID or false.
|
|
559
547
|
connector.findResource = function findResource(resource, local resID, creds) {
|
|
560
548
|
for resID in listResources().1 {
|
|
561
549
|
creds = access(resID).1
|
|
562
|
-
if resource.urlName == creds.urlName
|
|
550
|
+
if ((!resource.urlName ||resource.urlName == creds.urlName)
|
|
563
551
|
&& (!resource.label || resource.label == creds.label)
|
|
564
|
-
&& resource.authName == creds.authName
|
|
552
|
+
&& (!resource.authName || resource.authName == creds.authName))
|
|
565
553
|
return (resID)
|
|
566
554
|
}
|
|
567
555
|
false
|
|
568
556
|
}
|
|
569
557
|
|
|
570
|
-
//
|
|
558
|
+
// writeResource
|
|
571
559
|
//
|
|
572
|
-
// Add credentials for a named resource.
|
|
560
|
+
// Add or update credentials for a named resource. If no resID is specified this adds a new
|
|
561
|
+
// resource; otherwise it updates an existing one. The resource is a dictionary comprising these
|
|
562
|
+
// keys:
|
|
573
563
|
// label - [optional] label we use for this resource
|
|
574
564
|
// urlName - URL (e.g. "http://domain.com/dbname")
|
|
575
565
|
// - filepath (e.g. "/Users/richardz/dbname") - NodeJS filesystem access
|
|
@@ -579,10 +569,13 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
579
569
|
// hidden - [optional] don't enumerate to user
|
|
580
570
|
// locked - [optional] don't allow user delete
|
|
581
571
|
// existng - [optional] true to enable using existing database
|
|
582
|
-
// The return value is (error,
|
|
583
|
-
// keys and error diagnostic strings.
|
|
584
|
-
connector.
|
|
572
|
+
// The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
|
|
573
|
+
// keys and error diagnostic strings.
|
|
574
|
+
connector.writeResource = function writeResource(resource, resID,
|
|
575
|
+
local errors, creds, original, error, data) {
|
|
576
|
+
// badField - test for a bad string
|
|
585
577
|
function badField(str) { !string(str) || str.trim().length == 0 }
|
|
578
|
+
|
|
586
579
|
creds = { urlName: resource.urlName }
|
|
587
580
|
errors = { }
|
|
588
581
|
if resource.label
|
|
@@ -591,6 +584,8 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
591
584
|
creds.label = resource.urlName
|
|
592
585
|
if badField(creds.label)
|
|
593
586
|
errors.label = "invalid label"
|
|
587
|
+
else if !resID && findResource({ label: creds.label })
|
|
588
|
+
errors.label = "duplicate name"
|
|
594
589
|
if badField(resource.urlName)
|
|
595
590
|
errors.urlName = "required field"
|
|
596
591
|
else if !resource.existing {
|
|
@@ -612,15 +607,36 @@ closure DbConnector(psm, local connClassID, connector, watch) {
|
|
|
612
607
|
creds.hidden = resource.hidden
|
|
613
608
|
if resource.locked
|
|
614
609
|
creds.locked = resource.locked
|
|
615
|
-
resID
|
|
616
|
-
|
|
610
|
+
if resID {
|
|
611
|
+
`(error, data) = connector.vault.updateResource(resID, creds)
|
|
612
|
+
change = { changed: [resID] }
|
|
613
|
+
} else {
|
|
614
|
+
resID = UUID()
|
|
615
|
+
`(error, data) = connector.vault.addResource(resID, creds)
|
|
616
|
+
change = { added: [resID] }
|
|
617
|
+
}
|
|
617
618
|
if data {
|
|
618
|
-
watch.notify(connClassID,
|
|
619
|
-
data = resID
|
|
619
|
+
watch.notify(connClassID, change)
|
|
620
|
+
data = resID
|
|
621
|
+
}
|
|
620
622
|
}
|
|
621
623
|
list(error, data)
|
|
622
624
|
}
|
|
623
|
-
|
|
625
|
+
|
|
626
|
+
// addResource
|
|
627
|
+
//
|
|
628
|
+
// Add a new resource, returning a standard error tuple per writeResource.
|
|
629
|
+
connector.addResource = function addResource(resource) {
|
|
630
|
+
writeResource(resource)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// updateResource
|
|
634
|
+
//
|
|
635
|
+
// Update an existing resource, returning a standard error tuple per writeResource.
|
|
636
|
+
connector.updateResource = function updateResource(resID, resource) {
|
|
637
|
+
writeResource(resource, resID)
|
|
638
|
+
}
|
|
639
|
+
|
|
624
640
|
// getResource
|
|
625
641
|
//
|
|
626
642
|
// Get credentials for a named resource, matching the dictionary semantics of addResource. The
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2019-
|
|
9
|
+
* Copyright (c) 2019-2022 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -75,7 +75,7 @@ closure MakePSMutil(fs, local util) {
|
|
|
75
75
|
} (tree, inpath.split(tree.data.pathsep).reverse())
|
|
76
76
|
if !treedir
|
|
77
77
|
return (callback(Error("treeDirUpdate: can't find directory within tree", inpath)))
|
|
78
|
-
`(error, files) = fs.dirList(dirpath, {
|
|
78
|
+
`(error, files) = fs.dirList(dirpath, { stat: true })
|
|
79
79
|
if error
|
|
80
80
|
return (callback(error))
|
|
81
81
|
//
|
|
@@ -238,46 +238,126 @@ closure MakePSMutil(fs, local util) {
|
|
|
238
238
|
//
|
|
239
239
|
// Perform a deep copy of an array of sources from the current filesystem and specified inpath to
|
|
240
240
|
// the outfs filesystem and specified outpath. The input path is used to find the source files,
|
|
241
|
-
// however it is not added to the output.
|
|
242
|
-
//
|
|
241
|
+
// however it is not added to the output. If the source array is false then the file or directory
|
|
242
|
+
// at the input path is copied to the destination. The result is a standard error tuple, where
|
|
243
|
+
// the errors are an array if any occur.
|
|
244
|
+
//
|
|
245
|
+
// Supported options are:
|
|
246
|
+
// {
|
|
247
|
+
// overwrite: <boolean> -- overwrite existing files
|
|
248
|
+
// erase: <boolean> -- remove extraneous files in destination folders
|
|
249
|
+
// force: <boolean> -- copy files even if older (default is only copy if new/newer)
|
|
250
|
+
// follow: <boolean> -- follow links instead of treating them as files
|
|
251
|
+
// preserve: <boolean> -- preserve file info where possible
|
|
252
|
+
// (e.g. mtime/atime/mode/uid/gid on NodeJS)
|
|
253
|
+
// }
|
|
254
|
+
//
|
|
255
|
+
// Limitations:
|
|
256
|
+
// - this does not copy info about source folders to destination folders
|
|
257
|
+
// (it would need to make destination folder writable, then update after files copied)
|
|
243
258
|
|
|
244
|
-
util.deepcopy = closure deepcopy(inpath, sources, outfs, outpath, options, callback,
|
|
259
|
+
util.deepcopy = closure deepcopy(inpath, sources, outfs, outpath, options, callback,
|
|
260
|
+
local errors, copies, fsopt, folders) {
|
|
245
261
|
if !callback
|
|
246
262
|
return (syncAdapter(deepcopy, inpath, sources, outfs, outpath, options))
|
|
247
|
-
if !array(sources) || !string(inpath) || !string(outpath)
|
|
263
|
+
if sources && !array(sources) || !string(inpath) || !string(outpath)
|
|
248
264
|
return (callback(Error("deepcopy: invalid arguments")))
|
|
265
|
+
if options.follow
|
|
266
|
+
fsopt = { follow: true }
|
|
249
267
|
errors = []
|
|
250
268
|
copies = []
|
|
251
|
-
|
|
252
|
-
|
|
269
|
+
folders = { }
|
|
270
|
+
if sources
|
|
271
|
+
sources = new(sources) // don't modify the original!
|
|
272
|
+
else {
|
|
273
|
+
sources = [fs.path.basename(inpath)] // copy inpath itself
|
|
274
|
+
inpath = fs.path.dirname(inpath)
|
|
275
|
+
}
|
|
276
|
+
//
|
|
277
|
+
// Pass 1 - Get info on all source files, and make destination directories
|
|
278
|
+
//
|
|
279
|
+
asyncArray(sources, 10, closure mklist(entry, index, elements,
|
|
280
|
+
local srcpath, destpath, epath, error, stat, folder, node) {
|
|
253
281
|
srcpath = fs.path.join(inpath, entry)
|
|
254
|
-
|
|
255
|
-
if
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if !
|
|
259
|
-
|
|
260
|
-
`(error, node) = outfs.mkdir(destpath) // may already exist but we don't care
|
|
261
|
-
if error
|
|
262
|
-
return (errors.push(error)) // don't do children if we can't form destination
|
|
263
|
-
for node in folder.children
|
|
264
|
-
sources.push(fs.path.join(entry, node.name)) // omit inpath from sources
|
|
265
|
-
}
|
|
266
|
-
else
|
|
267
|
-
copies.push({ // copy the file
|
|
282
|
+
destpath = outfs.path.join(outpath, entry)
|
|
283
|
+
if destpath == "."
|
|
284
|
+
destpath = "" // joined empty strings
|
|
285
|
+
`(error, stat) = fs.info(srcpath, fsopt)
|
|
286
|
+
if stat.type != "directory" && (!options.follow || stat.type != "symlink") {
|
|
287
|
+
copies.push({ // copy file or symlink
|
|
268
288
|
source: srcpath
|
|
269
289
|
dest: destpath
|
|
270
290
|
})
|
|
291
|
+
return (list(false, { ok: true })) // generic success not used
|
|
292
|
+
}
|
|
293
|
+
`(error, folder) = fs.dirList(srcpath)
|
|
294
|
+
if error
|
|
295
|
+
return (errors.push(error))
|
|
296
|
+
if destpath == ""
|
|
297
|
+
epath = ""
|
|
298
|
+
else
|
|
299
|
+
epath = outfs.path.join(destpath, outfs.path.sep) // canonical name
|
|
300
|
+
folders[epath] = folder // sources for this folder
|
|
301
|
+
`(error, node) = outfs.mkdir(destpath) // may already exist but we don't care
|
|
302
|
+
if error
|
|
303
|
+
return (errors.push(error)) // don't do children if we can't form destination
|
|
304
|
+
for node in folder.children
|
|
305
|
+
sources.push(fs.path.join(entry, node.name)) // add more sources, without inpath
|
|
271
306
|
}).wait()
|
|
307
|
+
//
|
|
308
|
+
// Pass 2 - erase extraneous files and folders from destination
|
|
309
|
+
//
|
|
310
|
+
if options.erase {
|
|
311
|
+
asyncArray(keys(folders).toarray, 10, closure eraser(destpath, index, elements
|
|
312
|
+
local error, outfolder, folder, namesin, removes, delopt) {
|
|
313
|
+
`(error, outfolder) = outfs.dirList(destpath) // existing files in our destination
|
|
314
|
+
if error
|
|
315
|
+
return
|
|
316
|
+
folder = folders[destpath] // our source folders, if any
|
|
317
|
+
namesin = { }
|
|
318
|
+
for node in folder.children // make list of top-level dest names
|
|
319
|
+
namesin[node.name] = true
|
|
320
|
+
removes = []
|
|
321
|
+
for node in outfolder.children
|
|
322
|
+
if !namesin[node.name] {
|
|
323
|
+
if node.info.type == "directory"
|
|
324
|
+
delopt = { recursive: true } // deleting a directory
|
|
325
|
+
else
|
|
326
|
+
delopt = false // deleting a file
|
|
327
|
+
removes.push(list(outfs.path.join(destpath, node.name), delopt))
|
|
328
|
+
}
|
|
329
|
+
asyncArray(removes, 10, closure cleardest(delitem, local error, data) {
|
|
330
|
+
`(error, data) = outfs.delete(delitem.0, delitem.1)
|
|
331
|
+
if error
|
|
332
|
+
errors.push(Error("deepcopy: can't remove", error))
|
|
333
|
+
}).wait()
|
|
334
|
+
}).wait()
|
|
335
|
+
}
|
|
336
|
+
//
|
|
337
|
+
// Pass 3 - copy files and links
|
|
338
|
+
//
|
|
272
339
|
asyncArray(copies, 10, closure cpfiles(entry, index, elements, local error, data, instat, outstat) {
|
|
273
|
-
`(error, instat) = fs.info(entry.source)
|
|
340
|
+
`(error, instat) = fs.info(entry.source, fsopt)
|
|
274
341
|
if !error {
|
|
275
342
|
`(error, outstat) = outfs.info(entry.dest)
|
|
276
|
-
if !error &&
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
343
|
+
if !error &&
|
|
344
|
+
(instat.md5 && instat.md5 == outstat.md5
|
|
345
|
+
|| !options.force && instat.mtimeMs <= outstat.mtimeMs)
|
|
346
|
+
return // file "unchanged"
|
|
347
|
+
if !error && !options.overwrite
|
|
348
|
+
error = Error("deepcopy: can't overwrite", entry.dest)
|
|
349
|
+
else if instat.type == "symlink" && !options.follow {
|
|
350
|
+
`(error, data) = fs.readlink(entry.source)
|
|
351
|
+
if !error
|
|
352
|
+
`(error, data) = outfs.symlink(data, entry.dest)
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
`(error, data) = fs.readFile(entry.source, { encoding: "binary"})
|
|
356
|
+
if !error
|
|
357
|
+
`(error, data) = outfs.writeFile(entry.dest, data, { encoding: "binary"})
|
|
358
|
+
if !error && options.preserve
|
|
359
|
+
`(error) = outfs.setInfo(entry.dest, instat)
|
|
360
|
+
}
|
|
281
361
|
}
|
|
282
362
|
if error
|
|
283
363
|
errors.push(error)
|
|
@@ -287,7 +287,7 @@ closure FSview(table, rootpath, local view, error) {
|
|
|
287
287
|
})))
|
|
288
288
|
if buildpath.slice(-1) != "/"
|
|
289
289
|
buildpath = buildpath.concat("/")
|
|
290
|
-
`(error, data) = view.table.
|
|
290
|
+
`(error, data) = view.table.head(buildpath) // get desired directory
|
|
291
291
|
if error.status == 404
|
|
292
292
|
`(error, data) = dbWriteRecord(buildpath) // create desired directory
|
|
293
293
|
if error
|
|
@@ -684,8 +684,12 @@ closure FSview(table, rootpath, local view, error) {
|
|
|
684
684
|
if !finfo
|
|
685
685
|
finfo = { }
|
|
686
686
|
name = item.key.slice(offset)
|
|
687
|
-
if name.slice(-1) == "/"
|
|
687
|
+
if name.slice(-1) == "/" {
|
|
688
688
|
name = name.slice(0,-1) // remove trailing "/" from directory
|
|
689
|
+
if !finfo.type
|
|
690
|
+
finfo.type = "directory"
|
|
691
|
+
} else if !finfo.type
|
|
692
|
+
finfo.type = "file" // everything is a file in UNIX
|
|
689
693
|
if item.doc._md5
|
|
690
694
|
finfo.md5 = item.doc._md5
|
|
691
695
|
if item.doc._length
|
|
@@ -703,7 +707,7 @@ closure FSview(table, rootpath, local view, error) {
|
|
|
703
707
|
// Read a directory at the specified path
|
|
704
708
|
|
|
705
709
|
view.readir = closure readir(path, callback, local name, dirdict) {
|
|
706
|
-
dirList(path, {
|
|
710
|
+
dirList(path, { stat: true }, callback)
|
|
707
711
|
}
|
|
708
712
|
|
|
709
713
|
// tree
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* column positioning: // // !
|
|
13
13
|
*
|
|
14
|
-
* Copyright (c) 2019-
|
|
14
|
+
* Copyright (c) 2019-2022 by Richard C. Zulch
|
|
15
15
|
*
|
|
16
16
|
*/
|
|
17
17
|
|
|
@@ -38,6 +38,21 @@ closure rsrcVault(db, tablename, local vault) {
|
|
|
38
38
|
vault.table.add(resID, data)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// updateResource
|
|
42
|
+
//
|
|
43
|
+
// Update credentials for accessing a resource, returning the added credential record.
|
|
44
|
+
|
|
45
|
+
vault.updateResource = function updateResource(resID, data, local error, existing) {
|
|
46
|
+
`(error, existing) = vault.table.get(resID)
|
|
47
|
+
if error
|
|
48
|
+
data = false
|
|
49
|
+
else {
|
|
50
|
+
existing.content = data
|
|
51
|
+
`(error, existing) = vault.table.update(existing)
|
|
52
|
+
}
|
|
53
|
+
list(error, data)
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
// deleteResource
|
|
42
57
|
//
|
|
43
58
|
// Delete resource credentials.
|
|
@@ -111,8 +126,9 @@ closure rsrcVault(db, tablename, local vault) {
|
|
|
111
126
|
* conn.uifields() - entry fields for add this kind of resource in UI
|
|
112
127
|
* conn.findResource(resource) - find a resource by content, returning resID or false
|
|
113
128
|
* conn.addResource(resource) - add the resource dict and return `(error, resID)
|
|
114
|
-
* conn.
|
|
129
|
+
* conn.updateResource(resID, resource) - update a resource
|
|
115
130
|
* conn.deleteResource(resID) - delete the specified resource, returning a tuple
|
|
131
|
+
* conn.info(resID) - return `(error, infoDictionary) tuple
|
|
116
132
|
* conn.connect(resID, serviceID, args) - connect to resource and return object
|
|
117
133
|
*
|
|
118
134
|
*/
|
|
@@ -309,6 +325,18 @@ closure ResourceTracker(reman, local retor, watch) {
|
|
|
309
325
|
reman.conns[classID].addResource(resource)
|
|
310
326
|
}
|
|
311
327
|
|
|
328
|
+
// updateResource
|
|
329
|
+
//
|
|
330
|
+
// Update credentials for accessing a resource, returning the added credential record.
|
|
331
|
+
|
|
332
|
+
retor.updateResource = function updateResource(resID, resource, local error, classI) {
|
|
333
|
+
`(error, classID) = resourceClassID(resID)
|
|
334
|
+
if error
|
|
335
|
+
list(error)
|
|
336
|
+
else
|
|
337
|
+
reman.conns[classID].updateResource(resID, resource)
|
|
338
|
+
}
|
|
339
|
+
|
|
312
340
|
// getResource
|
|
313
341
|
//
|
|
314
342
|
// Get a resource for the resID as a standard result tuple.
|