diff options
| author | Po Lu | 2023-08-03 10:41:40 +0800 |
|---|---|---|
| committer | Po Lu | 2023-08-03 10:41:40 +0800 |
| commit | 91a7e9d83f212e478958c2bafd59057ec816cba0 (patch) | |
| tree | b65829b43a7de48ab506956519a4fcc5759f7117 /java | |
| parent | 60dda3105c9c7eb66480a9cc8a947f6c6bed1a8e (diff) | |
| download | emacs-91a7e9d83f212e478958c2bafd59057ec816cba0.tar.gz emacs-91a7e9d83f212e478958c2bafd59057ec816cba0.zip | |
Update Android port
* java/org/gnu/emacs/EmacsSafThread.java (CacheToplevel):
(EmacsSafThread):
(DocIdEntry):
(getCache):
(pruneCache):
(cacheDirectoryFromCursor):
(run):
(documentIdFromName1):
(statDocument1):
(openDocumentDirectory1):
(openDocument1): Introduce a file status cache and populate
it with files within directories as they are opened.
* java/org/gnu/emacs/EmacsService.java (createDocument):
(createDirectory):
(moveDocument): Invalidate the file status cache wherever
needed.
* src/fileio.c (check_vfs_filename):
(Fset_file_modes): Permit `set-file-modes' to silently fail
on asset and content files.
Diffstat (limited to 'java')
| -rw-r--r-- | java/org/gnu/emacs/EmacsSafThread.java | 326 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsService.java | 38 |
2 files changed, 252 insertions, 112 deletions
diff --git a/java/org/gnu/emacs/EmacsSafThread.java b/java/org/gnu/emacs/EmacsSafThread.java index 007ea9acfbd..29cd3fa6bc7 100644 --- a/java/org/gnu/emacs/EmacsSafThread.java +++ b/java/org/gnu/emacs/EmacsSafThread.java | |||
| @@ -139,11 +139,39 @@ public final class EmacsSafThread extends HandlerThread | |||
| 139 | /* Map between document names and children. */ | 139 | /* Map between document names and children. */ |
| 140 | HashMap<String, DocIdEntry> children; | 140 | HashMap<String, DocIdEntry> children; |
| 141 | 141 | ||
| 142 | /* Map between document names and file status. */ | ||
| 143 | HashMap<String, StatCacheEntry> statCache; | ||
| 144 | |||
| 142 | /* Map between document IDs and cache items. */ | 145 | /* Map between document IDs and cache items. */ |
| 143 | HashMap<String, CacheEntry> idCache; | 146 | HashMap<String, CacheEntry> idCache; |
| 144 | }; | 147 | }; |
| 145 | 148 | ||
| 146 | private final class DocIdEntry | 149 | private static final class StatCacheEntry |
| 150 | { | ||
| 151 | /* The time at which this cache entry was created. */ | ||
| 152 | long time; | ||
| 153 | |||
| 154 | /* Flags, size, and modification time of this file. */ | ||
| 155 | long flags, size, mtime; | ||
| 156 | |||
| 157 | /* Whether or not this file is a directory. */ | ||
| 158 | boolean isDirectory; | ||
| 159 | |||
| 160 | public | ||
| 161 | StatCacheEntry () | ||
| 162 | { | ||
| 163 | time = SystemClock.uptimeMillis (); | ||
| 164 | } | ||
| 165 | |||
| 166 | public boolean | ||
| 167 | isValid () | ||
| 168 | { | ||
| 169 | return ((SystemClock.uptimeMillis () - time) | ||
| 170 | < CACHE_INVALID_TIME * 1000); | ||
| 171 | } | ||
| 172 | }; | ||
| 173 | |||
| 174 | private static final class DocIdEntry | ||
| 147 | { | 175 | { |
| 148 | /* The document ID. */ | 176 | /* The document ID. */ |
| 149 | String documentId; | 177 | String documentId; |
| @@ -162,10 +190,14 @@ public final class EmacsSafThread extends HandlerThread | |||
| 162 | containing this entry, and TOPLEVEL is the toplevel | 190 | containing this entry, and TOPLEVEL is the toplevel |
| 163 | representing it. SIGNAL is a cancellation signal. | 191 | representing it. SIGNAL is a cancellation signal. |
| 164 | 192 | ||
| 193 | RESOLVER is the content provider used to retrieve file | ||
| 194 | information. | ||
| 195 | |||
| 165 | Value is NULL if the file cannot be found. */ | 196 | Value is NULL if the file cannot be found. */ |
| 166 | 197 | ||
| 167 | public CacheEntry | 198 | public CacheEntry |
| 168 | getCacheEntry (Uri tree, CacheToplevel toplevel, | 199 | getCacheEntry (ContentResolver resolver, Uri tree, |
| 200 | CacheToplevel toplevel, | ||
| 169 | CancellationSignal signal) | 201 | CancellationSignal signal) |
| 170 | { | 202 | { |
| 171 | Uri uri; | 203 | Uri uri; |
| @@ -272,6 +304,7 @@ public final class EmacsSafThread extends HandlerThread | |||
| 272 | 304 | ||
| 273 | toplevel = new CacheToplevel (); | 305 | toplevel = new CacheToplevel (); |
| 274 | toplevel.children = new HashMap<String, DocIdEntry> (); | 306 | toplevel.children = new HashMap<String, DocIdEntry> (); |
| 307 | toplevel.statCache = new HashMap<String, StatCacheEntry> (); | ||
| 275 | toplevel.idCache = new HashMap<String, CacheEntry> (); | 308 | toplevel.idCache = new HashMap<String, CacheEntry> (); |
| 276 | cacheToplevels.put (uri, toplevel); | 309 | cacheToplevels.put (uri, toplevel); |
| 277 | return toplevel; | 310 | return toplevel; |
| @@ -311,7 +344,9 @@ public final class EmacsSafThread extends HandlerThread | |||
| 311 | pruneCache () | 344 | pruneCache () |
| 312 | { | 345 | { |
| 313 | Iterator<CacheEntry> iter; | 346 | Iterator<CacheEntry> iter; |
| 347 | Iterator<StatCacheEntry> statIter; | ||
| 314 | CacheEntry tem; | 348 | CacheEntry tem; |
| 349 | StatCacheEntry stat; | ||
| 315 | 350 | ||
| 316 | for (CacheToplevel toplevel : cacheToplevels.values ()) | 351 | for (CacheToplevel toplevel : cacheToplevels.values ()) |
| 317 | { | 352 | { |
| @@ -339,6 +374,25 @@ public final class EmacsSafThread extends HandlerThread | |||
| 339 | 374 | ||
| 340 | iter.remove (); | 375 | iter.remove (); |
| 341 | } | 376 | } |
| 377 | |||
| 378 | statIter = toplevel.statCache.values ().iterator (); | ||
| 379 | |||
| 380 | while (statIter.hasNext ()) | ||
| 381 | { | ||
| 382 | /* Get the cache entry. */ | ||
| 383 | stat = statIter.next (); | ||
| 384 | |||
| 385 | /* If it's not valid anymore, remove it. Iterating over a | ||
| 386 | collection whose contents are being removed is | ||
| 387 | undefined unless the removal is performed using the | ||
| 388 | iterator's own `remove' function, so tem.remove cannot | ||
| 389 | be used here. */ | ||
| 390 | |||
| 391 | if (stat.isValid ()) | ||
| 392 | continue; | ||
| 393 | |||
| 394 | statIter.remove (); | ||
| 395 | } | ||
| 342 | } | 396 | } |
| 343 | 397 | ||
| 344 | postPruneMessage (); | 398 | postPruneMessage (); |
| @@ -379,8 +433,60 @@ public final class EmacsSafThread extends HandlerThread | |||
| 379 | return cacheEntry; | 433 | return cacheEntry; |
| 380 | } | 434 | } |
| 381 | 435 | ||
| 436 | /* Cache file status for DOCUMENTID within TOPLEVEL. Value is the | ||
| 437 | new cache entry. CURSOR is the cursor from where to retrieve the | ||
| 438 | file status, in the form of the columns COLUMN_FLAGS, | ||
| 439 | COLUMN_SIZE, COLUMN_MIME_TYPE and COLUMN_LAST_MODIFIED. */ | ||
| 440 | |||
| 441 | private StatCacheEntry | ||
| 442 | cacheFileStatus (String documentId, CacheToplevel toplevel, | ||
| 443 | Cursor cursor) | ||
| 444 | { | ||
| 445 | StatCacheEntry entry; | ||
| 446 | int flagsIndex, columnIndex, typeIndex; | ||
| 447 | int sizeIndex, mtimeIndex; | ||
| 448 | String type; | ||
| 449 | |||
| 450 | /* Obtain the indices for columns wanted from this cursor. */ | ||
| 451 | flagsIndex = cursor.getColumnIndex (Document.COLUMN_FLAGS); | ||
| 452 | sizeIndex = cursor.getColumnIndex (Document.COLUMN_SIZE); | ||
| 453 | typeIndex = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE); | ||
| 454 | mtimeIndex = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED); | ||
| 455 | |||
| 456 | /* COLUMN_LAST_MODIFIED is allowed to be absent in a | ||
| 457 | conforming documents provider. */ | ||
| 458 | if (flagsIndex < 0 || sizeIndex < 0 || typeIndex < 0) | ||
| 459 | return null; | ||
| 460 | |||
| 461 | /* Get the file status from CURSOR. */ | ||
| 462 | entry = new StatCacheEntry (); | ||
| 463 | entry.flags = cursor.getInt (flagsIndex); | ||
| 464 | type = cursor.getString (typeIndex); | ||
| 465 | |||
| 466 | if (type == null) | ||
| 467 | return null; | ||
| 468 | |||
| 469 | entry.isDirectory = type.equals (Document.MIME_TYPE_DIR); | ||
| 470 | |||
| 471 | if (cursor.isNull (sizeIndex)) | ||
| 472 | /* The size is unknown. */ | ||
| 473 | entry.size = -1; | ||
| 474 | else | ||
| 475 | entry.size = cursor.getLong (sizeIndex); | ||
| 476 | |||
| 477 | /* mtimeIndex is potentially unset, since document providers | ||
| 478 | aren't obligated to provide modification times. */ | ||
| 479 | |||
| 480 | if (mtimeIndex >= 0 && !cursor.isNull (mtimeIndex)) | ||
| 481 | entry.mtime = cursor.getLong (mtimeIndex); | ||
| 482 | |||
| 483 | /* Finally, add this entry to the cache and return. */ | ||
| 484 | toplevel.statCache.put (documentId, entry); | ||
| 485 | return entry; | ||
| 486 | } | ||
| 487 | |||
| 382 | /* Cache the type and as many of the children of the directory | 488 | /* Cache the type and as many of the children of the directory |
| 383 | designated by DOC_ID as possible into TOPLEVEL. | 489 | designated by DOCUMENTID as possible into TOPLEVEL. |
| 384 | 490 | ||
| 385 | CURSOR should be a cursor representing an open directory stream, | 491 | CURSOR should be a cursor representing an open directory stream, |
| 386 | with its projection consisting of at least the display name, | 492 | with its projection consisting of at least the display name, |
| @@ -435,6 +541,12 @@ public final class EmacsSafThread extends HandlerThread | |||
| 435 | idEntry.documentId = id; | 541 | idEntry.documentId = id; |
| 436 | entry.children.put (id, idEntry); | 542 | entry.children.put (id, idEntry); |
| 437 | 543 | ||
| 544 | /* Cache the file status for ID within TOPELVEL too; if a | ||
| 545 | directory listing is being requested, it's very likely | ||
| 546 | that a series of calls for file status will follow. */ | ||
| 547 | |||
| 548 | cacheFileStatus (id, toplevel, cursor); | ||
| 549 | |||
| 438 | /* If this constituent is a directory, don't cache any | 550 | /* If this constituent is a directory, don't cache any |
| 439 | information about it. It cannot be cached without | 551 | information about it. It cannot be cached without |
| 440 | knowing its children. */ | 552 | knowing its children. */ |
| @@ -499,6 +611,7 @@ public final class EmacsSafThread extends HandlerThread | |||
| 499 | 611 | ||
| 500 | toplevel = getCache (uri); | 612 | toplevel = getCache (uri); |
| 501 | toplevel.idCache.remove (documentId); | 613 | toplevel.idCache.remove (documentId); |
| 614 | toplevel.statCache.remove (documentId); | ||
| 502 | 615 | ||
| 503 | /* If the parent of CACHENAME is cached, remove it. */ | 616 | /* If the parent of CACHENAME is cached, remove it. */ |
| 504 | 617 | ||
| @@ -570,6 +683,7 @@ public final class EmacsSafThread extends HandlerThread | |||
| 570 | 683 | ||
| 571 | toplevel = getCache (uri); | 684 | toplevel = getCache (uri); |
| 572 | toplevel.idCache.remove (documentId); | 685 | toplevel.idCache.remove (documentId); |
| 686 | toplevel.statCache.remove (documentId); | ||
| 573 | 687 | ||
| 574 | /* Now remove DOCUMENTID from CACHENAME's cache entry, if | 688 | /* Now remove DOCUMENTID from CACHENAME's cache entry, if |
| 575 | any. */ | 689 | any. */ |
| @@ -619,6 +733,27 @@ public final class EmacsSafThread extends HandlerThread | |||
| 619 | }); | 733 | }); |
| 620 | } | 734 | } |
| 621 | 735 | ||
| 736 | /* Invalidate the file status cache entry for DOCUMENTID within URI. | ||
| 737 | Call this when the contents of a file (i.e. the constituents of a | ||
| 738 | directory file) may have changed, but the document's display name | ||
| 739 | has not. */ | ||
| 740 | |||
| 741 | public void | ||
| 742 | postInvalidateStat (final Uri uri, final String documentId) | ||
| 743 | { | ||
| 744 | handler.post (new Runnable () { | ||
| 745 | @Override | ||
| 746 | public void | ||
| 747 | run () | ||
| 748 | { | ||
| 749 | CacheToplevel toplevel; | ||
| 750 | |||
| 751 | toplevel = getCache (uri); | ||
| 752 | toplevel.statCache.remove (documentId); | ||
| 753 | } | ||
| 754 | }); | ||
| 755 | } | ||
| 756 | |||
| 622 | 757 | ||
| 623 | 758 | ||
| 624 | /* ``Prototypes'' for nested functions that are run within the SAF | 759 | /* ``Prototypes'' for nested functions that are run within the SAF |
| @@ -857,7 +992,8 @@ public final class EmacsSafThread extends HandlerThread | |||
| 857 | /* Fetch just the information for this document. */ | 992 | /* Fetch just the information for this document. */ |
| 858 | 993 | ||
| 859 | if (cache == null) | 994 | if (cache == null) |
| 860 | cache = idEntry.getCacheEntry (uri, toplevel, signal); | 995 | cache = idEntry.getCacheEntry (resolver, uri, toplevel, |
| 996 | signal); | ||
| 861 | 997 | ||
| 862 | if (cache == null) | 998 | if (cache == null) |
| 863 | { | 999 | { |
| @@ -1082,114 +1218,105 @@ public final class EmacsSafThread extends HandlerThread | |||
| 1082 | statDocument1 (String uri, String documentId, | 1218 | statDocument1 (String uri, String documentId, |
| 1083 | CancellationSignal signal) | 1219 | CancellationSignal signal) |
| 1084 | { | 1220 | { |
| 1085 | Uri uriObject; | 1221 | Uri uriObject, tree; |
| 1086 | String[] projection; | 1222 | String[] projection; |
| 1087 | long[] stat; | 1223 | long[] stat; |
| 1088 | int flagsIndex, columnIndex, typeIndex; | ||
| 1089 | int sizeIndex, mtimeIndex, flags; | ||
| 1090 | long tem; | ||
| 1091 | String tem1; | ||
| 1092 | Cursor cursor; | 1224 | Cursor cursor; |
| 1225 | CacheToplevel toplevel; | ||
| 1226 | StatCacheEntry cache; | ||
| 1093 | 1227 | ||
| 1094 | uriObject = Uri.parse (uri); | 1228 | tree = Uri.parse (uri); |
| 1095 | 1229 | ||
| 1096 | if (documentId == null) | 1230 | if (documentId == null) |
| 1097 | documentId = DocumentsContract.getTreeDocumentId (uriObject); | 1231 | documentId = DocumentsContract.getTreeDocumentId (tree); |
| 1098 | 1232 | ||
| 1099 | /* Create a document URI representing DOCUMENTID within URI's | 1233 | /* Create a document URI representing DOCUMENTID within URI's |
| 1100 | authority. */ | 1234 | authority. */ |
| 1101 | 1235 | ||
| 1102 | uriObject | 1236 | uriObject |
| 1103 | = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId); | 1237 | = DocumentsContract.buildDocumentUriUsingTree (tree, documentId); |
| 1104 | 1238 | ||
| 1105 | /* Now stat this document. */ | 1239 | /* See if the file status cache currently contains this |
| 1240 | document. */ | ||
| 1106 | 1241 | ||
| 1107 | projection = new String[] { | 1242 | toplevel = getCache (tree); |
| 1108 | Document.COLUMN_FLAGS, | 1243 | cache = toplevel.statCache.get (documentId); |
| 1109 | Document.COLUMN_LAST_MODIFIED, | ||
| 1110 | Document.COLUMN_MIME_TYPE, | ||
| 1111 | Document.COLUMN_SIZE, | ||
| 1112 | }; | ||
| 1113 | 1244 | ||
| 1114 | cursor = resolver.query (uriObject, projection, null, | 1245 | if (cache == null || !cache.isValid ()) |
| 1115 | null, null, signal); | ||
| 1116 | |||
| 1117 | if (cursor == null) | ||
| 1118 | return null; | ||
| 1119 | |||
| 1120 | /* Obtain the indices for columns wanted from this cursor. */ | ||
| 1121 | flagsIndex = cursor.getColumnIndex (Document.COLUMN_FLAGS); | ||
| 1122 | sizeIndex = cursor.getColumnIndex (Document.COLUMN_SIZE); | ||
| 1123 | typeIndex = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE); | ||
| 1124 | mtimeIndex = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED); | ||
| 1125 | |||
| 1126 | if (!cursor.moveToFirst () | ||
| 1127 | /* COLUMN_LAST_MODIFIED is allowed to be absent in a | ||
| 1128 | conforming documents provider. */ | ||
| 1129 | || flagsIndex < 0 || sizeIndex < 0 || typeIndex < 0) | ||
| 1130 | { | 1246 | { |
| 1131 | cursor.close (); | 1247 | /* Stat this document and enter its information into the |
| 1132 | return null; | 1248 | cache. */ |
| 1133 | } | ||
| 1134 | 1249 | ||
| 1135 | /* Create the array of file status. */ | 1250 | projection = new String[] { |
| 1136 | stat = new long[3]; | 1251 | Document.COLUMN_FLAGS, |
| 1252 | Document.COLUMN_LAST_MODIFIED, | ||
| 1253 | Document.COLUMN_MIME_TYPE, | ||
| 1254 | Document.COLUMN_SIZE, | ||
| 1255 | }; | ||
| 1137 | 1256 | ||
| 1138 | try | 1257 | cursor = resolver.query (uriObject, projection, null, |
| 1139 | { | 1258 | null, null, signal); |
| 1140 | flags = cursor.getInt (flagsIndex); | ||
| 1141 | 1259 | ||
| 1142 | stat[0] |= S_IRUSR; | 1260 | if (cursor == null) |
| 1143 | if ((flags & Document.FLAG_SUPPORTS_WRITE) != 0) | 1261 | return null; |
| 1144 | stat[0] |= S_IWUSR; | ||
| 1145 | 1262 | ||
| 1146 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N | 1263 | try |
| 1147 | && (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) | 1264 | { |
| 1148 | stat[0] |= S_IFCHR; | 1265 | if (!cursor.moveToFirst ()) |
| 1266 | return null; | ||
| 1149 | 1267 | ||
| 1150 | if (cursor.isNull (sizeIndex)) | 1268 | cache = cacheFileStatus (documentId, toplevel, cursor); |
| 1151 | stat[1] = -1; /* The size is unknown. */ | 1269 | } |
| 1152 | else | 1270 | finally |
| 1153 | stat[1] = cursor.getLong (sizeIndex); | 1271 | { |
| 1272 | cursor.close (); | ||
| 1273 | } | ||
| 1154 | 1274 | ||
| 1155 | tem1 = cursor.getString (typeIndex); | 1275 | /* If cache is still null, return null. */ |
| 1156 | 1276 | ||
| 1157 | /* Check if this is a directory file. */ | 1277 | if (cache == null) |
| 1158 | if (tem1.equals (Document.MIME_TYPE_DIR) | 1278 | return null; |
| 1159 | /* Files shouldn't be specials and directories at the same | 1279 | } |
| 1160 | time, but Android doesn't forbid document providers | ||
| 1161 | from returning this information. */ | ||
| 1162 | && (stat[0] & S_IFCHR) == 0) | ||
| 1163 | { | ||
| 1164 | /* Since FLAG_SUPPORTS_WRITE doesn't apply to directories, | ||
| 1165 | just assume they're writable. */ | ||
| 1166 | stat[0] |= S_IFDIR | S_IWUSR | S_IXUSR; | ||
| 1167 | 1280 | ||
| 1168 | /* Directory files cannot be modified if | 1281 | /* Create the array of file status and populate it with the |
| 1169 | FLAG_DIR_SUPPORTS_CREATE is not set. */ | 1282 | information within cache. */ |
| 1283 | stat = new long[3]; | ||
| 1170 | 1284 | ||
| 1171 | if ((flags & Document.FLAG_DIR_SUPPORTS_CREATE) == 0) | 1285 | stat[0] |= S_IRUSR; |
| 1172 | stat[0] &= ~S_IWUSR; | 1286 | if ((cache.flags & Document.FLAG_SUPPORTS_WRITE) != 0) |
| 1173 | } | 1287 | stat[0] |= S_IWUSR; |
| 1174 | 1288 | ||
| 1175 | /* If this file is neither a character special nor a | 1289 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N |
| 1176 | directory, indicate that it's a regular file. */ | 1290 | && (cache.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) |
| 1291 | stat[0] |= S_IFCHR; | ||
| 1177 | 1292 | ||
| 1178 | if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0) | 1293 | stat[1] = cache.size; |
| 1179 | stat[0] |= S_IFREG; | ||
| 1180 | 1294 | ||
| 1181 | if (mtimeIndex >= 0 && !cursor.isNull (mtimeIndex)) | 1295 | /* Check if this is a directory file. */ |
| 1182 | { | 1296 | if (cache.isDirectory |
| 1183 | /* Content providers are allowed to not provide mtime. */ | 1297 | /* Files shouldn't be specials and directories at the same |
| 1184 | tem = cursor.getLong (mtimeIndex); | 1298 | time, but Android doesn't forbid document providers |
| 1185 | stat[2] = tem; | 1299 | from returning this information. */ |
| 1186 | } | 1300 | && (stat[0] & S_IFCHR) == 0) |
| 1187 | } | ||
| 1188 | finally | ||
| 1189 | { | 1301 | { |
| 1190 | cursor.close (); | 1302 | /* Since FLAG_SUPPORTS_WRITE doesn't apply to directories, |
| 1303 | just assume they're writable. */ | ||
| 1304 | stat[0] |= S_IFDIR | S_IWUSR | S_IXUSR; | ||
| 1305 | |||
| 1306 | /* Directory files cannot be modified if | ||
| 1307 | FLAG_DIR_SUPPORTS_CREATE is not set. */ | ||
| 1308 | |||
| 1309 | if ((cache.flags & Document.FLAG_DIR_SUPPORTS_CREATE) == 0) | ||
| 1310 | stat[0] &= ~S_IWUSR; | ||
| 1191 | } | 1311 | } |
| 1192 | 1312 | ||
| 1313 | /* If this file is neither a character special nor a | ||
| 1314 | directory, indicate that it's a regular file. */ | ||
| 1315 | |||
| 1316 | if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0) | ||
| 1317 | stat[0] |= S_IFREG; | ||
| 1318 | |||
| 1319 | stat[2] = cache.mtime; | ||
| 1193 | return stat; | 1320 | return stat; |
| 1194 | } | 1321 | } |
| 1195 | 1322 | ||
| @@ -1389,6 +1516,9 @@ public final class EmacsSafThread extends HandlerThread | |||
| 1389 | Document.COLUMN_DISPLAY_NAME, | 1516 | Document.COLUMN_DISPLAY_NAME, |
| 1390 | Document.COLUMN_DOCUMENT_ID, | 1517 | Document.COLUMN_DOCUMENT_ID, |
| 1391 | Document.COLUMN_MIME_TYPE, | 1518 | Document.COLUMN_MIME_TYPE, |
| 1519 | Document.COLUMN_FLAGS, | ||
| 1520 | Document.COLUMN_LAST_MODIFIED, | ||
| 1521 | Document.COLUMN_SIZE, | ||
| 1392 | }; | 1522 | }; |
| 1393 | 1523 | ||
| 1394 | cursor = resolver.query (uriObject, projection, null, null, | 1524 | cursor = resolver.query (uriObject, projection, null, null, |
| @@ -1441,6 +1571,7 @@ public final class EmacsSafThread extends HandlerThread | |||
| 1441 | Uri treeUri, documentUri; | 1571 | Uri treeUri, documentUri; |
| 1442 | String mode; | 1572 | String mode; |
| 1443 | ParcelFileDescriptor fileDescriptor; | 1573 | ParcelFileDescriptor fileDescriptor; |
| 1574 | CacheToplevel toplevel; | ||
| 1444 | 1575 | ||
| 1445 | treeUri = Uri.parse (uri); | 1576 | treeUri = Uri.parse (uri); |
| 1446 | 1577 | ||
| @@ -1450,35 +1581,26 @@ public final class EmacsSafThread extends HandlerThread | |||
| 1450 | documentUri | 1581 | documentUri |
| 1451 | = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId); | 1582 | = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId); |
| 1452 | 1583 | ||
| 1453 | if (write || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) | 1584 | /* Select the mode used to open the file. */ |
| 1454 | { | ||
| 1455 | /* Select the mode used to open the file. `rw' means open | ||
| 1456 | a stat-able file, while `rwt' means that and to | ||
| 1457 | truncate the file as well. */ | ||
| 1458 | 1585 | ||
| 1586 | if (write) | ||
| 1587 | { | ||
| 1459 | if (truncate) | 1588 | if (truncate) |
| 1460 | mode = "rwt"; | 1589 | mode = "rwt"; |
| 1461 | else | 1590 | else |
| 1462 | mode = "rw"; | 1591 | mode = "rw"; |
| 1463 | |||
| 1464 | fileDescriptor | ||
| 1465 | = resolver.openFileDescriptor (documentUri, mode, | ||
| 1466 | signal); | ||
| 1467 | } | 1592 | } |
| 1468 | else | 1593 | else |
| 1469 | { | 1594 | mode = "r"; |
| 1470 | /* Select the mode used to open the file. `openFile' | ||
| 1471 | below means always open a stat-able file. */ | ||
| 1472 | 1595 | ||
| 1473 | if (truncate) | 1596 | fileDescriptor |
| 1474 | /* Invalid mode! */ | 1597 | = resolver.openFileDescriptor (documentUri, mode, |
| 1475 | return null; | 1598 | signal); |
| 1476 | else | ||
| 1477 | mode = "r"; | ||
| 1478 | 1599 | ||
| 1479 | fileDescriptor = resolver.openFile (documentUri, mode, | 1600 | /* Every time a document is opened, remove it from the file status |
| 1480 | signal); | 1601 | cache. */ |
| 1481 | } | 1602 | toplevel = getCache (treeUri); |
| 1603 | toplevel.statCache.remove (documentId); | ||
| 1482 | 1604 | ||
| 1483 | return fileDescriptor; | 1605 | return fileDescriptor; |
| 1484 | } | 1606 | } |
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 8554dadd06e..a3dea368272 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java | |||
| @@ -1586,7 +1586,7 @@ public final class EmacsService extends Service | |||
| 1586 | String mimeType, separator, mime, extension; | 1586 | String mimeType, separator, mime, extension; |
| 1587 | int index; | 1587 | int index; |
| 1588 | MimeTypeMap singleton; | 1588 | MimeTypeMap singleton; |
| 1589 | Uri directoryUri, docUri; | 1589 | Uri treeUri, directoryUri, docUri; |
| 1590 | 1590 | ||
| 1591 | /* Try to get the MIME type for this document. | 1591 | /* Try to get the MIME type for this document. |
| 1592 | Default to ``application/octet-stream''. */ | 1592 | Default to ``application/octet-stream''. */ |
| @@ -1608,15 +1608,15 @@ public final class EmacsService extends Service | |||
| 1608 | } | 1608 | } |
| 1609 | 1609 | ||
| 1610 | /* Now parse URI. */ | 1610 | /* Now parse URI. */ |
| 1611 | directoryUri = Uri.parse (uri); | 1611 | treeUri = Uri.parse (uri); |
| 1612 | 1612 | ||
| 1613 | if (documentId == null) | 1613 | if (documentId == null) |
| 1614 | documentId = DocumentsContract.getTreeDocumentId (directoryUri); | 1614 | documentId = DocumentsContract.getTreeDocumentId (treeUri); |
| 1615 | 1615 | ||
| 1616 | /* And build a file URI referring to the directory. */ | 1616 | /* And build a file URI referring to the directory. */ |
| 1617 | 1617 | ||
| 1618 | directoryUri | 1618 | directoryUri |
| 1619 | = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri, | 1619 | = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri, |
| 1620 | documentId); | 1620 | documentId); |
| 1621 | 1621 | ||
| 1622 | docUri = DocumentsContract.createDocument (resolver, | 1622 | docUri = DocumentsContract.createDocument (resolver, |
| @@ -1626,6 +1626,11 @@ public final class EmacsService extends Service | |||
| 1626 | if (docUri == null) | 1626 | if (docUri == null) |
| 1627 | return null; | 1627 | return null; |
| 1628 | 1628 | ||
| 1629 | /* Invalidate the file status of the containing directory. */ | ||
| 1630 | |||
| 1631 | if (storageThread != null) | ||
| 1632 | storageThread.postInvalidateStat (treeUri, documentId); | ||
| 1633 | |||
| 1629 | /* Return the ID of the new document. */ | 1634 | /* Return the ID of the new document. */ |
| 1630 | return DocumentsContract.getDocumentId (docUri); | 1635 | return DocumentsContract.getDocumentId (docUri); |
| 1631 | } | 1636 | } |
| @@ -1638,18 +1643,18 @@ public final class EmacsService extends Service | |||
| 1638 | throws FileNotFoundException | 1643 | throws FileNotFoundException |
| 1639 | { | 1644 | { |
| 1640 | int index; | 1645 | int index; |
| 1641 | Uri directoryUri, docUri; | 1646 | Uri treeUri, directoryUri, docUri; |
| 1642 | 1647 | ||
| 1643 | /* Now parse URI. */ | 1648 | /* Now parse URI. */ |
| 1644 | directoryUri = Uri.parse (uri); | 1649 | treeUri = Uri.parse (uri); |
| 1645 | 1650 | ||
| 1646 | if (documentId == null) | 1651 | if (documentId == null) |
| 1647 | documentId = DocumentsContract.getTreeDocumentId (directoryUri); | 1652 | documentId = DocumentsContract.getTreeDocumentId (treeUri); |
| 1648 | 1653 | ||
| 1649 | /* And build a file URI referring to the directory. */ | 1654 | /* And build a file URI referring to the directory. */ |
| 1650 | 1655 | ||
| 1651 | directoryUri | 1656 | directoryUri |
| 1652 | = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri, | 1657 | = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri, |
| 1653 | documentId); | 1658 | documentId); |
| 1654 | 1659 | ||
| 1655 | /* If name ends with a directory separator character, delete | 1660 | /* If name ends with a directory separator character, delete |
| @@ -1669,7 +1674,12 @@ public final class EmacsService extends Service | |||
| 1669 | if (docUri == null) | 1674 | if (docUri == null) |
| 1670 | return null; | 1675 | return null; |
| 1671 | 1676 | ||
| 1672 | /* Return the ID of the new document. */ | 1677 | /* Return the ID of the new document, but first invalidate the |
| 1678 | state of the containing directory. */ | ||
| 1679 | |||
| 1680 | if (storageThread != null) | ||
| 1681 | storageThread.postInvalidateStat (treeUri, documentId); | ||
| 1682 | |||
| 1673 | return DocumentsContract.getDocumentId (docUri); | 1683 | return DocumentsContract.getDocumentId (docUri); |
| 1674 | } | 1684 | } |
| 1675 | 1685 | ||
| @@ -1763,7 +1773,15 @@ public final class EmacsService extends Service | |||
| 1763 | /* Now invalidate the caches for both DIRNAME and DOCID. */ | 1773 | /* Now invalidate the caches for both DIRNAME and DOCID. */ |
| 1764 | 1774 | ||
| 1765 | if (storageThread != null) | 1775 | if (storageThread != null) |
| 1766 | storageThread.postInvalidateCacheDir (uri1, docId, dirName); | 1776 | { |
| 1777 | storageThread.postInvalidateCacheDir (uri1, docId, dirName); | ||
| 1778 | |||
| 1779 | /* Invalidate the stat cache entries for both the source and | ||
| 1780 | destination directories, since their contents have | ||
| 1781 | changed. */ | ||
| 1782 | storageThread.postInvalidateStat (uri1, dstId); | ||
| 1783 | storageThread.postInvalidateStat (uri1, srcId); | ||
| 1784 | } | ||
| 1767 | 1785 | ||
| 1768 | return (name != null | 1786 | return (name != null |
| 1769 | ? DocumentsContract.getDocumentId (name) | 1787 | ? DocumentsContract.getDocumentId (name) |