cordova-plugin-crosswire-sword 0.2.8 → 0.2.11

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 (40) hide show
  1. package/RELEASENOTES.md +18 -0
  2. package/libs/android/arm64-v8a/libsword.so +0 -0
  3. package/libs/android/armeabi-v7a/libsword.so +0 -0
  4. package/libs/android/x86/libsword.so +0 -0
  5. package/libs/android/x86_64/libsword.so +0 -0
  6. package/libs/ios/SWORD.xcframework/ios-arm64/libSWORD.a +2 -2
  7. package/libs/ios/SWORD.xcframework/ios-arm64-simulator/libSWORD.a +2 -2
  8. package/package.json +1 -1
  9. package/plugin.xml +5 -3
  10. package/src/android/SWORD.java +79 -9
  11. package/src/android/SafStorage.java +66 -2
  12. package/src/ios/SWORD.swift +571 -69
  13. package/www/SWORD.js +31 -3
  14. package/libs/ios/curl.xcframework/Info.plist +0 -47
  15. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/curl.h +0 -3373
  16. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/curlver.h +0 -77
  17. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/easy.h +0 -123
  18. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/header.h +0 -74
  19. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/mprintf.h +0 -85
  20. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/multi.h +0 -560
  21. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/options.h +0 -70
  22. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/stdcheaders.h +0 -35
  23. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/system.h +0 -399
  24. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/typecheck-gcc.h +0 -964
  25. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/urlapi.h +0 -156
  26. package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/websockets.h +0 -98
  27. package/libs/ios/curl.xcframework/ios-arm64/libcurl-all.a +0 -0
  28. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/curl.h +0 -3373
  29. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/curlver.h +0 -77
  30. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/easy.h +0 -123
  31. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/header.h +0 -74
  32. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/mprintf.h +0 -85
  33. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/multi.h +0 -560
  34. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/options.h +0 -70
  35. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/stdcheaders.h +0 -35
  36. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/system.h +0 -399
  37. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/typecheck-gcc.h +0 -964
  38. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/urlapi.h +0 -156
  39. package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/websockets.h +0 -98
  40. package/libs/ios/curl.xcframework/ios-arm64-simulator/libcurl-all.a +0 -0
package/RELEASENOTES.md CHANGED
@@ -20,6 +20,24 @@
20
20
  -->
21
21
  # Release Notes
22
22
 
