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.
- package/RELEASENOTES.md +18 -0
- package/libs/android/arm64-v8a/libsword.so +0 -0
- package/libs/android/armeabi-v7a/libsword.so +0 -0
- package/libs/android/x86/libsword.so +0 -0
- package/libs/android/x86_64/libsword.so +0 -0
- package/libs/ios/SWORD.xcframework/ios-arm64/libSWORD.a +2 -2
- package/libs/ios/SWORD.xcframework/ios-arm64-simulator/libSWORD.a +2 -2
- package/package.json +1 -1
- package/plugin.xml +5 -3
- package/src/android/SWORD.java +79 -9
- package/src/android/SafStorage.java +66 -2
- package/src/ios/SWORD.swift +571 -69
- package/www/SWORD.js +31 -3
- package/libs/ios/curl.xcframework/Info.plist +0 -47
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/curl.h +0 -3373
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/curlver.h +0 -77
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/easy.h +0 -123
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/header.h +0 -74
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/mprintf.h +0 -85
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/multi.h +0 -560
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/options.h +0 -70
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/stdcheaders.h +0 -35
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/system.h +0 -399
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/typecheck-gcc.h +0 -964
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/urlapi.h +0 -156
- package/libs/ios/curl.xcframework/ios-arm64/Headers/curl/websockets.h +0 -98
- package/libs/ios/curl.xcframework/ios-arm64/libcurl-all.a +0 -0
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/curl.h +0 -3373
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/curlver.h +0 -77
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/easy.h +0 -123
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/header.h +0 -74
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/mprintf.h +0 -85
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/multi.h +0 -560
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/options.h +0 -70
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/stdcheaders.h +0 -35
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/system.h +0 -399
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/typecheck-gcc.h +0 -964
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/urlapi.h +0 -156
- package/libs/ios/curl.xcframework/ios-arm64-simulator/Headers/curl/websockets.h +0 -98
- 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
|
|
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:
|
|
4
|
-
sha256:
|
|
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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 37272016 bytes
|
|
4
|
+
sha256: d4a361c089cb1384d15fce6755470d9d3bd012d8acf7af69ba27315ea4f71ae8
|
package/package.json
CHANGED
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.
|
|
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>
|
package/src/android/SWORD.java
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|