diff options
Diffstat (limited to 'libs/samba/src/main')
10 files changed, 408 insertions, 47 deletions
diff --git a/libs/samba/src/main/cpp/jni.cpp b/libs/samba/src/main/cpp/jni.cpp index be13df7..30f03a9 100644 --- a/libs/samba/src/main/cpp/jni.cpp +++ b/libs/samba/src/main/cpp/jni.cpp @@ -97,4 +97,14 @@ LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str) { return {env, env->NewStringUTF(str.c_str())}; } +LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects) { + auto ret = LocalRef<jobjectArray>(env, env->NewObjectArray(static_cast<jsize>(objects.size()), element_class.get(), nullptr)); + ABORT_IF_NULL(env, ret); + jsize i = 0; + for (auto&& obj : objects) { + env->SetObjectArrayElement(ret.get(), i++, obj.release()); + } + return ret; +} + } // namespace jni diff --git a/libs/samba/src/main/cpp/jni.hpp b/libs/samba/src/main/cpp/jni.hpp index 90ca011..450fa71 100644 --- a/libs/samba/src/main/cpp/jni.hpp +++ b/libs/samba/src/main/cpp/jni.hpp @@ -3,6 +3,7 @@ #include <jni.h> #include <string> +#include <vector> #define ABORT_IF_NOT_OK(x) (::jni::internal::_abort_if_not_ok(__FILE__, __LINE__, (x))) #define ABORT_IF_NULL(env, x) (::jni::internal::_abort_if_null(__FILE__, __LINE__, (env), (x))) @@ -21,8 +22,8 @@ class Ref { public: constexpr Ref() : env_(nullptr), ptr_(0) {} Ref(const Ref<T>&) = delete; - Ref<T>& operator=(const Ref<T>&) = delete; + [[nodiscard]] JNIEnv* env() const { return env_; } [[nodiscard]] T get() const { return ptr_; } [[nodiscard]] T release() { auto ret = release_to_local(); @@ -44,7 +45,7 @@ class Ref { virtual T release_to_local() = 0; virtual void del() = 0; - JNIEnv* const env_; + JNIEnv* env_; T ptr_; }; @@ -54,6 +55,10 @@ class LocalRef : public Ref<T> { LocalRef(JNIEnv* env, T ptr): Ref<T>(env, ptr) {} ~LocalRef() override { free(); } + LocalRef(LocalRef<T> &&ref) noexcept : Ref<T>(ref.env_, ref.release()) {} + + LocalRef& operator=(const Ref<T>& other) = delete; + protected: T release_to_local() override { return this->ptr_; } void del() override { free(); } @@ -71,6 +76,8 @@ class ParamRef : public Ref<T> { ParamRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr) {} ~ParamRef() override = default; + ParamRef& operator=(const Ref<T>& other) = delete; + protected: T release_to_local() override { if (this->ptr_) @@ -83,11 +90,18 @@ class ParamRef : public Ref<T> { template<class T> class GlobalRef : public Ref<T> { public: - GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? env->NewGlobalRef(ptr) : 0) {} - explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env_, other ? other.env_->NewGlobalRef(other.ptr_) : 0) {} + GlobalRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr ? static_cast<T>(env->NewGlobalRef(static_cast<jobject>(ptr))) : 0) {} + explicit GlobalRef(const Ref<T>& other) : Ref<T>(other.env(), other ? static_cast<T>(other.env()->NewGlobalRef(static_cast<jobject>(other.get()))) : 0) {} ~GlobalRef() override { free(); } + GlobalRef& operator=(const Ref<T>& other) { + free(); + this->env_ = other.env(); + this->ptr_ = other ? static_cast<T>(this->env_->NewGlobalRef(static_cast<jobject>(other.get()))) : 0; + return *this; + } + protected: T release_to_local() override { if (this->ptr_) { @@ -117,19 +131,32 @@ LocalRef<jclass> FindClass(JNIEnv *env, const char *name); LocalRef<jthrowable> ExceptionOccurred(JNIEnv* env); -template<typename Out, typename In> -LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method) { - return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method))}; +template<typename Out, typename In, typename... Params> +LocalRef<Out> CallObjectMethod(JNIEnv* env, const Ref<In>& object, jmethodID method, Params... params) { + return {env, static_cast<Out>(env->CallObjectMethod(object.get(), method, params...))}; +} + +template<typename Out, typename... Params> +LocalRef<Out> CallStaticObjectMethod(JNIEnv* env, const Ref<jclass>& clazz, jmethodID method, Params... params) { + return {env, static_cast<Out>(env->CallStaticObjectMethod(clazz.get(), method, params...))}; } std::string StringToUTF8(JNIEnv* env, const Ref<jstring>& str); LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str); +LocalRef<jobjectArray> CreateArray(JNIEnv* env, const Ref<jclass>& element_class, std::vector<LocalRef<jobject>> objects); + namespace internal { template<typename T> -void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) { +inline void _abort_if_null(const char* file, int line, JNIEnv* env, const jni::Ref<T>& ref) { + if (ref) [[likely]] return; + + _abort_with_exception(file, line, env); +} + +inline void _abort_if_null(const char* file, int line, JNIEnv* env, jmethodID ref) { if (ref) [[likely]] return; _abort_with_exception(file, line, env); diff --git a/libs/samba/src/main/cpp/samba.cpp b/libs/samba/src/main/cpp/samba.cpp index e3b689d..7b83eb4 100644 --- a/libs/samba/src/main/cpp/samba.cpp +++ b/libs/samba/src/main/cpp/samba.cpp @@ -11,6 +11,8 @@ namespace { +jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat); + class Dir { public: Dir(std::shared_ptr<smb2_context> context, smb2dir* dir) : context_(std::move(context)), dir_(dir) { @@ -24,6 +26,20 @@ class Dir { Dir(const Dir&) = delete; Dir& operator=(const Dir&) = delete; + jni::LocalRef<jobjectArray> List(JNIEnv* env, const jni::Ref<jclass>& dir_entry_clazz) { + smb2_rewinddir(context_.get(), dir_); + std::vector<jni::LocalRef<jobject>> tmp; + while (auto* ent = smb2_readdir(context_.get(), dir_)) { + // Skip . and .. entries. + if (ent->name[0] == '.' && (ent->name[1] == '\0' || (ent->name[1] == '.' && ent->name[2] == '\0'))) continue; + auto obj = CreateDirEntry(env, ent->name, ent->st); + if (obj) { + tmp.push_back(std::move(obj)); + } + } + return jni::CreateArray(env, dir_entry_clazz, std::move(tmp)); + } + private: std::shared_ptr<smb2_context> context_; smb2dir* const dir_; @@ -81,6 +97,50 @@ class Context { return ptr ? std::make_unique<Dir>(context_, ptr) : nullptr; } + [[nodiscard]] jni::LocalRef<jobject> Entry(JNIEnv* env, const std::string& path) { + struct smb2_stat_64 stat; // NOLINT(*-pro-type-member-init) + auto ret = smb2_stat(context_.get(), path.c_str(), &stat); + if (ret) return {env, nullptr}; + auto slash = path.find_last_of('/'); + return CreateDirEntry(env, slash == std::string::npos ? path : path.substr(slash + 1), stat); + } + + bool MakeDir(const std::string& path) { + return smb2_mkdir(context_.get(), path.c_str()) == 0; + } + + bool RemoveDir(const std::string& path) { + return smb2_rmdir(context_.get(), path.c_str()) == 0; + } + + bool Unlink(const std::string& path) { + return smb2_unlink(context_.get(), path.c_str()) == 0; + } + + std::optional<std::string> ReadLink(const std::string& path) { + // Good to start with a fairly small size as current implementation + // of smb2_readlink uses strncpy, which pads the whole unused buffer + // with zeros. + uint32_t bufsize = 256; + std::vector<char> buf; + while (true) { + buf.resize(bufsize); + auto ret = smb2_readlink(context_.get(), path.c_str(), buf.data(), bufsize); + if (ret != 0) + return std::nullopt; + // smb2_readlink uses strncpy, so if actual path was larger than bufsize + // there will be no terminating zero. + auto it = std::find(buf.begin(), buf.end(), '\0'); + if (it != buf.end()) + return std::string(buf.begin(), it); + const auto previous = bufsize; + bufsize *= 2; + // Check for bufsize (a uint32_t) overflow. + if (bufsize <= previous) + return std::nullopt; + } + } + private: struct ContextDeleter { void operator()(smb2_context* context) { @@ -121,6 +181,29 @@ jlong nativeContextOpenDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { return reinterpret_cast<jlong>(reinterpret_cast<Context*>(ptr)->OpenDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release()); } +jobject nativeContextEntry(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast<Context*>(ptr)->Entry(env, jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))).release(); +} + +jboolean nativeContextMakeDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast<Context*>(ptr)->MakeDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeContextRemoveDir(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast<Context*>(ptr)->RemoveDir(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jboolean nativeContextUnlink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + return reinterpret_cast<Context*>(ptr)->Unlink(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))) ? JNI_TRUE : JNI_FALSE; +} + +jstring nativeContextReadLink(JNIEnv* env, jclass clazz, jlong ptr, jstring path) { + auto ret = reinterpret_cast<Context*>(ptr)->ReadLink(jni::StringToUTF8(env, jni::ParamRef<jstring>(env, path))); + if (ret.has_value()) + return jni::UTF8ToString(env, ret.value()).release(); + return nullptr; +} + void nativeUrlDestroy(JNIEnv* env, jclass clazz, jlong ptr) { delete reinterpret_cast<Url*>(ptr); } @@ -133,9 +216,20 @@ void nativeDirDestroy(JNIEnv* env, jclass clazz, jlong ptr) { delete reinterpret_cast<Dir*>(ptr); } +jni::GlobalRef<jclass> g_DirEntryClass(nullptr, nullptr); + +jobjectArray nativeDirList(JNIEnv* env, jclass clazz, jlong ptr) { + return reinterpret_cast<Dir*>(ptr)->List(env, g_DirEntryClass).release(); +} + +jni::GlobalRef<jclass> g_NativeSambaClass(nullptr, nullptr); +jmethodID g_CreateDirEntry; + void RegisterSamba(JNIEnv* env) { auto clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba"); ABORT_IF_NULL(env, clazz); + auto dir_entry_clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba$DirEntry"); + ABORT_IF_NULL(env, dir_entry_clazz); static const JNINativeMethod methods[] = { { "nativeContextNew", "(I)J", reinterpret_cast<void*>(&nativeContextNew) }, { "nativeContextDestroy", "(J)V", reinterpret_cast<void*>(&nativeContextDestroy) }, @@ -143,14 +237,48 @@ void RegisterSamba(JNIEnv* env) { { "nativeContextConnect", "(JJLjava/lang/String;Ljava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextConnect) }, { "nativeContextGetError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextGetError) }, { "nativeContextOpenDir", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeContextOpenDir) }, + { "nativeContextEntry", "(JLjava/lang/String;)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&nativeContextEntry) }, + { "nativeContextMakeDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextMakeDir) }, + { "nativeContextRemoveDir", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextRemoveDir) }, + { "nativeContextUnlink", "(JLjava/lang/String;)Z", reinterpret_cast<void*>(&nativeContextUnlink) }, + { "nativeContextReadLink", "(JLjava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextReadLink) }, { "nativeUrlDestroy", "(J)V", reinterpret_cast<void*>(&nativeUrlDestroy) }, { "nativeUrlPath", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeUrlPath) }, { "nativeDirDestroy", "(J)V", reinterpret_cast<void*>(&nativeDirDestroy) }, + { "nativeDirList", "(J)[Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;", reinterpret_cast<void*>(&nativeDirList) }, }; auto ret = env->RegisterNatives(clazz.get(), methods, sizeof(methods) / sizeof(methods[0])); ABORT_IF_NOT_OK(ret); + + g_CreateDirEntry = env->GetStaticMethodID(clazz.get(), "createDirEntry", "(Ljava/lang/String;IJJ)Lorg/the_jk/cleversync/io/samba/NativeSamba$DirEntry;"); + ABORT_IF_NULL(env, g_CreateDirEntry); + g_NativeSambaClass = clazz; + g_DirEntryClass = dir_entry_clazz; +} + +jni::LocalRef<jobject> CreateDirEntry(JNIEnv* env, const std::string& name, const smb2_stat_64& stat) { + auto j_name = jni::UTF8ToString(env, name); + // Kotlin size casts Long to ULong + auto size = static_cast<jlong>(stat.smb2_size); + auto last_modified = static_cast<jlong>(stat.smb2_mtime); + jint type; + switch (stat.smb2_type) { + case SMB2_TYPE_DIRECTORY: + type = 0; + break; + case SMB2_TYPE_FILE: + type = 1; + break; + case SMB2_TYPE_LINK: + type = 2; + break; + default: + return {env, nullptr}; + } + + return jni::CallStaticObjectMethod<jobject>(env, g_NativeSambaClass, g_CreateDirEntry, j_name.get(), type, size, last_modified); } } // namespace diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt index e48268d..1863e22 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt @@ -4,6 +4,7 @@ package org.the_jk.cleversync.io.samba import androidx.annotation.Keep +import java.time.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -21,14 +22,35 @@ internal object NativeSamba { fun connect(url: Url, credentials: SambaCredentials): Boolean fun lastError(): String fun openDir(path: String): Dir? + fun entry(path: String): DirEntry? + fun makeDir(path: String): Boolean + fun removeDir(path: String): Boolean + fun unlink(path: String): Boolean + fun readLink(path: String): String? } interface Url : Object { fun path(): String } + enum class DirEntryType { + DIR, + FILE, + LINK, + } + + @Keep + data class DirEntry( + val name: String, + val type: DirEntryType, + val size: ULong, + val lastModified: Instant, + ) + interface Dir : Object { val path: String + + fun list(): Array<DirEntry> } private class NativeContext(private var ptr: Long): Context { @@ -55,6 +77,26 @@ internal object NativeSamba { val dir = nativeContextOpenDir(ptr, path) return if (dir != 0L) NativeDir(path, dir) else null } + + override fun entry(path: String): DirEntry? { + return nativeContextEntry(ptr, path) + } + + override fun makeDir(path: String): Boolean { + return nativeContextMakeDir(ptr, path) + } + + override fun removeDir(path: String): Boolean { + return nativeContextRemoveDir(ptr, path) + } + + override fun unlink(path: String): Boolean { + return nativeContextUnlink(ptr, path) + } + + override fun readLink(path: String): String? { + return nativeContextReadLink(ptr, path) + } } private class NativeUrl(private var ptr: Long): Url { @@ -75,21 +117,47 @@ internal object NativeSamba { nativeDirDestroy(ptr) ptr = 0L } + + override fun list(): Array<DirEntry> { + return nativeDirList(ptr) + } } init { System.loadLibrary("samba") } + @JvmStatic + @Keep + @Suppress("UnusedPrivateMember") + private fun createDirEntry(name: String, type: Int, size: Long, lastModified: Long) = + DirEntry( + name = name, + when (type) { + 0 -> DirEntryType.DIR + 1 -> DirEntryType.FILE + 2 -> DirEntryType.LINK + else -> throw IllegalArgumentException("Unknown type: $type") + }, + size = size.toULong(), + lastModified = Instant.ofEpochMilli(lastModified), + ) + private external fun nativeContextNew(timeoutSeconds: Int): Long private external fun nativeContextDestroy(ptr: Long) private external fun nativeContextParseUrl(ptr: Long, url: String): Long private external fun nativeContextConnect(ptr: Long, url: Long, username: String?, password: String?): Boolean private external fun nativeContextGetError(ptr: Long): String private external fun nativeContextOpenDir(ptr: Long, path: String): Long + private external fun nativeContextEntry(ptr: Long, path: String): DirEntry? + private external fun nativeContextMakeDir(ptr: Long, path: String): Boolean + private external fun nativeContextRemoveDir(ptr: Long, path: String): Boolean + private external fun nativeContextUnlink(ptr: Long, path: String): Boolean + private external fun nativeContextReadLink(otr: Long, path: String): String? private external fun nativeUrlDestroy(ptr: Long) private external fun nativeUrlPath(ptr: Long): String private external fun nativeDirDestroy(ptr: Long) + private external fun nativeDirList(ptr: Long): Array<DirEntry> } diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt index 05cc831..04aff44 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt @@ -12,21 +12,26 @@ internal class SambaConnection(uri: String, credentials: SambaCredentials) { fun openDir(path: String): NativeSamba.Dir? = if (connected) context.openDir(join(url!!.path(), path)) else null + fun entry(path: String): NativeSamba.DirEntry? = + if (connected) context.entry(join(url!!.path(), path)) else null + + fun makeDir(path: String): Boolean = + connected && context.makeDir(join(url!!.path(), path)) + + fun removeDir(path: String): Boolean = + connected && context.removeDir(join(url!!.path(), path)) + + fun unlink(path: String): Boolean = + connected && context.unlink(join(url!!.path(), path)) + + fun readLink(path: String): String? = + if (connected) context.readLink(join(url!!.path(), path)) else null + companion object { fun join(a: String, b: String): String { if (a.isEmpty() || b.startsWith("/")) return b if (b.isEmpty()) return a return if (a.endsWith("/")) a + b else "${a}/${b}" } - - fun dirname(path: String): String { - val last = path.lastIndexOf('/') - return if (last == -1) path else path.substring(last + 1) - } - - fun basename(path: String): String { - val last = path.lastIndexOf('/') - return if (last < 1) "" else path.substring(0, last - 1) - } } } diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt index b82e745..d9ec6aa 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt @@ -3,30 +3,61 @@ package org.the_jk.cleversync.io.samba import androidx.lifecycle.LiveData import org.the_jk.cleversync.io.Directory import org.the_jk.cleversync.io.File -import org.the_jk.cleversync.io.Link import org.the_jk.cleversync.io.ModifiableDirectory import org.the_jk.cleversync.io.ModifiableFile import org.the_jk.cleversync.io.ModifiableLink +import java.io.IOException +import java.time.Instant internal open class SambaDirectory( private val conn: SambaConnection, - private val dir: NativeSamba.Dir, + private val path: String, + override val name: String, ) : ModifiableDirectory { override fun modifiableOpenDir(name: String): ModifiableDirectory? { - val dir = conn.openDir(SambaConnection.join(dir.path, name)) ?: return null - return SambaDirectory(conn, dir) + val newPath = SambaConnection.join(path, name) + val entry = conn.entry(newPath) ?: return null + if (entry.type != NativeSamba.DirEntryType.DIR) return null + return SambaDirectory(conn, newPath, name) } override fun modifiableOpenFile(name: String): ModifiableFile? { - TODO("Not yet implemented") + val newPath = SambaConnection.join(path, name) + val entry = conn.entry(newPath) ?: return null + if (entry.type != NativeSamba.DirEntryType.FILE) return null + return SambaFile(conn, newPath, name, entry.size, entry.lastModified) } override fun modifiableOpenLink(name: String): ModifiableLink? { - TODO("Not yet implemented") + val newPath = SambaConnection.join(path, name) + val entry = conn.entry(newPath) ?: return null + if (entry.type != NativeSamba.DirEntryType.LINK) return null + return SambaLink(conn, newPath, name) } override fun modifiableList(): ModifiableDirectory.Content { - TODO("Not yet implemented") + val directories = mutableListOf<ModifiableDirectory>() + val files = mutableListOf<ModifiableFile>() + val links = mutableListOf<ModifiableLink>() + val dir = conn.openDir(path) + if (dir != null) { + dir.list().forEach { entry -> + val entryPath = SambaConnection.join(path, entry.name) + when (entry.type) { + NativeSamba.DirEntryType.DIR -> { + directories.add(SambaDirectory(conn, entryPath, entry.name)) + } + NativeSamba.DirEntryType.FILE -> { + files.add(SambaFile(conn, entryPath, entry.name, entry.size, entry.lastModified)) + } + NativeSamba.DirEntryType.LINK -> { + links.add(SambaLink(conn, entryPath, entry.name)) + } + } + } + dir.destroy() + } + return ModifiableDirectory.Content(directories, files, links) } override fun modifiableLiveList(): LiveData<ModifiableDirectory.Content> { @@ -34,52 +65,48 @@ internal open class SambaDirectory( } override fun createDirectory(name: String): ModifiableDirectory { - TODO("Not yet implemented") + val newPath = SambaConnection.join(path, name) + if (!conn.makeDir(newPath)) throw IOException(conn.error) + return SambaDirectory(conn, newPath, name) } override fun createFile(name: String): ModifiableFile { - TODO("Not yet implemented") + val newPath = SambaConnection.join(path, name) + return SambaFile(conn, newPath, name, 0UL, Instant.EPOCH, Instant.EPOCH) } override fun createLink(name: String, target: Directory): ModifiableLink { - TODO("Not yet implemented") + throw IOException("Unsupported operation") } override fun createLink(name: String, target: File): ModifiableLink { - TODO("Not yet implemented") + throw IOException("Unsupported operation") } override fun createLink(name: String, target: String): ModifiableLink { - TODO("Not yet implemented") + throw IOException("Unsupported operation") } override fun removeDirectory(name: String): Boolean { - TODO("Not yet implemented") + return conn.removeDir(SambaConnection.join(path, name)) } override fun removeFile(name: String): Boolean { - TODO("Not yet implemented") + return conn.unlink(SambaConnection.join(path, name)) } override fun removeLink(name: String): Boolean { - TODO("Not yet implemented") + return conn.unlink(SambaConnection.join(path, name)) } - override val name: String - get() = SambaConnection.dirname(dir.path) - override fun openDir(name: String) = modifiableOpenDir(name) - override fun openFile(name: String): File? { - TODO("Not yet implemented") - } + override fun openFile(name: String) = modifiableOpenFile(name) - override fun openLink(name: String): Link? { - TODO("Not yet implemented") - } + override fun openLink(name: String) = modifiableOpenLink(name) - override fun list(): Directory.Content { - TODO("Not yet implemented") + override fun list() = with(modifiableList()) { + Directory.Content(directories, files, links) } override fun liveList(): LiveData<Directory.Content> { diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt new file mode 100644 index 0000000..817c1bf --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaFile.kt @@ -0,0 +1,40 @@ +package org.the_jk.cleversync.io.samba + +import org.the_jk.cleversync.io.ModifiableFile +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.time.Instant + +internal class SambaFile( + private val conn: SambaConnection, + private val path: String, + override val name: String, + private val cachedSize: ULong, + private val cachedLastModified: Instant, + private var cacheEndOfLife: Instant = Instant.now().plusSeconds(60), +) : ModifiableFile { + override fun write(): OutputStream { + TODO("Not yet implemented") + } + + override fun read(): InputStream { + TODO("Not yet implemented") + } + + override val size: ULong get() { + if (useCached()) return cachedSize + val entry = conn.entry(path) ?: throw IOException(conn.error) + return entry.size + } + + override val lastModified: Instant get() { + if (useCached()) return cachedLastModified + val entry = conn.entry(path) ?: throw IOException(conn.error) + return entry.lastModified + } + + private fun useCached(): Boolean { + return Instant.now().isBefore(cacheEndOfLife) + } +} diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt new file mode 100644 index 0000000..9d9fd1d --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaLink.kt @@ -0,0 +1,57 @@ +package org.the_jk.cleversync.io.samba + +import org.the_jk.cleversync.io.Directory +import org.the_jk.cleversync.io.File +import org.the_jk.cleversync.io.Link +import org.the_jk.cleversync.io.ModifiableLink +import java.io.IOException + +internal class SambaLink( + private val conn: SambaConnection, + private val path: String, + override val name: String, +) : ModifiableLink { + override fun modifiableResolve(): ModifiableLink.ModifiableLinkTarget { + val (newPath, entry) = doResolve() + if (entry == null) return ModifiableLink.NoTarget + return when (entry.type) { + NativeSamba.DirEntryType.DIR -> ModifiableLink.ModifiableDirectoryTarget(SambaDirectory(conn, newPath, entry.name)) + NativeSamba.DirEntryType.FILE -> ModifiableLink.ModifiableFileTarget(SambaFile(conn, newPath, entry.name, entry.size, entry.lastModified)) + NativeSamba.DirEntryType.LINK -> ModifiableLink.NoTarget + } + } + + override fun target(directory: Directory) { + throw IOException("Unsupported operation") + } + + override fun target(file: File) { + throw IOException("Unsupported operation") + } + + override fun target(name: String) { + throw IOException("Unsupported operation") + } + + override fun resolve(): Link.LinkTarget { + val (newPath, entry) = doResolve() + if (entry == null) return Link.NoTarget + return when (entry.type) { + NativeSamba.DirEntryType.DIR -> Link.DirectoryTarget(SambaDirectory(conn, newPath, entry.name)) + NativeSamba.DirEntryType.FILE -> Link.FileTarget(SambaFile(conn, newPath, entry.name, entry.size, entry.lastModified)) + NativeSamba.DirEntryType.LINK -> Link.NoTarget + } + } + + private fun doResolve(): Pair<String, NativeSamba.DirEntry?> { + var linkPath = path + var entry: NativeSamba.DirEntry? = null + while (true) { + val target = conn.readLink(linkPath) ?: break + linkPath = SambaConnection.join(linkPath, target) + entry = conn.entry(linkPath) ?: break + if (entry.type != NativeSamba.DirEntryType.LINK) break + } + return linkPath to entry + } +} diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt index 8b4a86d..762a61f 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt @@ -3,7 +3,7 @@ package org.the_jk.cleversync.io.samba import android.content.res.Resources import org.the_jk.cleversync.io.ModifiableTree -internal class SambaTree(conn: SambaConnection, root: NativeSamba.Dir) : SambaDirectory(conn, root), ModifiableTree { +internal class SambaTree(conn: SambaConnection, root: String) : SambaDirectory(conn, root, ""), ModifiableTree { override fun description(resources: Resources): CharSequence { TODO("Not yet implemented") } diff --git a/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt b/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt index 5c9bc4f..ebf47d2 100644 --- a/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt +++ b/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt @@ -12,7 +12,6 @@ object SambaTreeFactory { fun modifiableTree(uri: String, credentials: SambaCredentials): Result<ModifiableTree> { val connection = SambaConnection(uri, credentials) if (!connection.connected) return Result.failure(Exception(connection.error)) - val root = connection.openDir("") ?: return Result.failure(Exception(connection.error)) - return Result.success(SambaTree(connection, root)) + return Result.success(SambaTree(connection, "")) } } |