23
+ ### 0.2.11 (Oct 1, 2026)
24
+ * Study data (bookmarks and, in time, notes) in a folder of the user's choosing, named sword-studydata so several devices and SWORD apps can share it (doc/bookmarks-format.md in Bishop): a picked folder holding bookmarks/, or itself named sword-studydata, is used as it is, otherwise sword-studydata/ is made inside it
25
+ * iOS: the study-data folder may be in iCloud Drive or any app's folder; where a provider grants files but no folder (Google Drive), the bookmark files are picked in place (SWMgr_chooseDataFiles) or Bishop's own are saved there and opened once (SWMgr_exportDataFiles); a provider's file is rewritten in place so it keeps its identity
26
+ * Android: a cloud provider's stale record of a replaced file is refreshed before the retry by name
27
+ * initSWORD answers `features` ({ dataStorage, dataFiles, sharedStorage, bibleSync }) so the page can offer what the platform has; SWORD.js keeps it as SWORD.features
28
+ * iOS: HTTPUtils_makeRequest takes the method codes SWORD.js defines (METHOD_GET is 0) and no longer crashes on a bad URL; a user agent naming the app, device and engine; sharing a verse through the system share sheet; passive FTP and verified peers set in InstallMgr.conf
29
+
30
+ ### 0.2.10 (Sep 26, 2026)
31
+ * iOS: HTTP(S) through Apple's NSURLSession; no curl or OpenSSL in the app, so App Store Connect's export-compliance questions do not arise (only Apple's encryption)
32
+ * Android: a cloud folder (Google Drive) whose provider is still loading is waited for instead of being taken as empty, a listing that fails is reported to the page, and a file another device replaced is found again by name; the app no longer creates a second personal.conf beside the real one
33
+ * FTP without curl (iOS): EPSV first and PASV's advertised address ignored, as curl does, so sources such as STEP answer; failures are reported as with curl, so file-by-file installs proceed on sources without packages
34
+
35
+ ### 0.2.9 (Sep 25, 2026)
36
+ * BibleSync works: biblesync 2.2.0 with the Android and Apple interface discovery sent upstream; Android holds a Wi-Fi multicast lock while it runs (CHANGE_WIFI_MULTICAST_STATE); every speaker in the room is heard, not only the first
37
+ * Android: a sword:// or bible:// link the app is opened with reaches the page as handleOpenURL, exactly as cordova-ios does, on a cold start and a warm one
38
+ * iOS: a received BibleSync verse no longer crashes the app
39
+ * The native binaries come from the sword-android-latest and sword-ios-latest links on crosswire.org
40
+
23
41
  ### 0.2.8 (Aug 22, 2026)
24
42
  * iOS: builds with cordova-ios 8 and Xcode 27 (xcframeworks for libsword and curl 8.22 with OpenSSL 3.5.8, verified against the iOS trust store)
25
43
  * The native binaries of both platforms are no longer in git: `make fetch` in bindings/Android and `make ios` in bindings/objc/dependencies download the published sets from crosswire.org
Binary file
Binary file
Binary file
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/libs/ios/SWORD.xcframework/ios-arm64/libSWORD.a
3
- size: 37277640 bytes
4
- sha256: d4e974f37e98c9b7f39b1b8d253066bf01b3379f40dc13d29ef85930f17e01b4
3
+ size: 37229112 bytes
4
+ sha256: 3e031f0b4dbd6b3c5096cadf56adca793363d9d4bb40ad0810e79e2d2d4e7260
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/libs/ios/SWORD.xcframework/ios-arm64-simulator/libSWORD.a
3
- size: 37320688 bytes
4
- sha256: bd561095edca8a5277528bcd3140cc14b474d85498dd891d6656dd93e180db57
3
+ size: 37272016 bytes
4
+ sha256: d4a361c089cb1384d15fce6755470d9d3bd012d8acf7af69ba27315ea4f71ae8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-plugin-crosswire-sword",
3
- "version": "0.2.8",
3
+ "version": "0.2.11",
4
4
  "description": "The SWORD Project Plugin",
5
5
  "files": [
6
6
  "plugin.xml",
package/plugin.xml CHANGED
@@ -4,7 +4,7 @@
4
4
  xmlns:rim="http://www.blackberry.com/ns/widgets"
5
5
  xmlns:android="http://schemas.android.com/apk/res/android"
6
6
  id="cordova-plugin-crosswire-sword"
7
- version="0.2.8">
7
+ version="0.2.11">
8
8
  <name>cordova-plugin-crosswire-sword</name>
9
9
  <description>The SWORD Project Plugin</description>
10
10
  <license>GNU 2.0</license>
@@ -43,6 +43,10 @@
43
43
  <param name="android-package" value="org.crosswire.sword.cordova.SWORD"/>
44
44
  </feature>
45
45
  </config-file>
46
+ <!-- BibleSync: Wi-Fi drivers drop multicast unless the app holds a MulticastLock -->
47
+ <config-file target="AndroidManifest.xml" parent="/*">
48
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
49
+ </config-file>
46
50
  <source-file src="src/android/SWORD.java" target-dir="src/org/crosswire/sword/cordova" />
47
51
  <source-file src="src/android/SWMgr.java" target-dir="src/org/crosswire/android/sword" />
48
52
  <source-file src="src/android/AndroidMgr.java" target-dir="src/org/crosswire/android/sword" />
@@ -133,13 +137,11 @@
133
137
  <header-file src="src/ios/SWORD-Bridging-Header.h" type="BridgingHeader" />
134
138
  <source-file src="src/ios/SWORD.swift" />
135
139
  <framework src="libs/ios/SWORD.xcframework" custom="true" embed="false" />
136
- <framework src="libs/ios/curl.xcframework" custom="true" embed="false" />
137
140
  <framework src="libc++.tbd" />
138
141
  <framework src="libz.tbd" />
139
142
  <framework src="libicucore.tbd" />
140
143
  <framework src="Security.framework" />
141
144
  <framework src="CoreFoundation.framework" />
142
- <framework src="SystemConfiguration.framework" />
143
145
 
144
146
  <!-- <source-file src="lib/ios/libSWORD.a" framework="true" /> -->
145
147
  </platform>
@@ -40,6 +40,7 @@ import org.json.JSONException;
40
40
  import org.json.JSONObject;
41
41
 
42
42
  import org.crosswire.android.sword.SWMgr;
43
+ import org.crosswire.android.sword.SafStorage;
43
44
  import org.crosswire.android.sword.AndroidMgr;
44
45
  import org.crosswire.android.sword.SWModule;
45
46
  import org.crosswire.android.sword.InstallMgr;
@@ -47,9 +48,15 @@ import org.crosswire.android.sword.InstallMgr;
47
48
  import android.provider.Settings;
48
49
  import android.util.Log;
49
50
  import android.content.Intent;
51
+ import android.content.Context;
52
+ import android.net.wifi.WifiManager;
50
53
  import android.app.Activity;
51
54
 
52
55
  public class SWORD extends CordovaPlugin {
56
+ // the app-neutral study-data folder (see SWMgr_getDataPath)
57
+ public static final String STUDY_DATA_FOLDER = "sword-studydata";
58
+ // held while BibleSync runs: without it most Wi-Fi drivers filter out the multicast group
59
+ private WifiManager.MulticastLock multicastLock = null;
53
60
  public static final String TAG = "SWORD";
54
61
  public static SWMgr mgr = null;
55
62
  public static InstallMgr installMgr = null;
@@ -74,9 +81,28 @@ public class SWORD extends CordovaPlugin {
74
81
  * @param webView The CordovaWebView Cordova is running in.
75
82
  */
76
83
  // a sword:// or bible:// link the app was opened with, held until the
77
- // page asks for it (SWMgr_getLaunchUrl)
84
+ // page has loaded, then handed to it exactly as cordova-ios's
85
+ // HandleOpenUrl does: handleOpenURL(url) once deviceready has fired
78
86
  private static String launchUrl = null;
79
87
 
88
+ private void pushOpenUrl(final String url) {
89
+ final String js = "document.addEventListener('deviceready',function(){if (typeof handleOpenURL === 'function') { handleOpenURL(" + org.json.JSONObject.quote(url) + ");}});";
90
+ cordova.getActivity().runOnUiThread(new Runnable() { public void run() {
91
+ webView.getEngine().evaluateJavascript(js, null);
92
+ }});
93
+ }
94
+
95
+ // cold start: the page is loaded, the link can go in
96
+ @Override
97
+ public Object onMessage(String id, Object data) {
98
+ if ("onPageFinished".equals(id) && launchUrl != null) {
99
+ String url = launchUrl;
100
+ launchUrl = null;
101
+ pushOpenUrl(url);
102
+ }
103
+ return null;
104
+ }
105
+
80
106
  private static String deepLink(Intent intent) {
81
107
  if (intent == null || intent.getData() == null) return null;
82
108
  String scheme = intent.getData().getScheme();
@@ -84,14 +110,12 @@ public class SWORD extends CordovaPlugin {
84
110
  return intent.getDataString();
85
111
  }
86
112
 
113
+ // warm start: the page is up, the link goes straight in
87
114
  @Override
88
115
  public void onNewIntent(Intent intent) {
89
116
  final String url = deepLink(intent);
90
117
  if (url == null) return;
91
- launchUrl = url;
92
- cordova.getActivity().runOnUiThread(new Runnable() { public void run() {
93
- webView.getEngine().evaluateJavascript("if (window.swordDeepLink) swordDeepLink(" + org.json.JSONObject.quote(url) + ");", null);
94
- }});
118
+ pushOpenUrl(url);
95
119
  }
96
120
 
97
121
  // who is asking, for the servers we talk to: "Bishop/1.7.905 (OnePlus
@@ -207,6 +231,15 @@ public class SWORD extends CordovaPlugin {
207
231
  if (action.equals("initSWORD")) {
208
232
  JSONObject r = new JSONObject();
209
233
  r.put("version", mgr.version());
234
+ // what the page may offer on Android (SWORD.features): a shared
235
+ // module library and a study-data folder, both picked as SAF trees;
236
+ // no picking of single files (a tree grant covers any provider)
237
+ JSONObject features = new JSONObject();
238
+ features.put("sharedStorage", true);
239
+ features.put("dataStorage", true);
240
+ features.put("dataFiles", false);
241
+ features.put("bibleSync", true);
242
+ r.put("features", features);
210
243
  callbackContext.success(r);
211
244
  }
212
245
  else if (action.equals("echo")) {
@@ -520,6 +553,12 @@ Log.d(TAG, "SWModule_getRenderChapter");
520
553
  final String userName = args.getString(1);
521
554
  final String passphrase = args.getString(2);
522
555
 
556
+ if (multicastLock == null) {
557
+ WifiManager wifi = (WifiManager) cordova.getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
558
+ if (wifi != null) { multicastLock = wifi.createMulticastLock("BibleSync"); multicastLock.setReferenceCounted(false); }
559
+ }
560
+ if (multicastLock != null) multicastLock.acquire();
561
+
523
562
  cordova.getThreadPool().execute(new Runnable() {
524
563
  @Override
525
564
  public void run() {
@@ -557,6 +596,7 @@ Log.d(TAG, "SWModule_getRenderChapter");
557
596
  }
558
597
  else if (action.equals("SWMgr_stopBibleSync")) {
559
598
  mgr.stopBibleSync();
599
+ if (multicastLock != null && multicastLock.isHeld()) multicastLock.release();
560
600
  callbackContext.success();
561
601
  }
562
602
  else if (action.equals("SWMgr_setJavascript")) {
@@ -587,6 +627,7 @@ Log.d(TAG, "SWModule_getRenderChapter");
587
627
  String translated = mgr.translate(text, locale);
588
628
  callbackContext.success(translated);
589
629
  }
630
+ // kept for older pages; the link now arrives through handleOpenURL
590
631
  else if (action.equals("SWMgr_getLaunchUrl")) {
591
632
  String url = launchUrl;
592
633
  launchUrl = null;
@@ -606,9 +647,19 @@ Log.d(TAG, "SWModule_getRenderChapter");
606
647
  else callbackContext.error("couldn't write: " + args.getString(0));
607
648
  }
608
649
  else if (action.equals("SWMgr_listDir")) {
609
- JSONArray r = new JSONArray();
610
- for (String s : mgr.listDir(args.getString(0))) r.put(s);
611
- callbackContext.success(r);
650
+ // off the WebView thread: a cloud folder's provider may take
651
+ // seconds to finish loading, and the listing waits for it
652
+ final String path = args.getString(0);
653
+ cordova.getThreadPool().execute(new Runnable() { public void run() {
654
+ JSONArray r = new JSONArray();
655
+ for (String s : mgr.listDir(path)) r.put(s);
656
+ // a shared or data folder whose provider failed or never finished
657
+ // loading answers an empty listing natively; the page must know
658
+ SWMgr.SharedStorage ds = mgr.getDataStorage(), ss = mgr.getSharedStorage();
659
+ boolean failed = (ds instanceof SafStorage && ((SafStorage) ds).lastListFailed) || (ss instanceof SafStorage && ((SafStorage) ss).lastListFailed);
660
+ if (failed) callbackContext.error("couldn't list: " + path);
661
+ else callbackContext.success(r);
662
+ }});
612
663
  }
613
664
  else if (action.equals("SWMgr_removeFile")) {
614
665
  if (mgr.removeFile(args.getString(0))) callbackContext.success();
@@ -656,7 +707,26 @@ Log.d(TAG, "SWModule_getRenderChapter");
656
707
  callbackContext.success(label);
657
708
  }
658
709
  else if (action.equals("SWMgr_getDataPath")) {
659
- callbackContext.success(mgr.getDataPath());
710
+ // study data lives in an app-neutral folder named sword-studydata
711
+ // (bookmarks/ inside it, later notes and the like), so several
712
+ // devices and several SWORD apps can share one. Decided by what
713
+ // the picked folder holds, not by its name (a provider can report
714
+ // a name from before a rename for a while): a folder which already
715
+ // has bookmarks/ at its root IS the study-data folder, and so is one
716
+ // named sword-studydata (just created to be it, so still empty);
717
+ // otherwise sword-studydata/ inside it, existing or made on the
718
+ // first write. The provider is asked, so off the WebView thread.
719
+ cordova.getThreadPool().execute(new Runnable() { public void run() {
720
+ String path = mgr.getDataPath();
721
+ SWMgr.SharedStorage ds = mgr.getDataStorage();
722
+ if (ds instanceof SafStorage && path.startsWith("/sword-data")) {
723
+ SafStorage saf = (SafStorage) ds;
724
+ SWMgr.FileInfo root = saf.stat("bookmarks");
725
+ boolean isIt = (root != null && root.isDirectory) || STUDY_DATA_FOLDER.equals(saf.rootName());
726
+ if (!isIt) path += STUDY_DATA_FOLDER + "/";
727
+ }
728
+ callbackContext.success(path);
729
+ }});
660
730
  }
661
731
  else if (action.equals("SWMgr_getDataStorage")) {
662
732
  // the separately chosen study-data folder, or "" if study data lives beside the library
@@ -33,6 +33,7 @@ import android.content.ContentResolver;
33
33
  import android.content.Context;
34
34
  import android.database.Cursor;
35
35
  import android.net.Uri;
36
+ import android.os.Bundle;
36
37
  import android.os.ParcelFileDescriptor;
37
38
  import android.provider.DocumentsContract;
38
39
  import android.provider.DocumentsContract.Document;
@@ -74,6 +75,8 @@ public class SafStorage implements SWMgr.SharedStorage {
74
75
  private final Map<Integer, Staged> staged = new HashMap<Integer, Staged>();
75
76
  // relative path -> document id, learned from listings and creations
76
77
  private final Map<String, String> docIds = new HashMap<String, String>();
78
+ // how long a first listing of a cloud folder may keep loading before we take what there is
79
+ private static final long LIST_WAIT_MILLIS = 15000, LIST_POLL_MILLIS = 250;
77
80
 
78
81
  public SafStorage(Context context, Uri treeUri) {
79
82
  this.stagingDir = new java.io.File(context.getCacheDir(), "saf-staging");
@@ -86,6 +89,12 @@ public class SafStorage implements SWMgr.SharedStorage {
86
89
 
87
90
  public Uri getTreeUri() { return treeUri; }
88
91
 
92
+ /** the picked folder's own name as the provider reports it, or null */
93
+ public String rootName() {
94
+ SWMgr.FileInfo info = queryOne(docUri(treeDocId));
95
+ return (info == null || info.name == null || info.name.length() == 0) ? null : info.name;
96
+ }
97
+
89
98
  /** true if we still hold a grant for the tree and it is reachable */
90
99
  public boolean isAvailable() {
91
100
  return stat("") != null;
@@ -159,7 +168,30 @@ public class SafStorage implements SWMgr.SharedStorage {
159
168
  String prefix = (dirPath.length() == 0) ? "" : dirPath + "/";
160
169
  Cursor c = null;
161
170
  try {
162
- c = resolver.query(childrenUri, COLUMNS, null, null, null);
171
+ // a cloud provider (Google Drive) answers the first query on a folder
172
+ // it has not fetched yet with a partial, often empty, cursor flagged
173
+ // EXTRA_LOADING: taking that as the folder's contents made Bishop
174
+ // think a bookmarks file was absent and create a second one. Ask
175
+ // again until the provider says it is done, within reason.
176
+ long deadline = System.currentTimeMillis() + LIST_WAIT_MILLIS;
177
+ while (true) {
178
+ c = resolver.query(childrenUri, COLUMNS, null, null, null);
179
+ if (c == null) break;
180
+ Bundle extras = c.getExtras();
181
+ String error = (extras == null) ? null : extras.getString(DocumentsContract.EXTRA_ERROR);
182
+ if (error != null) {
183
+ Log.w(TAG, "list failed for " + dirPath + ": provider says " + error);
184
+ return null;
185
+ }
186
+ boolean loading = extras != null && extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
187
+ if (!loading) break;
188
+ if (System.currentTimeMillis() > deadline) {
189
+ Log.w(TAG, "list of " + dirPath + " still loading after " + LIST_WAIT_MILLIS + " ms: not trusting it");
190
+ return null;
191
+ }
192
+ c.close(); c = null;
193
+ try { Thread.sleep(LIST_POLL_MILLIS); } catch (InterruptedException e) { break; }
194
+ }
163
195
  if (c != null) {
164
196
  while (c.moveToNext()) {
165
197
  SWMgr.FileInfo info = fromCursor(c);
@@ -191,6 +223,32 @@ public class SafStorage implements SWMgr.SharedStorage {
191
223
 
192
224
  @Override
193
225
  public int open(String path, String mode, boolean create) {
226
+ int fd = openResolved(path, mode, create);
227
+ if (fd >= 0) return fd;
228
+ // the id we had may be stale: another device can replace a file
229
+ // under the same name (a cloud provider gives it a new id); forget
230
+ // what we knew and look the name up once more
231
+ String known;
232
+ synchronized (this) { known = docIds.get(path); }
233
+ if (known == null) return fd;
234
+ forget(path);
235
+ // ask the provider to refresh what it knows about the file and its
236
+ // folder (a cloud provider's local record can lag a change made on
237
+ // the web or on another device) before the name is looked up again
238
+ try {
239
+ resolver.refresh(docUri(known), null, null);
240
+ String parentId = resolve(parentOf(path));
241
+ if (parentId != null) resolver.refresh(DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, parentId), null, null);
242
+ }
243
+ catch (Exception e) {
244
+ Log.i(TAG, "refresh not honoured for " + path + ": " + e);
245
+ }
246
+ forget(path);
247
+ Log.i(TAG, "retrying " + path + " by name after a failed open");
248
+ return openResolved(path, mode, create);
249
+ }
250
+
251
+ private int openResolved(String path, String mode, boolean create) {
194
252
  String id = resolve(path);
195
253
  if (id == null) {
196
254
  if (!create) return -1;
@@ -301,12 +359,18 @@ public class SafStorage implements SWMgr.SharedStorage {
301
359
  return info;
302
360
  }
303
361
 
362
+ /** set when the last list() could not be trusted (the provider failed or
363
+ * never finished loading): native code sees only an empty listing, and
364
+ * the app must not take that for an empty folder */
365
+ public volatile boolean lastListFailed = false;
366
+
304
367
  @Override
305
368
  public SWMgr.FileInfo[] list(String path) {
369
+ lastListFailed = false;
306
370
  String id = resolve(path);
307
371
  if (id == null) return null;
308
372
  List<SWMgr.FileInfo> entries = listChildren(path, id);
309
- if (entries == null) return null;
373
+ if (entries == null) { lastListFailed = true; return null; }
310
374
  return entries.toArray(new SWMgr.FileInfo[entries.size()]);
311
375
  }
312
376