@naanlang/naan 1.0.3 → 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.
@@ -27,10 +27,8 @@
27
27
  * knowledge of which ones are responding. If a request takes longer than a second to respond then
28
28
  * this executes a validate operation, which eliminates any clients that have gone away and rejects
29
29
  * their pending operations.
30
- * One potential problem is stale caches, where a new publisher (e.g. NaanIDE) or new version
31
- * arises but this worker is still serving old data. The solution is that every publisher provides
32
- * its ID and version along with the mesage port. If that is different from the previous version then
33
- * the old caches are cleared and data re-fetched.
30
+ * One potential problem is stale caches. When an older version is discovered then we notify the
31
+ * client and invalidate the old cache.
34
32
  *
35
33
  * column positioning: // // !
36
34
  *
@@ -38,7 +36,7 @@
38
36
  *
39
37
  */
40
38
 
41
- var CurrentCacheName = "Naanlang 1.0.3-1";
39
+ var CurrentCacheName = "Naanlang-1.0.4-2";
42
40
 
43
41
 
44
42
  //
@@ -91,18 +89,13 @@ var waitingForInit = []; // initialization functions, or
91
89
  var pubVersion = event.data.hereIsMyVersion;
92
90
  var sourceId = event.source.id; // client id of sender of message
93
91
  msgPorts[sourceId] = msgport;
94
- var newCacheName = "Naanlang".concat("-", pubID, "-", pubVersion);
95
- console.log("[1.0.3-1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (CurrentCacheName != newCacheName) {
97
- for (var srcid in msgPorts)
98
- msgPorts[srcid].postMessage({
99
- id: "upgrade",
100
- previous: "1.0.3-1",
101
- version: pubVersion
102
- });
103
- }
104
- ClearCaches(newCacheName)
105
- .then(Reaper); // clean up obsolete info
92
+ console.log("[1.0.4-2] received new msgport for", sourceId, pubID, "-", pubVersion);
93
+ if (pubVersion != "1.0.4-2")
94
+ msgport.postMessage({ // notify new version available
95
+ id: "upgrade",
96
+ version: "1.0.4-2"
97
+ });
98
+ Reaper(); // clean up obsolete info
106
99
 
107
100
  // msgport.onmessage events
108
101
  //
@@ -114,7 +107,7 @@ var waitingForInit = []; // initialization functions, or
114
107
  if (msg.id == "response")
115
108
  processResponse(msg);
116
109
  else if (msg.id == "text")
117
- console.log("[1.0.3-1] msg received:", msg.text);
110
+ console.log("[1.0.4-2] msg received:", msg.text);
118
111
  };
119
112
 
120
113
  // send text to IDE log
@@ -125,7 +118,7 @@ var waitingForInit = []; // initialization functions, or
125
118
  // don't clutter the log
126
119
  msgport.postMessage({
127
120
  id: "text",
128
- text: "port received by 1.0.3-1",
121
+ text: "port received by 1.0.4-2",
129
122
  });
130
123
  */
131
124
  if (--pending === 0)
@@ -142,7 +135,7 @@ var waitingForInit = []; // initialization functions, or
142
135
  includeUncontrolled: true
