[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
feature/android 37f68e86962: Partially implement rename operations on SA
From: |
Po Lu |
Subject: |
feature/android 37f68e86962: Partially implement rename operations on SAF files |
Date: |
Sun, 30 Jul 2023 01:42:52 -0400 (EDT) |
branch: feature/android
commit 37f68e8696200895832ee1f18b0cd1c0998bb207
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>
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.
---
java/org/gnu/emacs/EmacsSafThread.java | 78 +++++++++++++-
java/org/gnu/emacs/EmacsService.java | 37 +++++++
src/android.c | 3 +
src/android.h | 1 +
src/androidvfs.c | 179 ++++++++++++++++++++++++++++++++-
5 files changed, 293 insertions(+), 5 deletions(-)
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<String, DocIdEntry> 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<String, DocIdEntry> children;
+ String[] components;
+ CacheEntry entry;
+ DocIdEntry idEntry;
+ Iterator<DocIdEntry> 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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- feature/android 37f68e86962: Partially implement rename operations on SAF files,
Po Lu <=