]> git.eshelyaron.com Git - emacs.git/commitdiff
Partially implement rename operations on SAF files
authorPo Lu <luangruo@yahoo.com>
Sun, 30 Jul 2023 05:39:27 +0000 (13:39 +0800)
committerPo Lu <luangruo@yahoo.com>
Sun, 30 Jul 2023 05:39:27 +0000 (13:39 +0800)
* java/org/gnu/emacs/EmacsSafThread.java
(postInvalidateCacheDir):
* java/org/gnu/emacs/EmacsService.java (renameDocument): New
functions.
* src/android.c (android_init_emacs_service):
* src/android.h (struct android_emacs_service): Link to new JNI
function.
* src/androidvfs.c (android_saf_rename_document): New function.
(android_saf_tree_rename): Implement in terms of that function
if possible.

java/org/gnu/emacs/EmacsSafThread.java
java/org/gnu/emacs/EmacsService.java
src/android.c
src/android.h
src/androidvfs.c

index b0d014ffe947074e908e8752159197096e1375e4..007ea9acfbd9ce15bd4c03fcedc130a9d0915215 100644 (file)
@@ -134,7 +134,7 @@ public final class EmacsSafThread extends HandlerThread
   }
 
 \f
-  private final class CacheToplevel
+  private static final class CacheToplevel
   {
     /* Map between document names and children.  */
     HashMap<String, DocIdEntry> children;
@@ -232,7 +232,7 @@ public final class EmacsSafThread extends HandlerThread
     }
   };
 
-  private final class CacheEntry
+  private static final class CacheEntry
   {
     /* The type of this document.  */
     String type;
@@ -545,6 +545,80 @@ public final class EmacsSafThread extends HandlerThread
       });
   }
 
+  /* Invalidate the cache entry denoted by DOCUMENT_ID, within the
+     document tree URI.
+     Call this after deleting a document or directory.
+
+     At the same time, remove the child referring to DOCUMENTID from
+     within CACHENAME's cache entry if it exists.  */
+
+  public void
+  postInvalidateCacheDir (final Uri uri, final String documentId,
+                         final String cacheName)
+  {
+    handler.post (new Runnable () {
+       @Override
+       public void
+       run ()
+       {
+         CacheToplevel toplevel;
+         HashMap<String, DocIdEntry> children;
+         String[] components;
+         CacheEntry entry;
+         DocIdEntry idEntry;
+         Iterator<DocIdEntry> iter;
+
+         toplevel = getCache (uri);
+         toplevel.idCache.remove (documentId);
+
+         /* Now remove DOCUMENTID from CACHENAME's cache entry, if
+            any.  */
+
+         children = toplevel.children;
+         components = cacheName.split ("/");
+
+         for (String component : components)
+           {
+             /* Java `split' removes trailing empty matches but not
+                leading or intermediary ones.  */
+             if (component.isEmpty ())
+               continue;
+
+             /* Search for this component within the last level
+                of the cache.  */
+
+             idEntry = children.get (component);
+
+             if (idEntry == null)
+               /* Not cached, so return.  */
+               return;
+
+             entry = toplevel.idCache.get (idEntry.documentId);
+
+             if (entry == null)
+               /* Not cached, so return.  */
+               return;
+
+             /* Locate the next component within this
+                directory.  */
+             children = entry.children;
+           }
+
+         iter = children.values ().iterator ();
+         while (iter.hasNext ())
+           {
+             idEntry = iter.next ();
+
+             if (idEntry.documentId.equals (documentId))
+               {
+                 iter.remove ();
+                 break;
+               }
+           }
+       }
+      });
+  }
+
 \f
 
   /* ``Prototypes'' for nested functions that are run within the SAF
index 5186dec974a3cb388c4e6961970e4c9a2c9f9aaf..07e585ad37ce74ee9dec09ccbd801e950da7d62a 100644 (file)
@@ -1698,4 +1698,41 @@ public final class EmacsService extends Service
 
     return -1;
   }
+
+  /* Rename the document designated by DOCID inside the directory tree
+     identified by URI, which should be within the directory
+     designated by DIR, to NAME.  If the file can't be renamed because
+     it doesn't support renaming, return -1, 0 otherwise.  */
+
+  public int
+  renameDocument (String uri, String docId, String dir, String name)
+    throws FileNotFoundException
+  {
+    Uri tree, uriObject;
+
+    tree = Uri.parse (uri);
+    uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
+
+    try
+      {
+       if (DocumentsContract.renameDocument (resolver, uriObject,
+                                             name)
+           != null)
+         {
+           /* Invalidate the cache.  */
+           if (storageThread != null)
+             storageThread.postInvalidateCacheDir (tree, docId,
+                                                   name);
+           return 0;
+         }
+      }
+    catch (UnsupportedOperationException e)
+      {
+       ;;
+      }
+
+    /* Handle unsupported operation exceptions specially, so
+       `android_rename' can return ENXDEV.  */
+    return -1;
+  }
 };
