Initial commit
This commit is contained in:
131
backends/fs/android/android-fs-factory.cpp
Normal file
131
backends/fs/android/android-fs-factory.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
#include "backends/fs/android/android-fs-factory.h"
|
||||
#include "backends/fs/android/android-posix-fs.h"
|
||||
#include "backends/fs/android/android-saf-fs.h"
|
||||
|
||||
namespace Common {
|
||||
DECLARE_SINGLETON(AndroidFilesystemFactory);
|
||||
}
|
||||
|
||||
AndroidFilesystemFactory::AndroidFilesystemFactory() : _withSAF(false), _config(this) {
|
||||
}
|
||||
|
||||
void AndroidFilesystemFactory::initSAF() {
|
||||
_withSAF = true;
|
||||
AndroidSAFFilesystemNode::initJNI();
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidFilesystemFactory::makeRootFileNode() const {
|
||||
return new AndroidPOSIXFilesystemNode(_config);
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidFilesystemFactory::makeCurrentDirectoryFileNode() const {
|
||||
// As current working directory can point outside of our data don't take any risk
|
||||
return makeRootFileNode();
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidFilesystemFactory::makeFileNodePath(const Common::String &path) const {
|
||||
if (path.empty() || path.equals("/")) {
|
||||
return makeRootFileNode();
|
||||
}
|
||||
|
||||
// If SAF works, it was a SAF URL
|
||||
if (_withSAF) {
|
||||
// Accept /saf as it can be used to create the tree in DumpFile
|
||||
if (path == AddSAFFakeNode::SAF_ADD_FAKE_PATH) {
|
||||
// Not a SAF mount point
|
||||
return new AddSAFFakeNode(true);
|
||||
}
|
||||
|
||||
AbstractFSNode *node = AndroidSAFFilesystemNode::makeFromPath(path);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return new AndroidPOSIXFilesystemNode(path, _config);
|
||||
}
|
||||
|
||||
AndroidFilesystemFactory::Config::Config(const AndroidFilesystemFactory *factory) : _factory(factory),
|
||||
_storages(JNI::getAllStorageLocations()) {
|
||||
}
|
||||
|
||||
void AndroidFilesystemFactory::getSAFTrees(AbstractFSList &list, bool allowSAFadd) const {
|
||||
if (!_withSAF) {
|
||||
// Nothing if no SAF
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Array<jobject> trees = JNI::getSAFTrees();
|
||||
|
||||
list.reserve(trees.size() + (allowSAFadd ? 1 : 0));
|
||||
|
||||
for (Common::Array<jobject>::iterator it = trees.begin(); it != trees.end(); it++) {
|
||||
AbstractFSNode *node = AndroidSAFFilesystemNode::makeFromTree(*it);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list.push_back(node);
|
||||
}
|
||||
|
||||
if (allowSAFadd) {
|
||||
list.push_back(new AddSAFFakeNode(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool AndroidFilesystemFactory::Config::getDrives(AbstractFSList &list, bool hidden) const {
|
||||
// For SAF
|
||||
_factory->getSAFTrees(list, true);
|
||||
|
||||
list.reserve(list.size() + _storages.size() / 2);
|
||||
|
||||
// For old POSIX way
|
||||
for (Common::Array<Common::String>::const_iterator it = _storages.begin(); it != _storages.end(); ++it) {
|
||||
const Common::String &driveName = *it;
|
||||
++it;
|
||||
const Common::String &drivePath = *it;
|
||||
|
||||
AndroidPOSIXFilesystemNode *node = new AndroidPOSIXFilesystemNode(drivePath, *this);
|
||||
node->_displayName = driveName;
|
||||
|
||||
list.push_back(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AndroidFilesystemFactory::Config::isDrive(const Common::String &path) const {
|
||||
// This function is called from DrivePOSIXFilesystemNode::isDrive
|
||||
// DrivePOSIXFilesystemNode is only used for POSIX code so no need to look for SAF
|
||||
|
||||
for (Common::Array<Common::String>::const_iterator it = _storages.begin(); it != _storages.end(); it++) {
|
||||
++it;
|
||||
if (*it == path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
63
backends/fs/android/android-fs-factory.h
Normal file
63
backends/fs/android/android-fs-factory.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_FILESYSTEM_FACTORY_H
|
||||
#define ANDROID_FILESYSTEM_FACTORY_H
|
||||
|
||||
#include "backends/fs/fs-factory.h"
|
||||
#include "common/singleton.h"
|
||||
#include "backends/fs/posix-drives/posix-drives-fs.h"
|
||||
|
||||
/**
|
||||
* Creates AndroidFilesystemNode objects.
|
||||
*
|
||||
* Parts of this class are documented in the base interface class, FilesystemFactory.
|
||||
*/
|
||||
class AndroidFilesystemFactory final : public FilesystemFactory,
|
||||
public Common::Singleton<AndroidFilesystemFactory> {
|
||||
friend class Common::Singleton<SingletonBaseType>;
|
||||
protected:
|
||||
AndroidFilesystemFactory();
|
||||
public:
|
||||
AbstractFSNode *makeRootFileNode() const override;
|
||||
AbstractFSNode *makeCurrentDirectoryFileNode() const override;
|
||||
AbstractFSNode *makeFileNodePath(const Common::String &path) const override;
|
||||
|
||||
void initSAF();
|
||||
bool hasSAF() const { return _withSAF; }
|
||||
void getSAFTrees(AbstractFSList &list, bool allowSAFadd) const;
|
||||
private:
|
||||
struct Config : public DrivePOSIXFilesystemNode::Config {
|
||||
Config(const AndroidFilesystemFactory *factory);
|
||||
|
||||
bool getDrives(AbstractFSList &list, bool hidden) const override;
|
||||
bool isDrive(const Common::String &path) const override;
|
||||
|
||||
private:
|
||||
const AndroidFilesystemFactory *_factory;
|
||||
Common::Array<Common::String> _storages;
|
||||
};
|
||||
|
||||
bool _withSAF;
|
||||
Config _config;
|
||||
};
|
||||
|
||||
#endif /*ANDROID_FILESYSTEM_FACTORY_H*/
|
||||
35
backends/fs/android/android-fs.h
Normal file
35
backends/fs/android/android-fs.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_FILESYSTEM_H
|
||||
#define ANDROID_FILESYSTEM_H
|
||||
|
||||
/**
|
||||
* Common interface for Android filesystem types: SAF and POSIX
|
||||
* Currently, only a remove function to delete files.
|
||||
*/
|
||||
class AndroidFSNode {
|
||||
public:
|
||||
virtual ~AndroidFSNode() {}
|
||||
virtual int remove() = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
44
backends/fs/android/android-posix-fs.cpp
Normal file
44
backends/fs/android/android-posix-fs.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// For remove()
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "backends/fs/android/android-fs-factory.h"
|
||||
#include "backends/fs/android/android-posix-fs.h"
|
||||
#include "backends/fs/android/android-saf-fs.h"
|
||||
|
||||
AbstractFSNode *AndroidPOSIXFilesystemNode::makeNode() const {
|
||||
return new AndroidPOSIXFilesystemNode(_config);
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidPOSIXFilesystemNode::makeNode(const Common::String &path) const {
|
||||
return AndroidFilesystemFactory::instance().makeFileNodePath(path);
|
||||
}
|
||||
|
||||
int AndroidPOSIXFilesystemNode::remove() {
|
||||
if (::remove(_path.c_str()) != 0)
|
||||
return errno;
|
||||
|
||||
setFlags();
|
||||
return 0;
|
||||
}
|
||||
45
backends/fs/android/android-posix-fs.h
Normal file
45
backends/fs/android/android-posix-fs.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_FILESYSTEM_ROOT_H
|
||||
#define ANDROID_FILESYSTEM_ROOT_H
|
||||
|
||||
#include "backends/fs/posix-drives/posix-drives-fs.h"
|
||||
|
||||
#include "backends/fs/android/android-fs.h"
|
||||
|
||||
class AndroidPOSIXFilesystemNode : public DrivePOSIXFilesystemNode, public AndroidFSNode {
|
||||
// To let the factory redefine the displayed name
|
||||
friend class AndroidFilesystemFactory;
|
||||
protected:
|
||||
AbstractFSNode *makeNode() const override;
|
||||
AbstractFSNode *makeNode(const Common::String &path) const override;
|
||||
|
||||
int remove() override;
|
||||
|
||||
public:
|
||||
AndroidPOSIXFilesystemNode(const Common::String &path, const Config &config)
|
||||
: DrivePOSIXFilesystemNode(path, config) { }
|
||||
AndroidPOSIXFilesystemNode(const Config &config)
|
||||
: DrivePOSIXFilesystemNode(config) { _path = "/"; }
|
||||
};
|
||||
|
||||
#endif
|
||||
930
backends/fs/android/android-saf-fs.cpp
Normal file
930
backends/fs/android/android-saf-fs.cpp
Normal file
@@ -0,0 +1,930 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Allow use of stuff in <time.h> and abort()
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_abort
|
||||
|
||||
// Disable printf override in common/forbidden.h to avoid
|
||||
// clashes with log.h from the Android SDK.
|
||||
// That header file uses
|
||||
// __attribute__ ((format(printf, 3, 4)))
|
||||
// which gets messed up by our override mechanism; this could
|
||||
// be avoided by either changing the Android SDK to use the equally
|
||||
// legal and valid
|
||||
// __attribute__ ((format(__printf__, 3, 4)))
|
||||
// or by refining our printf override to use a varadic macro
|
||||
// (which then wouldn't be portable, though).
|
||||
// Anyway, for now we just disable the printf override globally
|
||||
// for the Android port
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
|
||||
|
||||
// Allow calling of fdopen
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
|
||||
|
||||
// Allow calling of close system call
|
||||
#include <unistd.h>
|
||||
#include <errno.h> // For remove error codes
|
||||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
#include "backends/fs/android/android-fs-factory.h"
|
||||
#include "backends/fs/android/android-saf-fs.h"
|
||||
|
||||
#include "backends/fs/posix/posix-iostream.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/util.h"
|
||||
|
||||
jclass AndroidSAFFilesystemNode::_CLS_SAFFSTree = nullptr;
|
||||
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_addNodeRef = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_decNodeRef = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_refToNode = 0;
|
||||
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_getTreeId = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_pathToNode = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_getChildren = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_getChild = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_createDirectory = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_createFile = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_createReadStream = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_createWriteStream = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_removeNode = 0;
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_removeTree = 0;
|
||||
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__treeName = 0;
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__root = 0;
|
||||
|
||||
jmethodID AndroidSAFFilesystemNode::_MID_addRef = 0;
|
||||
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__parent = 0;
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__path = 0;
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__documentId = 0;
|
||||
jfieldID AndroidSAFFilesystemNode::_FID__flags = 0;
|
||||
|
||||
bool AndroidSAFFilesystemNode::_JNIinit = false;
|
||||
|
||||
const char AndroidSAFFilesystemNode::SAF_MOUNT_POINT[] = "/saf/";
|
||||
|
||||
void AndroidSAFFilesystemNode::initJNI() {
|
||||
if (_JNIinit) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
// We can't call error here as the backend is not built yet
|
||||
#define FIND_STATIC_METHOD(prefix, name, signature) do { \
|
||||
_MID_ ## prefix ## name = env->GetStaticMethodID(cls, #name, signature); \
|
||||
if (_MID_ ## prefix ## name == 0) { \
|
||||
LOGE("Can't find method ID " #name); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define FIND_METHOD(prefix, name, signature) do { \
|
||||
_MID_ ## prefix ## name = env->GetMethodID(cls, #name, signature); \
|
||||
if (_MID_ ## prefix ## name == 0) { \
|
||||
LOGE("Can't find method ID " #name); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define FIND_FIELD(prefix, name, signature) do { \
|
||||
_FID_ ## prefix ## name = env->GetFieldID(cls, #name, signature); \
|
||||
if (_FID_ ## prefix ## name == 0) { \
|
||||
LOGE("Can't find field ID " #name); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define SAFFSNodeSig "Lorg/scummvm/scummvm/SAFFSTree$SAFFSNode;"
|
||||
|
||||
jclass cls = env->FindClass("org/scummvm/scummvm/SAFFSTree");
|
||||
_CLS_SAFFSTree = (jclass)env->NewGlobalRef(cls);
|
||||
|
||||
FIND_STATIC_METHOD(, addNodeRef, "(J)V");
|
||||
FIND_STATIC_METHOD(, decNodeRef, "(J)V");
|
||||
FIND_STATIC_METHOD(, refToNode, "(J)" SAFFSNodeSig);
|
||||
|
||||
FIND_METHOD(, getTreeId, "()Ljava/lang/String;");
|
||||
FIND_METHOD(, pathToNode, "(Ljava/lang/String;Z)" SAFFSNodeSig);
|
||||
FIND_METHOD(, getChildren, "(J)[" SAFFSNodeSig);
|
||||
FIND_METHOD(, getChild, "(JLjava/lang/String;)" SAFFSNodeSig);
|
||||
FIND_METHOD(, createDirectory, "(JLjava/lang/String;)" SAFFSNodeSig);
|
||||
FIND_METHOD(, createFile, "(JLjava/lang/String;)" SAFFSNodeSig);
|
||||
FIND_METHOD(, createReadStream, "(J)I");
|
||||
FIND_METHOD(, createWriteStream, "(J)I");
|
||||
FIND_METHOD(, removeNode, "(J)I");
|
||||
FIND_METHOD(, removeTree, "()V");
|
||||
|
||||
FIND_FIELD(, _treeName, "Ljava/lang/String;");
|
||||
FIND_FIELD(, _root, SAFFSNodeSig);
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
cls = env->FindClass("org/scummvm/scummvm/SAFFSTree$SAFFSNode");
|
||||
|
||||
FIND_METHOD(, addRef, "()J");
|
||||
|
||||
FIND_FIELD(, _parent, SAFFSNodeSig);
|
||||
FIND_FIELD(, _path, "Ljava/lang/String;");
|
||||
FIND_FIELD(, _documentId, "Ljava/lang/String;");
|
||||
FIND_FIELD(, _flags, "I");
|
||||
|
||||
env->DeleteLocalRef(cls);
|
||||
#undef SAFFSNodeSig
|
||||
#undef FIND_FIELD
|
||||
#undef FIND_METHOD
|
||||
#undef FIND_STATIC_METHOD
|
||||
|
||||
_JNIinit = true;
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::GlobalRef::Deleter::operator()(_jobject *obj) {
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
env->DeleteGlobalRef((jobject)obj);
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::NodeRef::reset() {
|
||||
if (_ref == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
env->CallStaticVoidMethod(_CLS_SAFFSTree, _MID_decNodeRef, _ref);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::decNodeRef failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
_ref = 0;
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::NodeRef::reset(const NodeRef &r) {
|
||||
if (_ref == 0 && r._ref == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
if (_ref) {
|
||||
env->CallStaticVoidMethod(_CLS_SAFFSTree, _MID_decNodeRef, _ref);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::decNodeRef failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
_ref = r._ref;
|
||||
if (!_ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
env->CallStaticVoidMethod(_CLS_SAFFSTree, _MID_addNodeRef, _ref);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::addNodeRef failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
_ref = 0;
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::NodeRef::reset(JNIEnv *env, jobject node) {
|
||||
if (_ref == 0 && node == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ref) {
|
||||
env->CallStaticVoidMethod(_CLS_SAFFSTree, _MID_decNodeRef, _ref);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::decNodeRef failed");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
if (node == nullptr) {
|
||||
_ref = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_ref = env->CallLongMethod(node, _MID_addRef);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSNode::addRef failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
_ref = 0;
|
||||
abort();
|
||||
}
|
||||
|
||||
assert(_ref != 0);
|
||||
}
|
||||
|
||||
jobject AndroidSAFFilesystemNode::NodeRef::localRef(JNIEnv *env) const {
|
||||
if (_ref == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jobject localRef = env->CallStaticObjectMethod(_CLS_SAFFSTree, _MID_refToNode, _ref);
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::refToNode failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return localRef;
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode *AndroidSAFFilesystemNode::makeFromPath(const Common::String &path) {
|
||||
if (!path.hasPrefix(SAF_MOUNT_POINT)) {
|
||||
// Not a SAF mount point
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Path is in the form /saf/<treeid>/<path>
|
||||
size_t pos = path.findFirstOf('/', sizeof(SAF_MOUNT_POINT) - 1);
|
||||
Common::String treeId;
|
||||
Common::String realPath;
|
||||
if (pos == Common::String::npos) {
|
||||
treeId = path.substr(sizeof(SAF_MOUNT_POINT) - 1);
|
||||
} else {
|
||||
treeId = path.substr(sizeof(SAF_MOUNT_POINT) - 1, pos - sizeof(SAF_MOUNT_POINT) + 1);
|
||||
realPath = path.substr(pos);
|
||||
}
|
||||
|
||||
jobject safTree = JNI::findSAFTree(treeId);
|
||||
if (!safTree) {
|
||||
LOGW("AndroidSAFFilesystemNode::makeFromPath: tree id %s not found", treeId.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring pathObj = env->NewStringUTF(realPath.c_str());
|
||||
|
||||
jobject node = env->CallObjectMethod(safTree, _MID_pathToNode, pathObj, false);
|
||||
|
||||
env->DeleteLocalRef(pathObj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::pathToNode failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
env->DeleteLocalRef(safTree);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
AndroidSAFFilesystemNode *ret = new AndroidSAFFilesystemNode(GlobalRef(env, safTree), node);
|
||||
|
||||
env->DeleteLocalRef(node);
|
||||
env->DeleteLocalRef(safTree);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Node doesn't exist: we will try to make a node from the parent and
|
||||
// if it works we will create a non-existent node
|
||||
|
||||
pos = realPath.findLastOf('/');
|
||||
if (pos == Common::String::npos || pos == 0) {
|
||||
// No / in path or at root, no parent and we have a tree: it's all good
|
||||
if (pos == 0) {
|
||||
realPath = realPath.substr(1);
|
||||
}
|
||||
AndroidSAFFilesystemNode *parent = makeFromTree(safTree);
|
||||
AndroidSAFFilesystemNode *ret = static_cast<AndroidSAFFilesystemNode *>(parent->getChild(realPath));
|
||||
delete parent;
|
||||
|
||||
// safTree has already been released by makeFromTree
|
||||
return ret;
|
||||
}
|
||||
|
||||
Common::String baseName(realPath.substr(pos + 1));
|
||||
realPath.erase(pos);
|
||||
|
||||
pathObj = env->NewStringUTF(realPath.c_str());
|
||||
|
||||
node = env->CallObjectMethod(safTree, _MID_pathToNode, pathObj, false);
|
||||
|
||||
env->DeleteLocalRef(pathObj);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::pathToNode failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
env->DeleteLocalRef(safTree);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
AndroidSAFFilesystemNode *parent = new AndroidSAFFilesystemNode(GlobalRef(env, safTree), node);
|
||||
env->DeleteLocalRef(node);
|
||||
env->DeleteLocalRef(safTree);
|
||||
|
||||
AndroidSAFFilesystemNode *ret = static_cast<AndroidSAFFilesystemNode *>(parent->getChild(baseName));
|
||||
delete parent;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(safTree);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode *AndroidSAFFilesystemNode::makeFromTree(jobject safTree) {
|
||||
assert(safTree);
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jobject node = env->GetObjectField(safTree, _FID__root);
|
||||
if (!node) {
|
||||
env->DeleteLocalRef(safTree);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode *ret = new AndroidSAFFilesystemNode(GlobalRef(env, safTree), node);
|
||||
|
||||
env->DeleteLocalRef(node);
|
||||
env->DeleteLocalRef(safTree);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode::AndroidSAFFilesystemNode(const GlobalRef &safTree, jobject safNode) :
|
||||
_flags(0) {
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
_safTree = safTree;
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
_safNode.reset(env, safNode);
|
||||
cacheData(env, safNode);
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode::AndroidSAFFilesystemNode(const GlobalRef &safTree, jobject safParent,
|
||||
const Common::String &path, const Common::String &name) : _flags(0) {
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
_safTree = safTree;
|
||||
_safParent.reset(env, safParent);
|
||||
|
||||
// In this case _path is the parent
|
||||
_path = path;
|
||||
_newName = name;
|
||||
}
|
||||
|
||||
AndroidSAFFilesystemNode::AndroidSAFFilesystemNode(const GlobalRef &safTree,
|
||||
const NodeRef &safParent, const Common::String &path,
|
||||
const Common::String &name) : _flags(0) {
|
||||
|
||||
_safTree = safTree;
|
||||
_safParent = safParent;
|
||||
|
||||
// In this case _path is the parent
|
||||
_path = path;
|
||||
_newName = name;
|
||||
}
|
||||
|
||||
Common::String AndroidSAFFilesystemNode::getName() const {
|
||||
if (!_safNode || !_safParent) {
|
||||
// _newName is for non-existent paths or root node pretty name
|
||||
return _newName;
|
||||
}
|
||||
|
||||
return lastPathComponent(_path, '/');
|
||||
}
|
||||
|
||||
Common::String AndroidSAFFilesystemNode::getPath() const {
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
if (_safNode) {
|
||||
return _path;
|
||||
}
|
||||
|
||||
// When no node, it means _path is the parent node
|
||||
return _path + "/" + _newName;
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidSAFFilesystemNode::getChild(const Common::String &n) const {
|
||||
assert(_safTree != nullptr);
|
||||
assert(_safNode);
|
||||
|
||||
// Make sure the string contains no slashes
|
||||
assert(!n.contains('/'));
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring name = env->NewStringUTF(n.c_str());
|
||||
|
||||
jobject child = env->CallObjectMethod(_safTree, _MID_getChild, _safNode.get(), name);
|
||||
|
||||
env->DeleteLocalRef(name);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::getChild failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (child) {
|
||||
AndroidSAFFilesystemNode *ret = new AndroidSAFFilesystemNode(_safTree, child);
|
||||
env->DeleteLocalRef(child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return new AndroidSAFFilesystemNode(_safTree, _safNode, _path, n);
|
||||
}
|
||||
|
||||
bool AndroidSAFFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode,
|
||||
bool hidden) const {
|
||||
assert(_flags & DIRECTORY);
|
||||
|
||||
assert(_safTree != nullptr);
|
||||
if (!_safNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jobjectArray array =
|
||||
(jobjectArray)env->CallObjectMethod(_safTree, _MID_getChildren, _safNode.get());
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::getChildren failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array) {
|
||||
// Fetching children failed: a log error has already been produced in Java code
|
||||
return false;
|
||||
}
|
||||
|
||||
myList.clear();
|
||||
|
||||
jsize size = env->GetArrayLength(array);
|
||||
myList.reserve(size);
|
||||
|
||||
for (jsize i = 0; i < size; ++i) {
|
||||
jobject node = env->GetObjectArrayElement(array, i);
|
||||
|
||||
myList.push_back(new AndroidSAFFilesystemNode(_safTree, node));
|
||||
|
||||
env->DeleteLocalRef(node);
|
||||
}
|
||||
env->DeleteLocalRef(array);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractFSNode *AndroidSAFFilesystemNode::getParent() const {
|
||||
assert(_safTree != nullptr);
|
||||
// No need to check for _safNode: if node doesn't exist yet parent is its parent
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
if (!_safParent) {
|
||||
return AndroidFilesystemFactory::instance().makeRootFileNode();
|
||||
}
|
||||
|
||||
jobject parent = _safParent.localRef(env);
|
||||
assert(parent);
|
||||
|
||||
AndroidSAFFilesystemNode *ret = new AndroidSAFFilesystemNode(_safTree, parent);
|
||||
env->DeleteLocalRef(parent);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *AndroidSAFFilesystemNode::createReadStream() {
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
if (!_safNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jint fd = env->CallIntMethod(_safTree, _MID_createReadStream, _safNode.get());
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::createReadStream failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fd == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FILE *f = fdopen(fd, "r");
|
||||
if (!f) {
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new PosixIoStream(f);
|
||||
}
|
||||
|
||||
Common::SeekableWriteStream *AndroidSAFFilesystemNode::createWriteStream(bool atomic) {
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
if (!_safNode) {
|
||||
assert(_safParent);
|
||||
jstring name = env->NewStringUTF(_newName.c_str());
|
||||
|
||||
// TODO: Add atomic support if possible
|
||||
jobject child = env->CallObjectMethod(_safTree, _MID_createFile, _safParent.get(), name);
|
||||
|
||||
env->DeleteLocalRef(name);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::createFile failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!child) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_safNode.reset(env, child);
|
||||
cacheData(env, child);
|
||||
|
||||
env->DeleteLocalRef(child);
|
||||
}
|
||||
|
||||
jint fd = env->CallIntMethod(_safTree, _MID_createWriteStream, _safNode.get());
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::createWriteStream failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fd == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FILE *f = fdopen(fd, "w");
|
||||
if (!f) {
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new PosixIoStream(f);
|
||||
}
|
||||
|
||||
bool AndroidSAFFilesystemNode::createDirectory() {
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
if (_safNode) {
|
||||
return _flags & DIRECTORY;
|
||||
}
|
||||
|
||||
assert(_safParent);
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jstring name = env->NewStringUTF(_newName.c_str());
|
||||
|
||||
jobject child = env->CallObjectMethod(_safTree, _MID_createDirectory, _safParent.get(), name);
|
||||
|
||||
env->DeleteLocalRef(name);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::createDirectory failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_safNode.reset(env, child);
|
||||
|
||||
cacheData(env, child);
|
||||
|
||||
env->DeleteLocalRef(child);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int AndroidSAFFilesystemNode::remove() {
|
||||
assert(_safTree != nullptr);
|
||||
|
||||
if (!_safNode) {
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
if (!_safParent) {
|
||||
// It's the root of the tree: we can't delete it
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
if (isDirectory()) {
|
||||
// Don't delete folders (yet?)
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jint result = env->CallIntMethod(_safTree, _MID_removeNode, _safNode.get());
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::removeNode failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return EIO;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
_safNode.reset();
|
||||
|
||||
// Create the parent node to fetch informations needed to make us a non-existent node
|
||||
|
||||
jobject jparent = _safParent.localRef(env);
|
||||
if (!jparent)
|
||||
return EIO;
|
||||
|
||||
AndroidSAFFilesystemNode *parent = new AndroidSAFFilesystemNode(_safTree, jparent);
|
||||
env->DeleteLocalRef(jparent);
|
||||
|
||||
size_t pos = _path.findLastOf('/');
|
||||
if (pos == Common::String::npos) {
|
||||
_newName = _path;
|
||||
} else {
|
||||
_newName = _path.substr(pos + 1);
|
||||
}
|
||||
_path = parent->_path;
|
||||
|
||||
delete parent;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::removeTree() {
|
||||
assert(!_safParent);
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
env->CallVoidMethod(_safTree, _MID_removeTree);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("SAFFSTree::removeTree failed");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSAFFilesystemNode::cacheData(JNIEnv *env, jobject node) {
|
||||
_flags = env->GetIntField(node, _FID__flags);
|
||||
|
||||
jobject safParent = env->GetObjectField(node, _FID__parent);
|
||||
_safParent.reset(env, safParent);
|
||||
|
||||
if (!_safParent) {
|
||||
jstring nameObj = (jstring)env->GetObjectField(_safTree, _FID__treeName);
|
||||
const char *nameP = env->GetStringUTFChars(nameObj, 0);
|
||||
if (nameP != 0) {
|
||||
_newName = Common::String(nameP);
|
||||
env->ReleaseStringUTFChars(nameObj, nameP);
|
||||
}
|
||||
env->DeleteLocalRef(nameObj);
|
||||
}
|
||||
|
||||
Common::String workingPath;
|
||||
|
||||
jstring pathObj = (jstring)env->GetObjectField(node, _FID__path);
|
||||
const char *path = env->GetStringUTFChars(pathObj, 0);
|
||||
if (path == nullptr) {
|
||||
env->DeleteLocalRef(pathObj);
|
||||
error("SAFFSNode::_path is null");
|
||||
return;
|
||||
}
|
||||
workingPath = Common::String(path);
|
||||
env->ReleaseStringUTFChars(pathObj, path);
|
||||
env->DeleteLocalRef(pathObj);
|
||||
|
||||
jstring idObj = (jstring)env->CallObjectMethod(_safTree, _MID_getTreeId);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
env->ReleaseStringUTFChars(pathObj, path);
|
||||
env->DeleteLocalRef(pathObj);
|
||||
error("SAFFSTree::getTreeId failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!idObj) {
|
||||
error("SAFFSTree::getTreeId returned null");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *id = env->GetStringUTFChars(idObj, 0);
|
||||
if (id == nullptr) {
|
||||
error("Failed to get string from SAFFSTree::getTreeId");
|
||||
env->DeleteLocalRef(idObj);
|
||||
return;
|
||||
}
|
||||
|
||||
_path = Common::String(SAF_MOUNT_POINT);
|
||||
_path += id;
|
||||
_path += workingPath;
|
||||
env->ReleaseStringUTFChars(idObj, id);
|
||||
env->DeleteLocalRef(idObj);
|
||||
}
|
||||
|
||||
const char AddSAFFakeNode::SAF_ADD_FAKE_PATH[] = "/saf";
|
||||
|
||||
AddSAFFakeNode::~AddSAFFakeNode() {
|
||||
delete _proxied;
|
||||
}
|
||||
|
||||
Common::U32String AddSAFFakeNode::getDisplayName() const {
|
||||
// I18N: This is displayed in the file browser to let the user choose a new folder for Android Storage Attached Framework
|
||||
return Common::U32String::format("\x01<%s>", _("Add a new folder").c_str());
|
||||
}
|
||||
|
||||
Common::String AddSAFFakeNode::getName() const {
|
||||
return Common::String::format("\x01<%s>", _("Add a new folder").encode().c_str());
|
||||
}
|
||||
|
||||
AbstractFSNode *AddSAFFakeNode::getChild(const Common::String &name) const {
|
||||
if (_fromPath) {
|
||||
// When starting from /saf try to get the tree node
|
||||
return AndroidSAFFilesystemNode::makeFromPath(Common::String(AndroidSAFFilesystemNode::SAF_MOUNT_POINT) + name);
|
||||
}
|
||||
// We can't call getChild as it's protected
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AbstractFSNode *AddSAFFakeNode::getParent() const {
|
||||
// We are always just below the root and getParent is protected
|
||||
return AndroidFilesystemFactory::instance().makeRootFileNode();
|
||||
}
|
||||
|
||||
bool AddSAFFakeNode::exists() const {
|
||||
if (_fromPath) {
|
||||
// /saf always exists when created as a path
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
makeProxySAF();
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _proxied->exists();
|
||||
}
|
||||
|
||||
bool AddSAFFakeNode::getChildren(AbstractFSList &list, ListMode mode, bool hidden) const {
|
||||
if (_fromPath) {
|
||||
// When built from path, /saf lists all SAF node but never proposes to add one
|
||||
if (mode == Common::FSNode::kListFilesOnly) {
|
||||
// All directories
|
||||
return true;
|
||||
}
|
||||
AndroidFilesystemFactory::instance().getSAFTrees(list, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
makeProxySAF();
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _proxied->getChildren(list, mode, hidden);
|
||||
}
|
||||
|
||||
Common::String AddSAFFakeNode::getPath() const {
|
||||
if (_fromPath) {
|
||||
return SAF_ADD_FAKE_PATH;
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
makeProxySAF();
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return _proxied->getPath();
|
||||
}
|
||||
|
||||
bool AddSAFFakeNode::isReadable() const {
|
||||
if (_fromPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
makeProxySAF();
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _proxied->isReadable();
|
||||
}
|
||||
|
||||
bool AddSAFFakeNode::isWritable() const {
|
||||
if (_fromPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
makeProxySAF();
|
||||
}
|
||||
|
||||
if (!_proxied) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _proxied->isWritable();
|
||||
}
|
||||
|
||||
int AddSAFFakeNode::remove() {
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
void AddSAFFakeNode::makeProxySAF() const {
|
||||
assert(!_fromPath);
|
||||
|
||||
if (_proxied) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I18N: This may be displayed in the Android UI used to add a Storage Attach Framework authorization
|
||||
jobject saftree = JNI::getNewSAFTree(true, "", _("Choose a new folder"));
|
||||
if (!saftree) {
|
||||
return;
|
||||
}
|
||||
|
||||
_proxied = AndroidSAFFilesystemNode::makeFromTree(saftree);
|
||||
}
|
||||
273
backends/fs/android/android-saf-fs.h
Normal file
273
backends/fs/android/android-saf-fs.h
Normal file
@@ -0,0 +1,273 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SAF_FILESYSTEM_H
|
||||
#define ANDROID_SAF_FILESYSTEM_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "backends/fs/abstract-fs.h"
|
||||
#include "common/ptr.h"
|
||||
|
||||
#include "backends/fs/android/android-fs.h"
|
||||
|
||||
/**
|
||||
* Implementation of the ScummVM file system API.
|
||||
*
|
||||
* Parts of this class are documented in the base interface class, AbstractFSNode.
|
||||
*/
|
||||
class AndroidSAFFilesystemNode final : public AbstractFSNode, public AndroidFSNode {
|
||||
protected:
|
||||
/**
|
||||
* A class managing a global reference.
|
||||
*
|
||||
* This handles the reference management and avoids duplicating them in JNI.
|
||||
*/
|
||||
class GlobalRef final : public Common::SharedPtr<_jobject> {
|
||||
struct Deleter {
|
||||
void operator()(_jobject *obj);
|
||||
};
|
||||
public:
|
||||
GlobalRef() : Common::SharedPtr<_jobject>() {}
|
||||
GlobalRef(const GlobalRef &ref) : Common::SharedPtr<_jobject>(ref) {}
|
||||
GlobalRef(JNIEnv *env, jobject jobj) : Common::SharedPtr<_jobject>(jobj ? env->NewGlobalRef(jobj) : nullptr, Deleter()) {
|
||||
// Make sure NewGlobalRef succeeded
|
||||
assert((jobj == nullptr) == (get() == nullptr));
|
||||
}
|
||||
GlobalRef &operator=(const GlobalRef &r) {
|
||||
Common::SharedPtr<_jobject>::reset(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator jobject() {
|
||||
return Common::SharedPtr<_jobject>::get();
|
||||
}
|
||||
operator jobject() const {
|
||||
return Common::SharedPtr<_jobject>::get();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A class managing our SAFFSNode references.
|
||||
*
|
||||
* Reference counting is managed by SAFFSNode in Java and this class uses
|
||||
* RAII to call the reference counting methods at the appropriate time.
|
||||
*/
|
||||
class NodeRef final {
|
||||
private:
|
||||
jlong _ref;
|
||||
|
||||
public:
|
||||
NodeRef() : _ref(0) {}
|
||||
~NodeRef() { reset(); }
|
||||
NodeRef(const NodeRef &r) { reset(r); }
|
||||
NodeRef(JNIEnv *env, jobject node) { reset(env, node); }
|
||||
|
||||
void reset();
|
||||
void reset(const NodeRef &r);
|
||||
void reset(JNIEnv *env, jobject node);
|
||||
|
||||
NodeRef &operator=(const NodeRef &r) {
|
||||
reset(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const NodeRef &r) const {
|
||||
return _ref == r._ref;
|
||||
}
|
||||
|
||||
bool operator!=(const NodeRef &r) const {
|
||||
return _ref != r._ref;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return _ref != 0;
|
||||
}
|
||||
|
||||
jlong get() const { return _ref; }
|
||||
jobject localRef(JNIEnv *env) const;
|
||||
};
|
||||
|
||||
// SAFFSTree
|
||||
static jclass _CLS_SAFFSTree;
|
||||
|
||||
static jmethodID _MID_addNodeRef;
|
||||
static jmethodID _MID_decNodeRef;
|
||||
static jmethodID _MID_refToNode;
|
||||
static jmethodID _MID_getTreeId;
|
||||
static jmethodID _MID_pathToNode;
|
||||
static jmethodID _MID_getChildren;
|
||||
static jmethodID _MID_getChild;
|
||||
static jmethodID _MID_createDirectory;
|
||||
static jmethodID _MID_createFile;
|
||||
static jmethodID _MID_createReadStream;
|
||||
static jmethodID _MID_createWriteStream;
|
||||
static jmethodID _MID_removeNode;
|
||||
static jmethodID _MID_removeTree;
|
||||
|
||||
static jfieldID _FID__treeName;
|
||||
static jfieldID _FID__root;
|
||||
|
||||
// SAFFSNode
|
||||
static jmethodID _MID_addRef;
|
||||
|
||||
static jfieldID _FID__parent;
|
||||
static jfieldID _FID__path;
|
||||
static jfieldID _FID__documentId;
|
||||
static jfieldID _FID__flags;
|
||||
|
||||
static bool _JNIinit;
|
||||
|
||||
protected:
|
||||
static const int DIRECTORY = 1;
|
||||
static const int WRITABLE = 2;
|
||||
static const int READABLE = 4;
|
||||
|
||||
GlobalRef _safTree;
|
||||
// When 0, node doesn't exist yet
|
||||
// In this case _path is the parent path, _newName the node name and _safParent the parent SAF object
|
||||
NodeRef _safNode;
|
||||
|
||||
Common::String _path;
|
||||
int _flags;
|
||||
NodeRef _safParent;
|
||||
|
||||
// Used when creating a new node
|
||||
// Also used for root node to store its pretty name
|
||||
Common::String _newName;
|
||||
|
||||
public:
|
||||
static const char SAF_MOUNT_POINT[];
|
||||
|
||||
/**
|
||||
* Init JNI parts related to SAF
|
||||
* Called by AndroidFilesystemFactory::AndroidFilesystemFactory()
|
||||
*/
|
||||
static void initJNI();
|
||||
|
||||
/**
|
||||
* Creates an AndroidSAFFilesystemNode given its absolute path
|
||||
*
|
||||
* @param path Path of the node
|
||||
*/
|
||||
static AndroidSAFFilesystemNode *makeFromPath(const Common::String &path);
|
||||
|
||||
/**
|
||||
* Creates an AndroidSAFFilesystemNode given its tree object
|
||||
* @param safTree SAF root in Java side. Must be a local reference and must not be used after this call.
|
||||
*
|
||||
*/
|
||||
static AndroidSAFFilesystemNode *makeFromTree(jobject safTree);
|
||||
|
||||
bool exists() const override { return (bool)_safNode; }
|
||||
Common::U32String getDisplayName() const override { return Common::U32String(getName()); }
|
||||
Common::String getName() const override;
|
||||
Common::String getPath() const override;
|
||||
bool isDirectory() const override { return _flags & DIRECTORY; }
|
||||
bool isReadable() const override { return _flags & READABLE; }
|
||||
bool isWritable() const override { return _flags & WRITABLE; }
|
||||
|
||||
AbstractFSNode *getChild(const Common::String &n) const override;
|
||||
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
|
||||
AbstractFSNode *getParent() const override;
|
||||
|
||||
Common::SeekableReadStream *createReadStream() override;
|
||||
Common::SeekableWriteStream *createWriteStream(bool atomic) override;
|
||||
bool createDirectory() override;
|
||||
|
||||
int remove() override;
|
||||
|
||||
/**
|
||||
* Removes the SAF tree.
|
||||
* Only works on the root node
|
||||
*/
|
||||
void removeTree();
|
||||
protected:
|
||||
/**
|
||||
* Creates an AndroidSAFFilesystemNode given its tree and its node
|
||||
*
|
||||
* @param safTree SAF root in Java side
|
||||
* @param safNode SAF node in Java side
|
||||
*/
|
||||
AndroidSAFFilesystemNode(const GlobalRef &safTree, jobject safNode);
|
||||
|
||||
/**
|
||||
* Creates an non-existent AndroidSAFFilesystemNode given its tree, parent node and name
|
||||
*
|
||||
* @param safTree SAF root in Java side
|
||||
* @param safParent SAF parent node in Java side
|
||||
* @param path Parent path
|
||||
* @param name Item name
|
||||
*/
|
||||
AndroidSAFFilesystemNode(const GlobalRef &safTree, jobject safParent,
|
||||
const Common::String &path, const Common::String &name);
|
||||
|
||||
/**
|
||||
* Creates an non-existent AndroidSAFFilesystemNode given its tree, parent node and name
|
||||
*
|
||||
* @param safTree SAF root in Java side
|
||||
* @param safParent SAF parent node reference in Java side
|
||||
* @param path Parent path
|
||||
* @param name Item name
|
||||
*/
|
||||
AndroidSAFFilesystemNode(const GlobalRef &safTree, const NodeRef &safParent,
|
||||
const Common::String &path, const Common::String &name);
|
||||
|
||||
void cacheData(JNIEnv *env, jobject node);
|
||||
};
|
||||
|
||||
class AddSAFFakeNode final : public AbstractFSNode, public AndroidFSNode {
|
||||
protected:
|
||||
AbstractFSNode *getChild(const Common::String &name) const override;
|
||||
AbstractFSNode *getParent() const override;
|
||||
|
||||
public:
|
||||
static const char SAF_ADD_FAKE_PATH[];
|
||||
|
||||
AddSAFFakeNode(bool fromPath) : _proxied(nullptr), _fromPath(fromPath) { }
|
||||
~AddSAFFakeNode() override;
|
||||
|
||||
bool exists() const override;
|
||||
|
||||
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
|
||||
|
||||
// I18N: This is displayed in the file browser to let the user choose a new folder for Android Storage Attached Framework
|
||||
Common::U32String getDisplayName() const override;
|
||||
Common::String getName() const override;
|
||||
Common::String getPath() const override;
|
||||
|
||||
bool isDirectory() const override { return true; }
|
||||
bool isReadable() const override;
|
||||
bool isWritable() const override;
|
||||
|
||||
Common::SeekableReadStream *createReadStream() override { return nullptr; }
|
||||
Common::SeekableWriteStream *createWriteStream(bool atomic) override { return nullptr; }
|
||||
|
||||
bool createDirectory() override { return false; }
|
||||
int remove() override;
|
||||
|
||||
private:
|
||||
void makeProxySAF() const;
|
||||
|
||||
bool _fromPath;
|
||||
mutable AbstractFSNode *_proxied;
|
||||
};
|
||||
#endif
|
||||
Reference in New Issue
Block a user