mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2025-01-05 11:45:45 +00:00
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.
This commit is contained in:
parent
2ad50c7ff5
commit
5a8130ab96
@ -1713,26 +1713,59 @@ In addition, arbitrary runtime exceptions (such as
|
||||
tree = Uri.parse (uri);
|
||||
uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
|
||||
|
||||
try
|
||||
if (DocumentsContract.renameDocument (resolver, uriObject,
|
||||
name)
|
||||
!= null)
|
||||
{
|
||||
if (DocumentsContract.renameDocument (resolver, uriObject,
|
||||
name)
|
||||
!= null)
|
||||
{
|
||||
/* Invalidate the cache. */
|
||||
if (storageThread != null)
|
||||
storageThread.postInvalidateCacheDir (tree, docId,
|
||||
name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
;;
|
||||
/* 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);
|
||||
}
|
||||
};
|
||||
|
@ -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)))))
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
237
src/androidvfs.c
237
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
|
||||
|
Loading…
Reference in New Issue
Block a user