emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android 5a8130ab967: Implement cross-directory SAF rename operat


From: Po Lu
Subject: feature/android 5a8130ab967: Implement cross-directory SAF rename operations
Date: Sun, 30 Jul 2023 22:53:03 -0400 (EDT)

branch: feature/android
commit 5a8130ab967cb296d028539d10c749ee35f62e6a
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Implement cross-directory SAF rename operations
    
    * 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 |  67 +++++++---
 lisp/subr.el                         |   9 +-
 src/android.c                        |  27 ++++
 src/android.h                        |   2 +
 src/androidvfs.c                     | 237 ++++++++++++++++++++++++++++++++++-
 5 files changed, 314 insertions(+), 28 deletions(-)

diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
index 07e585ad37c..e714f75fdf2 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -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);
+  }
 };
diff --git a/lisp/subr.el b/lisp/subr.el
index 4346f99fa38..36aeeabea47 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -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)))))
diff --git a/src/android.c b/src/android.c
index 0bdbc4e0c4b..8c0232a51f8 100644
--- a/src/android.c
+++ b/src/android.c
@@ -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
diff --git a/src/android.h b/src/android.h
index 591f1a1e43c..8440fb9bc75 100644
--- a/src/android.h
+++ b/src/android.h
@@ -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;
diff --git a/src/androidvfs.c b/src/androidvfs.c
index c529e1fb30f..9acc8f2b139 100644
--- a/src/androidvfs.c
+++ b/src/androidvfs.c
@@ -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;
+}
+
 
 
 /* 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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]