index a75193e5edd4043283dfef6419fc88633b055a1a..0bdbc4e0c4be969aa98d70adc386c903dadb15da 100644 (file)
@@ -1583,6 +1583,9 @@ android_init_emacs_service (void)
   FIND_METHOD (delete_document, "deleteDocument",
               "(Ljava/lang/String;Ljava/lang/String;"
               "Ljava/lang/String;)I");
+  FIND_METHOD (rename_document, "renameDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;Ljava/lang/String;)I");
 #undef FIND_METHOD
 }
 
index fd391fa643510ab41e72864983caedf5be858d73..591f1a1e43c5473ec3968406174ae9ad450e0939 100644 (file)
@@ -281,6 +281,7 @@ struct android_emacs_service
   jmethodID create_document;
   jmethodID create_directory;
   jmethodID delete_document;
+  jmethodID rename_document;
 };
 
 extern JNIEnv *android_java_env;
index c83d3a14b5001f31a159deda43d25bdef18a2d7f..c529e1fb30f8ec4afe7406440ed6fedcdf1d005f 100644 (file)
@@ -4035,6 +4035,71 @@ android_saf_delete_document (const char *tree, const char *doc_id,
   return 0;
 }
 
+/* Declared further below.  */
+static int android_document_id_from_name (const char *, char *, char **);
+
+/* Rename the document designated by DOC_ID inside the directory tree
+   identified by URI, which should be within the directory by the name
+   of DIR, to NAME.  If the document can't be renamed, return -1 and
+   set errno to a value describing the error.  Return 0 if the rename
+   is successful.
+
+   Android permits the same document to appear in multiple
+   directories, but stores the display name inside the document
+   ``inode'' itself instead of the directory entries that refer to it.
+   Because of this, this operation may cause other directory entries
+   outside DIR to be renamed.  */
+
+static int
+android_saf_rename_document (const char *uri, const char *doc_id,
+                            const char *dir, const char *name)
+{
+  int rc;
+  jstring uri1, doc_id1, dir1, name1;
+  jmethodID method;
+
+  /* Now build the strings for the URI, document ID, directory name
+     and directory ID.  */
+
+  uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
+  android_exception_check ();
+  doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, doc_id);
+  android_exception_check_1 (uri1);
+  dir1 = (*android_java_env)->NewStringUTF (android_java_env, dir);
+  android_exception_check_2 (doc_id1, uri1);
+  name1 = (*android_java_env)->NewStringUTF (android_java_env, name);
+  android_exception_check_3 (dir1, doc_id1, uri1);
+
+  method = service_class.rename_document;
+  rc = (*android_java_env)->CallNonvirtualIntMethod (android_java_env,
+                                                    emacs_service,
+                                                    service_class.class,
+                                                    method, uri1, doc_id1,
+                                                    dir1, name1);
+
+  /* Check for exceptions.  */
+  if (android_saf_exception_check (4, uri1, doc_id1, dir1, name1))
+    return -1;
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (uri1);
+  ANDROID_DELETE_LOCAL_REF (doc_id1);
+  ANDROID_DELETE_LOCAL_REF (dir1);
+  ANDROID_DELETE_LOCAL_REF (name1);
+
+  /* Then check for errors handled within the Java code.  */
+
+  if (rc == -1)
+    {
+      /* UnsupportedOperationException.  Trick the caller into falling
+        back on delete-then-copy code.  */
+      errno = EXDEV;
+      return -1;
+    }
+
+  return 0;
+}
+
 \f
 
 /* SAF directory vnode.  A file within a SAF directory tree is
@@ -4591,9 +4656,117 @@ android_saf_tree_rename (struct android_vnode *src,
                         struct android_vnode *dst,
                         bool keep_existing)
 {
-  /* TODO */
-  errno = ENOSYS;
-  return -1;
+  char *last, *dst_last;
+  struct android_saf_tree_vnode *vp, *vdst;
+  char path[PATH_MAX], *fill;
+
+  /* If dst isn't a tree, file or new vnode, return EXDEV.  */
+
+  if (dst->type != ANDROID_VNODE_SAF_TREE
+      && dst->type != ANDROID_VNODE_SAF_FILE
+      && dst->type != ANDROID_VNODE_SAF_NEW)
+    {
+      errno = EXDEV;
+      return -1;
+    }
+
+  vp = (struct android_saf_tree_vnode *) src;
+  vdst = (struct android_saf_tree_vnode *) dst;
+
+  /* if vp and vdst refer to different tree URIs, return EXDEV.  */
+
+  if (strcmp (vp->tree_uri, vdst->tree_uri))
+    {
+      errno = EXDEV;
+      return -1;
+    }
+
+  /* If `keep_existing' and the destination vnode designates an
+     existing file, return EEXIST.  */
+
+  if (keep_existing && dst->type != ANDROID_VNODE_SAF_NEW)
+    {
+      errno = EEXIST;
+      return -1;
+    }
+
+  /* Unix `rename' maps to two Android content provider operations.
+     The first case is a simple rename, where src and dst are both
+     located within the same directory.  Compare the file names of
+     both up to the component before the last.  */
+
+  last = strrchr (vp->name, '/');
+  eassert (last != NULL);
+
+  if (last[1] == '\0')
+    {
+      if (last == vp->name)
+       {
+         /* This means the caller is trying to rename the root
+            directory of the tree.  */
+         errno = EROFS;
+         return -1;
+       }
+
+      /* The name is terminated by a trailing directory separator.
+         Search backwards for the preceding directory separator.  */
+      last = memrchr (vp->name, '/', last - vp->name);
+      eassert (last != NULL);
+    }
+
+  /* Find the end of the second-to-last component in vdst's name.  */
+
+  dst_last = strrchr (vdst->name, '/');
+  eassert (dst_last != NULL);
+
+  if (dst_last[1] == '\0')
+    {
+      if (dst_last == vdst->name)
+       {
+         /* Forbid overwriting the root of the tree either.  */
+         errno = EROFS;
+         return -1;
+       }
+
+      dst_last = memrchr (vdst->name, '/', dst_last - vdst->name);
+      eassert (dst_last != NULL);
+    }
+
+  if (dst_last - vdst->name != last - vp->name
+      || memcmp (vp->name, vdst->name, last - vp->name))
+    {
+      /* The second case is where the file must be moved from one
+         directory to the other, and possibly then recreated under a
+         new name.  */
+
+      errno = EXDEV; /* TODO */
+      return -1;
+    }
+
+  /* Otherwise, do this simple rename.  The name of the parent
+     directory is required, as it provides the directory whose entries
+     will be modified.  */
+
+  if (last - vp->name >= PATH_MAX)
+    {
+      errno = ENAMETOOLONG;
+      return -1;
+    }
+
+  /* If the destination document exists, delete it.  */
+
+  if (dst->type != ANDROID_VNODE_SAF_NEW
+      && android_saf_delete_document (vdst->tree_uri,
+                                     vdst->document_id,
+                                     vdst->name))
+    return -1;
+
+  fill = mempcpy (path, vp->name, last - vp->name);
+  *fill = '\0';
+  return android_saf_rename_document (vp->tree_uri,
+                                     vp->document_id,
+                                     path,
+                                     dst_last + 1);
 }
 
 static int