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.
This commit is contained in:
parent
7ce7a004f6
commit
37f68e8696
5 changed files with 293 additions and 5 deletions
|
@ -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 @@ private final class DocIdEntry
|
|||
}
|
||||
};
|
||||
|
||||
private final class CacheEntry
|
||||
private static final class CacheEntry
|
||||
{
|
||||
/* The type of this document. */
|
||||
String type;
|
||||
|
@ -545,6 +545,80 @@ private final class CacheEntry
|
|||
});
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
|
|
@ -1698,4 +1698,41 @@ In addition, arbitrary runtime exceptions (such as
|
|||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
179
src/androidvfs.c
179
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
|
||||
|
|
Loading…
Add table
Reference in a new issue