]> git.eshelyaron.com Git - emacs.git/commitdiff
Implement cross-directory SAF rename operations
authorPo Lu <luangruo@yahoo.com>
Mon, 31 Jul 2023 02:50:12 +0000 (10:50 +0800)
committerPo Lu <luangruo@yahoo.com>
Mon, 31 Jul 2023 02:50:12 +0000 (10:50 +0800)
* java/org/gnu/emacs/EmacsService.java (renameDocument): Don't
catch UnsupportedOperationException; handle ENOSYS in
android_saf_rename_document instead.
(moveDocument): New function.
* lisp/subr.el (y-or-n-p): Always change the text conversion
style.
* src/android.c (android_init_emacs_service)
(android_exception_check_4): New function.
* src/android.h: Update Java function table.
* src/androidvfs.c (android_saf_rename_document): Handle ENOSYS
here by setting errno to EXDEV.
(android_saf_move_document): New function.
(android_document_id_from_name): Take const `dir_name'.
(android_saf_tree_rename): Use delete-move-rename to implement
cross-directory renames.

java/org/gnu/emacs/EmacsService.java
lisp/subr.el
src/android.c
src/android.h
src/androidvfs.c

index 07e585ad37ce74ee9dec09ccbd801e950da7d62a..e714f75fdf28b8af807c81014bad370989e35669 100644 (file)
@@ -1713,26 +1713,59 @@ public final class EmacsService extends Service
     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)
+    if (DocumentsContract.renameDocument (resolver, uriObject,
+                                         name)
+       != null)
       {
-       ;;
+       /* Invalidate the cache.  */
+       if (storageThread != null)
+         storageThread.postInvalidateCacheDir (tree, docId,
+                                               name);
+       return 0;
       }
 
-    /* Handle unsupported operation exceptions specially, so
-       `android_rename' can return ENXDEV.  */
+    /* Handle errors specially, so `android_saf_rename_document' can
+       return ENXDEV.  */
     return -1;
   }
+
+  /* Move the document designated by DOCID from the directory under
+     DIR_NAME designated by SRCID to the directory designated by
+     DSTID.  If the ID of the document being moved changes as a
+     consequence of the movement, return the new ID, else NULL.
+
+     URI is the document tree containing all three documents.  */
+
+  public String
+  moveDocument (String uri, String docId, String dirName,
+               String dstId, String srcId)
+    throws FileNotFoundException
+  {
+    Uri uri1, docId1, dstId1, srcId1;
+    Uri name;
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+      throw new UnsupportedOperationException ("Documents aren't capable"
+                                              + " of being moved on Android"
+                                              + " versions before 7.0.");
+
+    uri1 = Uri.parse (uri);
+    docId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, docId);
+    dstId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, dstId);
+    srcId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, srcId);
+
+    /* Move the document; this function returns the new ID of the
+       document should it change.  */
+    name = DocumentsContract.moveDocument (resolver, docId1,
+                                          srcId1, dstId1);
+
+    /* Now invalidate the caches for both DIRNAME and DOCID.  */
+
+    if (storageThread != null)
+      storageThread.postInvalidateCacheDir (uri1, docId, dirName);
+
+    return (name != null
+           ? DocumentsContract.getDocumentId (name)
+           : null);
+  }
 };
