From: Po Lu Date: Sun, 30 Jul 2023 05:39:27 +0000 (+0800) Subject: Partially implement rename operations on SAF files X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=37f68e8696200895832ee1f18b0cd1c0998bb207;p=emacs.git Partially implement rename operations on SAF files * 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. --- diff --git a/java/org/gnu/emacs/EmacsSafThread.java b/java/org/gnu/emacs/EmacsSafThread.java index b0d014ffe94..007ea9acfbd 100644 --- a/java/org/gnu/emacs/EmacsSafThread.java +++ b/java/org/gnu/emacs/EmacsSafThread.java @@ -134,7 +134,7 @@ public final class EmacsSafThread extends HandlerThread } - private final class CacheToplevel + private static final class CacheToplevel { /* Map between document names and children. */ HashMap 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 children; + String[] components; + CacheEntry entry; + DocIdEntry idEntry; + Iterator 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; + } + } + } + }); + } + /* ``Prototypes'' for nested functions that are run within the SAF diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 5186dec974a..07e585ad37c 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -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; + } }; diff --git a/src/android.c b/src/android.c index a75193e5edd..0bdbc4e0c4b 100644 --- a/src/android.c +++ b/src/android.c @@ -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 } diff --git a/src/android.h b/src/android.h index fd391fa6435..591f1a1e43c 100644 --- a/src/android.h +++ b/src/android.h @@ -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; diff --git a/src/androidvfs.c b/src/androidvfs.c index c83d3a14b50..c529e1fb30f 100644 --- a/src/androidvfs.c +++ b/src/androidvfs.c @@ -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; +} + /* 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