143
136
  }).then(function(clientList) {
144
137
  clientList.every(function(client) {
145
- console.log("[1.0.3-1] requesting new msgport for", client.id);
138
+ console.log("[1.0.4-2] requesting new msgport for", client.id);
146
139
  ++pending;
147
140
  client.postMessage({ // tell client(s) we need this fetch source
148
141
  msg: "Naan_need_fetch_port",
@@ -188,12 +181,12 @@ function Reaper() {
188
181
  clients[clientList[clidex].id] = clientList[clidex];
189
182
  for (var sourceId in msgPorts)
190
183
  if (!clients[sourceId]) {
191
- console.log("[1.0.3-1] source gone:", sourceId);
184
+ console.log("[1.0.4-2] source gone:", sourceId);
192
185
  delete msgPorts[sourceId]; // no longer a source
193
186
  }
194
187
  for (var clientId in fetchPorts)
195
188
  if (!clients[clientId]) {
196
- console.log("[1.0.3-1] client gone:", clientId);
189
+ console.log("[1.0.4-2] client gone:", clientId);
197
190
  delete fetchPorts[clientId]; // no longer a client
198
191
  }
199
192
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -218,25 +211,22 @@ function Reaper() {
218
211
  /*
219
212
  * ClearCaches
220
213
  *
221
- * Clear obsolete caches in favor of the new name, returning a promise.
214
+ * Clear obsolete caches, returning a promise.
222
215
  *
223
216
  */
224
217
 
225
- function ClearCaches(newCacheName) {
226
- if (CurrentCacheName == newCacheName)
227
- return (Promise.resolve()); // no change
228
- CurrentCacheName = newCacheName;
218
+ function ClearCaches() {
229
219
  var promise = caches.keys().then(function(cacheNames) { // delete old cache entries
230
220
  return (Promise.all(
231
221
  cacheNames.map(function(cacheName) {
232
- if (cacheName !== CurrentCacheName) {
233
- console.log('[1.0.3-1] deleting old cache:', cacheName);
222
+ if (cacheName != CurrentCacheName) {
223
+ console.log('[1.0.4-2] deleting old cache:', cacheName);
234
224
  return (caches.delete(cacheName));
235
225
  }
236
226
  })
237
227
  ));
238
228
  }).then(function() { // claim all clients
239
- console.log('[1.0.3-1] claiming clients for version', CurrentCacheName);
229
+ console.log('[1.0.4-2] claiming clients');
240
230
  return (self.clients.claim());
241
231
  });
242
232
  return (promise);
@@ -279,7 +269,7 @@ function GetClientResponse(event, urlpath) {
279
269
  msgport.postMessage({
280
270
  id: "fetch",
281
271
  seq: seqno,
282
- version: "1.0.3-1",
272
+ version: "1.0.4-2",
283
273
  request: {
284
274
  method: event.request.method,
285
275
  url: event.request.url
@@ -327,7 +317,7 @@ function GetClientResponse(event, urlpath) {
327
317
  */
328
318
 
329
319
  self.addEventListener('install', function(event) {
330
- console.log("[1.0.3-1] install");
320
+ console.log("[1.0.4-2] install");
331
321
  self.skipWaiting();
332
322
  });
333
323
 
@@ -366,7 +356,7 @@ self.addEventListener('fetch', function(event) {
366
356
  }
367
357
  else
368
358
  promise = fetch(event.request).catch(function (e) {
369
- console.log("[1.0.3-1] fetch failed", e);
359
+ console.log("[1.0.4-2] fetch failed", e);
370
360
  return (new Response(undefined, {
371
361
  status: 404,
372
362
  statusText: "Fetch Failed"
@@ -395,16 +385,16 @@ self.addEventListener('fetch', function(event) {
395
385
  */
396
386
 
397
387
  self.addEventListener('activate', function(event) {
398
- console.log("[1.0.3-1] activate");
388
+ console.log("[1.0.4-2] activate");
399
389
  self.clients.matchAll({ // for debugging, list controlled clients
400
390
  includeUncontrolled: true
401
391
  }).then(function(clientList) {
402
392
  var urls = clientList.map(function(client) {
403
393
  return (client.url);
404
394
  });
405
- console.log('[1.0.3-1] matching clients:', urls.join(', '));
395
+ console.log('[1.0.4-2] matching clients:', urls.join(', '));
406
396
  });
407
- var promise = ClearCaches(CurrentCacheName);
397
+ var promise = ClearCaches();
408
398
  if (event.waitUntil)
409
399
  event.waitUntil(promise);
410
400
  });
@@ -77,7 +77,7 @@ closure TermHost(track, api, name, workerID, options,
77
77
  if !target.written
78
78
  target.term.WriteLn("(reconnected)")
79
79
  }, 1000).run()
80
- else
80
+ else if message.respOp
81
81
  debuglog("unknown host response message:", message.respOp)
82
82
  }
83
83
 
@@ -55,8 +55,13 @@ closure ServiceWorker(pubID, pubVersion, local serv, callback) {
55
55
  version: msg.version
56
56
  })
57
57
  } else if msg.id == "upgrade" {
58
- if msg.version != msg.previous // same version: new install
59
- debuglog("service worker version upgrade to:", msg.version, "from", msg.previous)
58
+ if serv.upgrade != msg.version {
59
+ serv.upgrade = msg.version // only report once
60
+ callback(false, {
61
+ message: "upgrade"
62
+ version: msg.version
63
+ })
64
+ }
60
65
  }
61
66
  else if msg.id == "text"
62
67
  debuglog("sw[".concat(serv.scope, "]: "), msg.text) // handy remote debug capability
@@ -182,7 +187,7 @@ closure ServiceWorker(pubID, pubVersion, local serv, callback) {
182
187
  *
183
188
  */
184
189
 
185
- closure ScopedServiceWorker(pubID, pubVersion, local scoper, result) {
190
+ closure ScopedServiceWorker(pubID, pubVersion, callback, local scoper, result) {
186
191
  scoper = new(object, ScopedServiceWorker)
187
192
  scoper.scopes = {}
188
193
  scoper.serv = ServiceWorker(pubID, pubVersion)
@@ -232,7 +237,8 @@ closure ScopedServiceWorker(pubID, pubVersion, local scoper, result) {
232
237
  response.body = data
233
238
  scoper.serv.msgport.postMessage(response)
234
239
  return
235
- }
240
+ } else if data.message == "upgrade"
241
+ callback(data.version)
236
242
  })
237
243
  if result.0 {
238
244
  ErrorDebuglog("ScopedServiceWorker failed", result)
@@ -365,8 +371,8 @@ closure TrackedScope(track, scope, fs, rootpath, local trope, result) {
365
371
  *
366
372
  */
367
373
 
368
- closure TrackServiceWorkers(track, pubID, pubVersion, local error) {
369
- `(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion)
374
+ closure TrackServiceWorkers(track, pubID, pubVersion, callback, local error) {
375
+ `(error, serviceWorker) = ScopedServiceWorker(pubID, pubVersion, callback)
370
376
  if error
371
377
  ErrorDebugLog("TrackServiceWorkers: cannot load service worker", error)
372
378
  track.register(TrackedScope, "V-Site")
@@ -22,6 +22,44 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
22
22
  view = new(object, this)
23
23
  view.api = api
24
24
  view.rootpath = rootpath
25
+
26
+ // data
27
+ //
28
+ // Return the filesystem data, i.e. the tree data field. The minimum information is:
29
+ // platform -- e.g. "darwin" or "win32"
30
+ // semantics -- e.g. "win32" or "posix"
31
+ // pathsep -- e.g. / or \
32
+
33
+ view.data = closure data(callback) {
34
+ if !callback
35
+ return (syncAdapter(data))
36
+ params = {
37
+ path: ""
38
+ op: "tree"
39
+ depthlimit: 0
40
+ }
41
+ api.psmRemote(params, false, function(error, tree) {
42
+ view.fsdata = tree.data
43
+ callback(error, tree.data)
44
+ })
45
+ }
46
+
47
+ // pathInit
48
+ //
49
+ // Initialize our path module for the applicable path type.
50
+
51
+ closure pathInit(local result) {
52
+ result = view.data()
53
+ if result.0
54
+ pathmod = false
55
+ else {
56
+ if view.fsdata.pathsep == "\\"
57
+ pathmod = JSpath.win32
58
+ else
59
+ pathmod = JSpath.posix
60
+ false
61
+ }
62
+ }
25
63
 
26
64
  // psmRemote
27
65
  //
@@ -30,10 +68,14 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
30
68
  closure psmRemote(path, op, options, putdata, callback, local params, item) {
31
69
  if !callback
32
70
  return (syncAdapter(psmRemote, path, op, options, putdata))
71
+ if !pathmod && pathInit()
72
+ return // failed to initialize path module
33
73
  if !string(path)
34
74
  return (asyncResult(callback, Error("invalid path:", typeof(path))))
75
+ if rootpath != ""
76
+ path = pathmod.resolve(rootpath, path)
35
77
  params = {
36
- path: JSpath.resolve(rootpath, path)
78
+ path: path
37
79
  op: op
38
80
  }
39
81
  for item in options
@@ -59,38 +101,30 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
59
101
 
60
102
  // path
61
103
  //
62
- // The path sub-object remotes calls to the NodeJS path module.
104
+ // Redirect path calls to module chosen for filesystem semantics. It is possible for this to fail
105
+ // on first connection to the filesystem, in which case we return false for the path result.
106
+ // Throwing an error would complicate our callers and testing. This way there's a small chance
107
+ // that a transient error will give a bad path, but that will be detected downstream.
63
108
 
64
109
  view.path = new(object, this)
65
- view.path[".undefined"] = closure path args {
66
- closure (selector, arglist, local error, info) {
67
- if !pathmod {
68
- `(error, data) = view.data()
69
- if error
70
- return (list(error))
71
- if info.pathsep == "\\"
72
- pathmod = JSpath.win32
73
- else
74
- pathmod = JSpath.posix
75
- }
76
- xapply(pathmod, selector, arglist)
77
- } (args.0, args.-1)
110
+ view.path[".undefined"] = function path args {
111
+ if !pathmod
112
+ pathInit()
113
+ xapply(pathmod, args.0, args.-1)
78
114
  }
79
115
 
80
- // data
116
+ // folderPath
81
117
  //
82
- // Return the filesystem data, i.e. the tree data field. The minimum information is:
83
- // platform -- e.g. "darwin" or "win32"
84
- // semantics -- e.g. "win32" or "posix"
85
- // pathsep -- e.g. / or \
86
-
87
- view.data = closure data(callback) {
88
- if !callback
89
- return (syncAdapter(data))
90
- psmRemote("", "tree", { depthlimit: 0 }, false, function(error, tree) {
91
- view.fsdata = tree.data
92
- callback(error, tree.data)
93
- })
118
+ // Return the path of the specified special folder:
119
+ // "HomeDir"
120
+ // "TempDir"
121
+ // "PackageDir"
122
+ // The returned path is relative to our root if within it, or absolute otherwise.
123
+
124
+ view.folderPath = function folderPath(folderID, callback) {
125
+ psmRemote("", "folderPath", {
126
+ folderid: folderID
127
+ }, false, callback)
94
128
  }
95
129
 
96
130
  // readLines
@@ -66,7 +66,7 @@ function JsonParse(data) {
66
66
  list(false, data)
67
67
  } catch {
68
68
  if true
69
- list(exception)
69
+ list(exception)
70
70
  }
71
71
  };
72
72
 
@@ -84,7 +84,7 @@ function JsonStringify(data) {
84
84
  list(false, data)
85
85
  } catch {
86
86
  if true
87
- list(exception)
87
+ list(exception)
88
88
  }
89
89
  };
90
90
 
@@ -110,8 +110,12 @@ closure MakeAPI(port, options, callback, local chroot, server) {
110
110
 
111
111
  server.app.use(function(req,res,next) {
112
112
  res.header("Access-Control-Allow-Origin", "*")
113
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
114
- next()
113
+ res.header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
114
+ res.header("Access-Control-Allow-Headers", "x-naanlang-api-guid")
115
+ if req.method == "OPTIONS"
116
+ res.sendStatus(200)
117
+ else
118
+ next()
115
119
  })
116
120
 
117
121
  server.app.use("", express.static(chroot))
@@ -197,7 +201,7 @@ closure MakeAPI(port, options, callback, local chroot, server) {
197
201
 
198
202
  server.app.all("/psm", closure(req, res, local guid) {
199
203
  guid = req.get("x-naanlang-api-guid")
200
- if server.guid && guid != server.guid
204
+ if server.guid && guid != server.guid && req.method !== "OPTIONS"
201
205
  res.status(403).send("unauthorized")
202
206
  else if !server.hostfs
203
207
  res.status(503).send("host filesystem not available")
@@ -23,7 +23,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
23
23
  if !rootpath
24
24
  return (list(Error("mandatory rootpath missing")))
25
25
  else if rootpath == ""
26
- rootpath = js.g.process.cwd() // NodeJS uses CWD for empty rootpath
26
+ rootpath = JSpath.sep
27
27
  files = new(object, this)
28
28
  files.rootpath = rootpath
29
29
  files.path = JSpath
@@ -54,24 +54,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
54
54
  // Convert a path argument to a local path, returning a standard result tuple. If the path is
55
55
  // invalid this will report why. Otherwise this returns the correct localpath, which is the path
56
56
  // we use with the underlying API.
57
+ // Win32 driver letters are annoying here. If the rootpath specifies a drive letter then the
58
+ // paths must match it. If the rootpath doesn't specify a drive letter then path can have any
59
+ // drive letter, or none.
57
60
 
58
61
  badCreatePathRegEx = RegExp("[:]|[/\\\\][/\\\\]|[/\\\\]$|(^|[/\\\\])[.][.]?$")
59
62
 
60
- closure localPath(path, create, local pattern, testpath) {
63
+ files.localPath = closure localPath(path, create, local pattern, testpath, locroot) {
61
64
  if !path
62
65
  return (list(Error("filesystem path false")))
63
66
  else if !string(path)
64
67
  return (list(Error("filesystem path invalid:", typeof(path))))
68
+ if JSpath === JSpath.win32 {
69
+ locroot = JSpath.resolve(path, rootpath) // use path's drive letter if we don't have one
70
+ pattern = "\\:\\" // unchanged and unique on win32
71
+ }
72
+ else {
73
+ locroot = rootpath
74
+ pattern = "/:/" // unchanged and unique on posix
75
+ }
65
76
  if JSpath.isAbsolute(path)
66
- path = JSpath.resolve(path) // puts C: in front of \abs\win\path
77
+ path = JSpath.resolve(locroot, path) // add drive letter to absolute path
67
78
  else
68
79
  path = JSpath.normalize(path) // normalize for current platform
69
- if JSpath === JSpath.win32
70
- pattern = "\\:\\" // unchanged and unique on win32
71
- else
72
- pattern = "/:/" // unchanged and unique on posix
73
- if ((testpath = commonParents(path, rootpath)) != "")
74
- list(false, JSpath.resolve(rootpath, path)) // ### shouldn't be so lax on this test
80
+ if (commonParents(path, locroot) != "")
81
+ list(false, JSpath.resolve(locroot, path))
75
82
  else if ((testpath = JSpath.resolve(pattern, path)).indexOf(pattern.slice(0,-1)) < 0)
76
83
  list(Error("filesystem path reaches above root:", path))
77
84
  else if create && testpath.indexOf(pattern) < 0
@@ -79,7 +86,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
79
86
  else if create && badCreatePathRegEx.test(path)
80
87
  list(Error("filesystem create path invalid", path))
81
88
  else
82
- list(false, JSpath.resolve(rootpath, path))
89
+ list(false, JSpath.resolve(locroot, path))
90
+ }
91
+
92
+ // folderPath
93
+ //
94
+ // Return a folder path for the specified folderID, relative to our root. If the folder path is
95
+ // not within our root then it is returned as an absolute path.
96
+
97
+ files.folderPath = function folderPath(folderID, callback, local envID, path) {
98
+ if !callback
99
+ return (syncAdapter(folderPath, folderID))
100
+ envID = {
101
+ "HomeDir": "HOME",
102
+ "TempDir": "TMPDIR",
103
+ "PackageDir": "npm_config_prefix"
104
+ }[folderID]
105
+ if envID {
106
+ path = js.g.process.env[envID]
107
+ if path.startsWith(rootpath)
108
+ path = path.slice(rootpath.length) // remove rootpath from beginning
109
+ }
110
+ if path
111
+ callback(false, path)
112
+ else
113
+ callback(Error("unknown folderID:", folderID))
83
114
  }
84
115
 
85
116
  // winDrives
@@ -639,8 +670,10 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
639
670
  // Recursively build the tree
640
671
 
641
672
  closure recurTree(localpath, depth, parent) {
642
- if depthlimit && depth >= depthlimit
673
+ if depthlimit && depth >= depthlimit {
674
+ recurNext()
643
675
  return
676
+ }
644
677
  ++pending.active
645
678
  fs.readdir(localpath, { }, function(err, nodes, local child, node, stat) {
646
679
  if err.code == "ENOTDIR"
@@ -670,13 +703,21 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
670
703
  parent: child
671
704
  }) } }
672
705
  --pending.active
673
- while queue.length > 0 && pending.active < 20 {
674
- node = queue.pop()
675
- recurTree(node.path, node.depth, node.parent) }
676
- if pending.active == 0 && queue.length == 0
677
- pending.signal(list(false, root))
706
+ recurNext()
678
707
  })
679
708
  }
709
+
710
+ // recurNext
711
+ //
712
+ // Pull more queued items and signal when done.
713
+
714
+ function recurNext(local node) {
715
+ while queue.length > 0 && pending.active < 20 {
716
+ node = queue.pop()
717
+ recurTree(node.path, node.depth, node.parent) }
718
+ if pending.active == 0 && queue.length == 0
719
+ pending.signal(list(false, root))
720
+ }
680
721
 
681
722
  `(error, localpath) = localPath(path)
682
723
  if error
@@ -23,6 +23,28 @@ closure Gitter(fs, local gitt) {
23
23
  gitt = new(object, this)
24
24
  gitt.roots = {}
25
25
 
26
+ // gitPath
27
+ //
28
+ // Convert a Windows path to a posix (git) path if needed.
29
+ //
30
+ function gitPath(fspath) {
31
+ if fs.path.sep == "/"
32
+ fspath
33
+ else
34
+ fspath.split(fs.path.sep).join("/")
35
+ }
36
+
37
+ // fsPath
38
+ //
39
+ // Convert a posix (git) path to a filesystem path if needed.
40
+ //
41
+ function fsPath(gitpath) {
42
+ if fs.path.sep == "/"
43
+ gitpath
44
+ else
45
+ gitpath.split("/").join(fs.path.sep)
46
+ }
47
+
26
48
  // repoRoot
27
49
  //
28
50
  // Compute the path from the fs root to the repo root.
@@ -54,11 +76,10 @@ closure Gitter(fs, local gitt) {
54
76
  // Compute the bridge path to an absolute repopath (which can point inside a repo) from the
55
77
  // phyiscal repo root. If these are the same, or an error occurs, then the result is "".
56
78
  //
57
-
58
79
  function bridgePath(repopath, local top) {
59
80
  top = repoRoot(repopath).1
60
81
  if top && repopath.startsWith(top) && repopath.length > top.length
61
- top = repopath.slice(top.length+1).concat("/") // move leading "/" of bridge path to trailing
82
+ top = repopath.slice(top.length+1).concat(fs.path.sep) // move leading "/" of bridge path to trailing
62
83
  else
63
84
  "" // we are looking at the root, or an error
64
85
  }
@@ -151,12 +172,12 @@ closure Gitter(fs, local gitt) {
151
172
  outLE: "\x00" // lines delimited by nulls
152
173
  }
153
174
  if options.pathspec
154
- execOptions.cmdargs = execOptions.cmdargs.concat("--", options.pathspec)
175
+ execOptions.cmdargs = execOptions.cmdargs.concat("--", gitPath(options.pathspec))
155
176
  result = []
156
177
  execOptions.outproc = function(output, liner, kill, local line, path) {
157
178
  while (line = liner.read())
158
179
  if line != "" {
159
- path = line.substring(3)
180
+ path = fsPath(line.substring(3))
160
181
  if path.startsWith(bridge) { // must start with bridge to be within repopath
161
182
  path = path.slice(bridge.length)
162
183
  result.push({
@@ -183,7 +204,7 @@ closure Gitter(fs, local gitt) {
183
204
  // Retrieve the content of a specific file and revision. The file pathspec must be the path of
184
205
  // the file within the repo.
185
206
 
186
- gitt.showfile = closure showfile(repopath, options, callback, local execOptions, result) {
207
+ gitt.showfile = closure showfile(repopath, options, callback, local execOptions) {
187
208
  if !callback
188
209
  return (syncAdapter(showfile, repopath, options))
189
210
  if !string(repopath)
@@ -192,7 +213,7 @@ closure Gitter(fs, local gitt) {
192
213
  return (asyncResult(callback, Error("both pathspec and hash required:", options.pathspec, options.hash)))
193
214
  repopath = fs.path.resolve(fs.rootpath, repopath)
194
215
  execOptions = {
195
- cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), options.pathspec)]
216
+ cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), gitPath(options.pathspec))]
196
217
  execops: {
197
218
  cwd: repopath
198
219
  }
@@ -27,6 +27,7 @@ closure psmsFsView(rootpath, local view, gitter) {
27
27
  view = view.1
28
28
 
29
29
  // inherited
30
+ // -- view.folderPath(folderid, callback)
30
31
  // -- view.winDrives(callback)
31
32
  // -- view.testHidden(path, callback)
32
33
  // -- view.readLines(path, options, callback)
@@ -86,16 +87,27 @@ closure psmsFsView(rootpath, local view, gitter) {
86
87
  }
87
88
  res.send(new(array, result))
88
89
  }
89
-
90
+
91
+ // op: folderPath
92
+ function folderPathApi() {
93
+ res.send(new(array, view.folderPath(req.query.folderid)))
94
+ }
95
+
90
96
  // op: readLines
91
97
  function readLinesApi() {
92
98
  res.send(new(array, view.readLines(path, req.query)))
93
99
  }
94
100
 
95
101
  // op: readFile
96
- function readFileApi(local options) {
102
+ function readFileApi(local error, localpath, options) {
103
+ `(error, localpath) = view.localPath(path)
104
+ if error {
105
+ res.send(new(array, list(error)))
106
+ return
107
+ }
97
108
  options = {
98
- dotfiles: "allow" }
109
+ dotfiles: "allow"
110
+ }
99
111
  if req.query.encoding == "binary"
100
112
  options.headers = {
101
113
  "Content-Type": "application/octet-stream"
@@ -104,7 +116,7 @@ closure psmsFsView(rootpath, local view, gitter) {
104
116
  options.headers = { // ### is this always the right default?
105
117
  "Content-Type": "text/html; charset=UTF-8"
106
118
  }
107
- res.sendFile(path, options, function (err, local status) {
119
+ res.sendFile(localpath, options, function (err, local status) {
108
120
  if err {
109
121
  if err.code == "ENOENT"
110
122
  status = 404
@@ -187,7 +199,7 @@ closure psmsFsView(rootpath, local view, gitter) {
187
199
 
188
200
  // op: tree
189
201
  function treeApi() {
190
- res.send(new(array, view.tree(path, req.query.depthlimit)))
202
+ res.send(new(array, view.tree(path, toint(req.query.depthlimit))))
191
203
  }
192
204
 
193
205
  // op: dirSearch
@@ -250,6 +262,7 @@ closure psmsFsView(rootpath, local view, gitter) {
250
262
 
251
263
  proc = {
252
264
  pathOp: pathOpApi
265
+ folderPath: folderPathApi
253
266
  readFile: readFileApi
254
267
  readLines: readLinesApi
255
268
  writeFile: writeFileApi
@@ -293,7 +293,7 @@ closure projConnector(projman, projtype, local connector, watch) {
293
293
  *
294
294
  * Nide.proj/
295
295
  * nide_cliser.cfg - JSON definition of the project (see below)
296
- * NaanIDE_version.txt - [default] substitution file defining 1.0.3-1 etc.
296
+ * NaanIDE_version.txt - [default] substitution file defining 1.0.4-1 etc.
297
297
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
298
298
  * build/ - [optional] default build output location
299
299
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -457,7 +457,9 @@ closure projConfig(projman, where, local procon, fs, configpath) {
457
457
  error = Error("project configuration not a directory", where.path)
458
458
  else
459
459
  `(error, data) = readConfig() }
460
- if error
460
+ if error.code == "ENOENT"
461
+ return (list(Error("Project <b>".concat(where.name, "</b>:<br>not found at ", where.path))))
462
+ else if error
461
463
  return (list(Error("Project.load failed", error)))
462
464
  procon.loaded = true
463
465
  list(false, procon.configDict)