index 4346f99fa38504cf05f6485a0c80a7eb20518f87..36aeeabea47f467a0a9af476d8f038840004ef64 100644 (file)
@@ -3796,11 +3796,10 @@ like) while `y-or-n-p' is running)."
              ;; Protect this-command when called from pre-command-hook (bug#45029)
              (this-command this-command)
              (str (progn
-                    (when (active-minibuffer-window)
-                      ;; If the minibuffer is already active, the
-                      ;; selected window might not change.  Disable
-                      ;; text conversion by hand.
-                      (set-text-conversion-style text-conversion-style))
+                    ;; If the minibuffer is already active, the
+                    ;; selected window might not change.  Disable
+                    ;; text conversion by hand.
+                    (set-text-conversion-style text-conversion-style)
                     (read-from-minibuffer
                      prompt nil keymap nil
                      (or y-or-n-p-history-variable t)))))
index 0bdbc4e0c4be969aa98d70adc386c903dadb15da..8c0232a51f84410480203d23307a1cdfe8e158bc 100644 (file)
@@ -1586,6 +1586,10 @@ android_init_emacs_service (void)
   FIND_METHOD (rename_document, "renameDocument",
               "(Ljava/lang/String;Ljava/lang/String;"
               "Ljava/lang/String;Ljava/lang/String;)I");
+  FIND_METHOD (move_document, "moveDocument",
+              "(Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;Ljava/lang/String;"
+              "Ljava/lang/String;)Ljava/lang/String;");
 #undef FIND_METHOD
 }
 
@@ -5667,6 +5671,29 @@ android_exception_check_3 (jobject object, jobject object1,
   memory_full (0);
 }
 
+/* Like android_exception_check_3, except it takes more than three
+   local reference arguments.  */
+
+void
+android_exception_check_4 (jobject object, jobject object1,
+                          jobject object2, jobject object3)
+{
+  if (likely (!(*android_java_env)->ExceptionCheck (android_java_env)))
+    return;
+
+  __android_log_print (ANDROID_LOG_WARN, __func__,
+                      "Possible out of memory error. "
+                      " The Java exception follows:  ");
+  /* Describe exactly what went wrong.  */
+  (*android_java_env)->ExceptionDescribe (android_java_env);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+  ANDROID_DELETE_LOCAL_REF (object1);
+  ANDROID_DELETE_LOCAL_REF (object2);
+  ANDROID_DELETE_LOCAL_REF (object3);
+  memory_full (0);
+}
+
 /* Check for JNI problems based on the value of OBJECT.
 
    Signal out of memory if OBJECT is NULL.  OBJECT1 means the
index 591f1a1e43c5473ec3968406174ae9ad450e0939..8440fb9bc753d3d1e9c3406df7a0c058a5b861c6 100644 (file)
@@ -110,6 +110,7 @@ extern void android_exception_check (void);
 extern void android_exception_check_1 (jobject);
 extern void android_exception_check_2 (jobject, jobject);
 extern void android_exception_check_3 (jobject, jobject, jobject);
+extern void android_exception_check_4 (jobject, jobject, jobject, jobject);
 extern void android_exception_check_nonnull (void *, jobject);
 extern void android_exception_check_nonnull_1 (void *, jobject, jobject);
 
@@ -282,6 +283,7 @@ struct android_emacs_service
   jmethodID create_directory;
   jmethodID delete_document;
   jmethodID rename_document;
+  jmethodID move_document;
 };
 
 extern JNIEnv *android_java_env;
index c529e1fb30f8ec4afe7406440ed6fedcdf1d005f..9acc8f2b1393a53cc971f7f41d5d31e34dafedbb 100644 (file)
@@ -4036,7 +4036,8 @@ android_saf_delete_document (const char *tree, const char *doc_id,
 }
 
 /* Declared further below.  */
-static int android_document_id_from_name (const char *, char *, char **);
+static int android_document_id_from_name (const char *, const 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
@@ -4078,8 +4079,17 @@ android_saf_rename_document (const char *uri, const char *doc_id,
                                                     dir1, name1);
 
   /* Check for exceptions.  */
+
   if (android_saf_exception_check (4, uri1, doc_id1, dir1, name1))
-    return -1;
+    {
+      /* Substitute EXDEV for ENOSYS, so callers fall back on
+        delete-then-copy.  */
+
+      if (errno == ENOSYS)
+       errno = EXDEV;
+
+      return -1;
+    }
 
   /* Delete unused local references.  */
   ANDROID_DELETE_LOCAL_REF (uri1);
@@ -4100,6 +4110,109 @@ android_saf_rename_document (const char *uri, const char *doc_id,
   return 0;
 }
 
+/* Move the document designated by *DOC_ID from the directory under
+   DIR_NAME to the directory designated by DST_ID.  All three
+   directories are located within the tree identified by the given
+   URI.
+
+   If the document's ID changes as a result of the movement, free
+   *DOC_ID and store the new document ID within.
+
+   Value is 0 upon success, -1 otherwise with errno set.  */
+
+static int
+android_saf_move_document (const char *uri, char **doc_id,
+                          const char *dir_name, const char *dst_id)
+{
+  char *src_id, *id;
+  jobject uri1, doc_id1, dir_name1, dst_id1, src_id1;
+  jstring result;
+  jmethodID method;
+  int rc;
+  const char *new_id;
+
+  /* Obtain the name of the source directory.  */
+  src_id = NULL;
+  rc = android_document_id_from_name (uri, dir_name, &src_id);
+
+  if (rc != 1)
+    {
+      /* This file is either not a directory or nonexistent.  */
+      xfree (src_id);
+
+      switch (rc)
+       {
+       case 0:
+         errno = ENOTDIR;
+         return -1;
+
+       case -1:
+       case -2:
+         errno = ENOENT;
+         return -1;
+
+       default:
+         emacs_abort ();
+       }
+    }
+
+  /* Build Java strings for all five arguments.  */
+  id = *doc_id;
+  uri1 = (*android_java_env)->NewStringUTF (android_java_env, uri);
+  android_exception_check ();
+  doc_id1 = (*android_java_env)->NewStringUTF (android_java_env, id);
+  android_exception_check_1 (uri1);
+  dir_name1 = (*android_java_env)->NewStringUTF (android_java_env, dir_name);
+  android_exception_check_2 (doc_id1, uri1);
+  dst_id1 = (*android_java_env)->NewStringUTF (android_java_env, dst_id);
+  android_exception_check_3 (dir_name1, doc_id1, uri1);
+  src_id1 = (*android_java_env)->NewStringUTF (android_java_env, src_id);
+  xfree (src_id);
+  android_exception_check_4 (dst_id1, dir_name1, doc_id1, uri1);
+
+  /* Do the rename.  */
+  method = service_class.move_document;
+  result = (*android_java_env)->CallObjectMethod (android_java_env,
+                                                 emacs_service,
+                                                 method, uri1,
+                                                 doc_id1, dir_name1,
+                                                 dst_id1, src_id1);
+  if (android_saf_exception_check (5, src_id1, dst_id1, dir_name1,
+                                  doc_id1, uri1))
+    {
+      /* Substitute EXDEV for ENOSYS, so callers fall back on
+        delete-then-copy.  */
+
+      if (errno == ENOSYS)
+       errno = EXDEV;
+
+      return -1;
+    }
+
+  /* Delete unused local references.  */
+  ANDROID_DELETE_LOCAL_REF (src_id1);
+  ANDROID_DELETE_LOCAL_REF (dst_id1);
+  ANDROID_DELETE_LOCAL_REF (dir_name1);
+  ANDROID_DELETE_LOCAL_REF (doc_id1);
+  ANDROID_DELETE_LOCAL_REF (uri1);
+
+  if (result)
+    {
+      /* The document ID changed.  Free id and replace *DOC_ID with
+        the new ID.  */
+      xfree (id);
+      new_id = (*android_java_env)->GetStringUTFChars (android_java_env,
+                                                      result, NULL);
+      android_exception_check_nonnull ((void *) new_id, result);
+      *doc_id = xstrdup (new_id);
+      (*android_java_env)->ReleaseStringUTFChars (android_java_env, result,
+                                                 new_id);
+      ANDROID_DELETE_LOCAL_REF (result);
+    }
+
+  return 0;
+}
+
 \f
 
 /* SAF directory vnode.  A file within a SAF directory tree is
@@ -4282,7 +4395,7 @@ android_verify_jni_string (const char *name)
    ID lookup to be canceled.  */
 
 static int
-android_document_id_from_name (const char *tree_uri, char *name,
+android_document_id_from_name (const char *tree_uri, const char *name,
                               char **id)
 {
   jobjectArray result;
@@ -4658,7 +4771,9 @@ android_saf_tree_rename (struct android_vnode *src,
 {
   char *last, *dst_last;
   struct android_saf_tree_vnode *vp, *vdst;
-  char path[PATH_MAX], *fill;
+  char path[PATH_MAX], path1[PATH_MAX];
+  char *fill, *dst_id;
+  int rc;
 
   /* If dst isn't a tree, file or new vnode, return EXDEV.  */
 
@@ -4739,8 +4854,118 @@ android_saf_tree_rename (struct android_vnode *src,
          directory to the other, and possibly then recreated under a
          new name.  */
 
-      errno = EXDEV; /* TODO */
-      return -1;
+      /* The names of the source and destination directories will have
+        to be copied to path.  */
+
+      if (last - vp->name >= PATH_MAX
+         || dst_last - vdst->name >= PATH_MAX)
+       {
+         errno = ENAMETOOLONG;
+         return -1;
+       }
+
+      fill = mempcpy (path, vp->name, last - vp->name);
+      *fill = '\0';
+
+      /* If vdst doesn't already exist, its document_id field is
+        already the name of its parent directory.  */
+
+      if (dst->type == ANDROID_VNODE_SAF_NEW)
+       {
+         /* First, move the document.  This will update
+            VP->document_id if it changes.  */
+
+         if (android_saf_move_document (vp->tree_uri,
+                                        &vp->document_id,
+                                        path,
+                                        vdst->document_id))
+           return -1;
+
+         fill = mempcpy (path, vdst->name, dst_last - vdst->name);
+         *fill = '\0';
+
+         /* Next, rename the document, if its display name differs
+            from that of the source.  */
+
+         if (strcmp (dst_last + 1, last + 1)
+             /* By now vp->document_id is already in the destination
+                directory.  */
+             && android_saf_rename_document (vp->tree_uri,
+                                             vp->document_id,
+                                             path,
+                                             dst_last + 1))
+           return -1;
+
+         return 0;
+       }
+
+      /* Retrieve the ID designating the destination document's parent
+        directory.  */
+
+      fill = mempcpy (path1, vdst->name, dst_last - vdst->name);
+      *fill = '\0';
+
+      rc = android_document_id_from_name (vp->tree_uri,
+                                         path1, &dst_id);
+
+      if (rc != 1)
+       {
+         /* This file is either not a directory or nonexistent.  */
+
+         switch (rc)
+           {
+           case 0:
+             errno = ENOTDIR;
+             goto error;
+
+           case -1:
+             /* dst_id is not set here, as the penultimate component
+                also couldn't be located.  */
+             errno = ENOENT;
+             return -1;
+
+           case -2:
+             errno = ENOENT;
+             goto error;
+
+           default:
+             emacs_abort ();
+           }
+       }
+
+      /* vdst already exists, so it needs to be deleted first.  */
+
+      if (android_saf_delete_document (vdst->tree_uri,
+                                      vdst->document_id,
+                                      vdst->name))
+        goto error;
+
+      /* First, move the document.  This will update
+        VP->document_id if it changes.  */
+
+      if (android_saf_move_document (vp->tree_uri,
+                                    &vp->document_id,
+                                    path, dst_id))
+        goto error;
+
+      /* Next, rename the document, if its display name differs from
+        that of the source.  */
+
+      if (strcmp (dst_last + 1, last + 1)
+         /* By now vp->document_id is already in the destination
+            directory.  */
+         && android_saf_rename_document (vp->tree_uri,
+                                         vp->document_id,
+                                         path1,
+                                         dst_last + 1))
+       goto error;
+
+      xfree (dst_id);
+      return 0;
+
+    error:
+      xfree (dst_id);
+      return 1;
     }
 
   /* Otherwise, do this simple rename.  The name of the parent