[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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- feature/android 5a8130ab967: Implement cross-directory SAF rename operations,
Po Lu <=