aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorPo Lu2023-08-03 10:41:40 +0800
committerPo Lu2023-08-03 10:41:40 +0800
commit91a7e9d83f212e478958c2bafd59057ec816cba0 (patch)
treeb65829b43a7de48ab506956519a4fcc5759f7117 /java
parent60dda3105c9c7eb66480a9cc8a947f6c6bed1a8e (diff)
downloademacs-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.java326
-rw-r--r--java/org/gnu/emacs/EmacsService.java38
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)