diff options
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/samba/CMakeLists.txt | 33 | ||||
| -rw-r--r-- | libs/samba/build.gradle.kts | 26 | ||||
| l--------- | libs/samba/cmake | 1 | ||||
| m--------- | libs/samba/libsmb2 | 0 | ||||
| -rw-r--r-- | libs/samba/src/main/cpp/jni.cpp | 100 | ||||
| -rw-r--r-- | libs/samba/src/main/cpp/jni.hpp | 142 | ||||
| -rw-r--r-- | libs/samba/src/main/cpp/samba.cpp | 155 | ||||
| -rw-r--r-- | libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt | 92 | ||||
| -rw-r--r-- | libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt | 32 | ||||
| -rw-r--r-- | libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt | 88 | ||||
| -rw-r--r-- | libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt | 10 | ||||
| -rw-r--r-- | libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt | 17 |
12 files changed, 696 insertions, 0 deletions
diff --git a/libs/samba/CMakeLists.txt b/libs/samba/CMakeLists.txt new file mode 100644 index 0000000..b45e0aa --- /dev/null +++ b/libs/samba/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.12) + +project(libsmb2 + VERSION 4.0.0 + LANGUAGES C CXX) + +add_compile_options("-Wno-deprecated-non-prototype") + +option(BUILD_SHARED_LIBS "Build shared libraries" ON) + +set(SOVERSION 1 CACHE STRING "" FORCE) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + libsmb2/include + libsmb2/include/smb2 +) + +include(cmake/ConfigureChecks.cmake) + +add_subdirectory(libsmb2/lib) + +add_library( + samba + SHARED + src/main/cpp/jni.cpp + src/main/cpp/jni.hpp + src/main/cpp/samba.cpp +) + +find_library(log-lib log) + +target_link_libraries(samba smb2 ${log-lib}) diff --git a/libs/samba/build.gradle.kts b/libs/samba/build.gradle.kts new file mode 100644 index 0000000..79827ee --- /dev/null +++ b/libs/samba/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "org.the_jk.cleversync.samba" + + externalNativeBuild { + cmake { + path("CMakeLists.txt") + } + } + + buildTypes { + debug { + externalNativeBuild { cmake { cFlags += listOf("-g") } } + } + release { + externalNativeBuild { cmake { cFlags += listOf("-O3") } } + } + } +} + +dependencies { + implementation(project(":libs:io")) +} diff --git a/libs/samba/cmake b/libs/samba/cmake new file mode 120000 index 0000000..4c9d5fa --- /dev/null +++ b/libs/samba/cmake @@ -0,0 +1 @@ +libsmb2/cmake
\ No newline at end of file diff --git a/libs/samba/libsmb2 b/libs/samba/libsmb2 new file mode 160000 +Subproject 2ef9bb74ca48208fcf69235888fa1693a6bb4ff diff --git a/libs/samba/src/main/cpp/jni.cpp b/libs/samba/src/main/cpp/jni.cpp new file mode 100644 index 0000000..be13df7 --- /dev/null +++ b/libs/samba/src/main/cpp/jni.cpp @@ -0,0 +1,100 @@ +#include "jni.hpp" + +#include <android/log.h> + +namespace { + +JavaVM *g_vm; + +const char *_jni_error(jint err) { + switch (err) { + case JNI_OK: + return "OK"; + case JNI_ERR: + return "Unknown error"; + case JNI_EDETACHED: + return "Thread detached from the VM"; + case JNI_EVERSION: + return "JNI version error"; + case JNI_ENOMEM: + return "Not enough memory"; + case JNI_EEXIST: + return "VM already created"; + case JNI_EINVAL: + return "Invalid arguments"; + default: + return "Unexpected error"; + } +} + +} // namespace + +namespace jni { + +namespace internal { + +void _abort_if_not_ok(const char *file, int line, jint ret) { + if (ret == JNI_OK) [[likely]] return; + __android_log_assert(nullptr, "jni", "JNI error: %s", _jni_error(ret)); +} + +void _abort_with_exception(const char* file, int line, JNIEnv* env) { + auto throwable = jni::ExceptionOccurred(env); + env->ExceptionClear(); + if (throwable) { + auto throwable_class = jni::FindClass(env, "java/lang/Throwable"); + if (throwable_class) { + auto throwable_toString = env->GetMethodID(throwable_class.get(), + "toString", + "()Ljava/lang/String;"); + if (throwable_toString) { + auto description = jni::CallObjectMethod<jstring>(env, throwable, + throwable_toString); + auto str = jni::StringToUTF8(env, description); + __android_log_assert(nullptr, "jni", "JNI error: %s", str.c_str()); + } + } + env->ExceptionClear(); + __android_log_assert(nullptr, "jni", + "Unexpected NULL but no exception"); + } +} + +} // namespace internal + +JNIEnv* AttachCurrentThread() { + JNIEnv* env; + auto ret = g_vm->AttachCurrentThread(&env, nullptr); + ABORT_IF_NOT_OK(ret); + return env; +} + +JNIEnv* OnLoad(JavaVM* vm) { + void* v_env; + auto ret = vm->GetEnv(&v_env, JNI_VERSION); + ABORT_IF_NOT_OK(ret); + return reinterpret_cast<JNIEnv*>(v_env); +} + +LocalRef<jclass> FindClass(JNIEnv *env, const char *name) { + return {env, env->FindClass(name)}; +} + +LocalRef<jthrowable> ExceptionOccurred(JNIEnv* env) { + return {env, env->ExceptionOccurred()}; +} + +std::string StringToUTF8(JNIEnv* env, const Ref<jstring>& str) { + if (!str) return "null"; + auto len = env->GetStringUTFLength(str.get()); + std::string ret(len, ' '); + env->GetStringUTFRegion(str.get(), 0, len, ret.data()); + // This returns modified UTF-8 encoding, don't care. + return ret; +} + +LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str) { + return {env, env->NewStringUTF(str.c_str())}; +} + +} // namespace jni diff --git a/libs/samba/src/main/cpp/jni.hpp b/libs/samba/src/main/cpp/jni.hpp new file mode 100644 index 0000000..90ca011 --- /dev/null +++ b/libs/samba/src/main/cpp/jni.hpp @@ -0,0 +1,142 @@ +#ifndef CLEVERSYNC_JNI_HPP +#define CLEVERSYNC_JNI_HPP + +#include <jni.h> +#include <string> + +#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))) + +namespace jni { + +namespace internal { + +void _abort_if_not_ok(const char *file, int line, jint ret); +void _abort_with_exception(const char* file, int line, JNIEnv* env); + +} // namespace internal + +template<class T> +class Ref { + public: + constexpr Ref() : env_(nullptr), ptr_(0) {} + Ref(const Ref<T>&) = delete; + Ref<T>& operator=(const Ref<T>&) = delete; + + [[nodiscard]] T get() const { return ptr_; } + [[nodiscard]] T release() { + auto ret = release_to_local(); + ptr_ = 0; + return ret; + } + + void reset() { + del(); + ptr_ = 0; + } + + explicit operator bool() const { return ptr_ != 0; } + + protected: + Ref(JNIEnv* env, T ptr): env_(env), ptr_(ptr) {} + virtual ~Ref() = default; + + virtual T release_to_local() = 0; + virtual void del() = 0; + + JNIEnv* const env_; + T ptr_; +}; + +template<class T> +class LocalRef : public Ref<T> { + public: + LocalRef(JNIEnv* env, T ptr): Ref<T>(env, ptr) {} + ~LocalRef() override { free(); } + + protected: + T release_to_local() override { return this->ptr_; } + void del() override { free(); } + + private: + void free() { + if (this->ptr_) + this->env_->DeleteLocalRef(this->ptr_); + } +}; + +template<class T> +class ParamRef : public Ref<T> { + public: + ParamRef(JNIEnv* env, T ptr) : Ref<T>(env, ptr) {} + ~ParamRef() override = default; + + protected: + T release_to_local() override { + if (this->ptr_) + return static_cast<T>(this->env_->NewLocalRef(static_cast<jobject>(this->ptr_))); + return 0; + } + void del() override {} +}; + +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() override { free(); } + + protected: + T release_to_local() override { + if (this->ptr_) { + auto ret = static_cast<T>(this->env_->NewLocalRef( + static_cast<jobject>(this->ptr_))); + free(); + return ret; + } + return 0; + } + void del() override { free(); } + + private: + void free() { + if (this->ptr_) + this->env_->DeleteGlobalRef(this->ptr_); + } +}; + +constexpr jint JNI_VERSION = JNI_VERSION_1_2; + +JNIEnv* AttachCurrentThread(); + +JNIEnv* OnLoad(JavaVM* vm); + +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))}; +} + +std::string StringToUTF8(JNIEnv* env, const Ref<jstring>& str); + +LocalRef<jstring> UTF8ToString(JNIEnv* env, const std::string& str); + +namespace internal { + +template<typename T> +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); +} + +} // namespace internal + +} // namespace jni + +#endif // CLEVERSYNC_JNI_HPP diff --git a/libs/samba/src/main/cpp/samba.cpp b/libs/samba/src/main/cpp/samba.cpp new file mode 100644 index 0000000..5eafacc --- /dev/null +++ b/libs/samba/src/main/cpp/samba.cpp @@ -0,0 +1,155 @@ +#include <cassert> +#include <jni.h> +#include <memory> +#include <string> +#include <string_view> +#include <utility> + +#include "jni.hpp" +#include "libsmb2.h" + +namespace { + +class Dir { + public: + Dir(std::shared_ptr<smb2_context> context, smb2dir* dir) : context_(std::move(context)), dir_(dir) { + assert(context_ && dir_); + } + + ~Dir() { + smb2_closedir(context_.get(), dir_); + } + + Dir(const Dir&) = delete; + Dir& operator=(const Dir&) = delete; + + private: + std::shared_ptr<smb2_context> context_; + smb2dir* const dir_; +}; + +class Url { + public: + explicit Url(smb2_url* url) : url_(url) { + assert(url_); + } + + ~Url() { + smb2_destroy_url(url_); + } + + Url(const Url&) = delete; + Url& operator=(const Url&) = delete; + + [[nodiscard]] const char* path() const { return url_->path; } + [[nodiscard]] const char* server() const { return url_->server; } + [[nodiscard]] const char* share() const { return url_->share; } + [[nodiscard]] const char* user() const { return url_->user; } + + private: + smb2_url* url_; +}; + +class Context { + public: + Context() : context_(smb2_init_context(), ContextDeleter{}) {} + ~Context() = default; + + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + + [[nodiscard]] std::unique_ptr<Url> ParseUrl(const std::string& url) { + auto* ptr = smb2_parse_url(context_.get(), url.c_str()); + return ptr ? std::make_unique<Url>(ptr): nullptr; + } + + bool Connect(const Url& url) { + return smb2_connect_share(context_.get(), url.server(), url.share(), url.user()) == 0; + } + + [[nodiscard]] std::string_view GetError() { + return smb2_get_error(context_.get()); + } + + [[nodiscard]] std::unique_ptr<Dir> OpenDir(const std::string& path) { + auto* ptr = smb2_opendir(context_.get(), path.c_str()); + return ptr ? std::make_unique<Dir>(context_, ptr) : nullptr; + } + + private: + struct ContextDeleter { + void operator()(smb2_context* context) { + smb2_destroy_context(context); + } + }; + + std::shared_ptr<smb2_context> context_; +}; + +jlong nativeContextNew(JNIEnv* env, jclass clazz) { + return reinterpret_cast<jlong>(new Context()); +} + +void nativeContextDestroy(JNIEnv* env, jclass clazz, jlong ptr) { + delete reinterpret_cast<Context*>(ptr); +} + +jlong nativeContextParseUrl(JNIEnv* env, jclass clazz, jlong ptr, jstring url) { + return reinterpret_cast<jlong>(reinterpret_cast<Context*>(ptr)->ParseUrl(jni::StringToUTF8(env, jni::ParamRef(env, url))).release()); +} + +jboolean nativeContextConnect(JNIEnv* env, jclass clazz, jlong context_ptr, jlong url_ptr) { + auto* url = reinterpret_cast<Url*>(url_ptr); + if (!url) return JNI_FALSE; + return reinterpret_cast<Context*>(context_ptr)->Connect(*url) ? JNI_TRUE : JNI_FALSE; +} + +jstring nativeContextGetError(JNIEnv* env, jclass clazz, jlong ptr) { + return jni::UTF8ToString(env, std::string(reinterpret_cast<Context*>(ptr)->GetError())).release(); +} + +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()); +} + +void nativeUrlDestroy(JNIEnv* env, jclass clazz, jlong ptr) { + delete reinterpret_cast<Url*>(ptr); +} + +jstring nativeUrlPath(JNIEnv* env, jclass clazz, jlong ptr) { + return jni::UTF8ToString(env, std::string(reinterpret_cast<Url*>(ptr)->path())).release(); +} + +void nativeDirDestroy(JNIEnv* env, jclass clazz, jlong ptr) { + delete reinterpret_cast<Dir*>(ptr); +} + +void RegisterSamba(JNIEnv* env) { + auto clazz = jni::FindClass(env, "org/the_jk/cleversync/io/samba/NativeSamba"); + ABORT_IF_NULL(env, clazz); + static const JNINativeMethod methods[] = { + { "nativeContextNew", "()J", reinterpret_cast<void*>(&nativeContextNew) }, + { "nativeContextDestroy", "(J)V", reinterpret_cast<void*>(&nativeContextDestroy) }, + { "nativeContextParseUrl", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeContextParseUrl) }, + { "nativeContextConnect", "(JJ)Z", reinterpret_cast<void*>(&nativeContextConnect) }, + { "nativeContextGetError", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeContextGetError) }, + { "nativeContextOpenDir", "(JLjava/lang/String;)J", reinterpret_cast<void*>(&nativeContextOpenDir) }, + + { "nativeUrlDestroy", "(J)V", reinterpret_cast<void*>(&nativeUrlDestroy) }, + { "nativeUrlPath", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeUrlPath) }, + + { "nativeDirDestroy", "(J)V", reinterpret_cast<void*>(&nativeDirDestroy) }, + }; + auto ret = env->RegisterNatives(clazz.get(), methods, sizeof(methods) / sizeof(methods[0])); + ABORT_IF_NOT_OK(ret); +} + +} // namespace + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + auto* env = jni::OnLoad(vm); + + RegisterSamba(env); + + return jni::JNI_VERSION; +} 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 new file mode 100644 index 0000000..360aaa9 --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/NativeSamba.kt @@ -0,0 +1,92 @@ +// Using RegisterNatives +@file:Suppress("KotlinJniMissingFunction") + +package org.the_jk.cleversync.io.samba + +import androidx.annotation.Keep + +@Keep +internal object NativeSamba { + fun newContext(): Context = NativeContext(nativeContextNew()) + + interface Object { + fun destroy() + } + + interface Context : Object { + fun parseUrl(url: String): Url? + fun connect(url: Url): Boolean + fun lastError(): String + fun openDir(path: String): Dir? + } + + interface Url : Object { + fun path(): String + } + + interface Dir : Object { + val path: String + } + + private class NativeContext(private var ptr: Long): Context { + override fun destroy() { + if (ptr == 0L) return + nativeContextDestroy(ptr) + ptr = 0L + } + + override fun parseUrl(url: String): Url? { + val ptr = nativeContextParseUrl(ptr, url) + return if (ptr != 0L) NativeUrl(ptr) else null + } + + override fun connect(url: Url): Boolean { + return nativeContextConnect(ptr, (url as NativeUrl).get()) + } + + override fun lastError(): String { + return nativeContextGetError(ptr) + } + + override fun openDir(path: String): Dir? { + val dir = nativeContextOpenDir(ptr, path) + return if (dir != 0L) NativeDir(path, dir) else null + } + } + + private class NativeUrl(private var ptr: Long): Url { + override fun destroy() { + if (ptr == 0L) return + nativeUrlDestroy(ptr) + ptr = 0L + } + + override fun path(): String = nativeUrlPath(ptr) + + fun get(): Long = ptr + } + + private class NativeDir(override val path: String, private var ptr: Long): Dir { + override fun destroy() { + if (ptr == 0L) return + nativeDirDestroy(ptr) + ptr = 0L + } + } + + init { + System.loadLibrary("samba") + } + + private external fun nativeContextNew(): Long + private external fun nativeContextDestroy(ptr: Long) + private external fun nativeContextParseUrl(ptr: Long, url: String): Long + private external fun nativeContextConnect(ptr: Long, url: Long): Boolean + private external fun nativeContextGetError(ptr: Long): String + private external fun nativeContextOpenDir(ptr: Long, path: String): Long + + private external fun nativeUrlDestroy(ptr: Long) + private external fun nativeUrlPath(ptr: Long): String + + private external fun nativeDirDestroy(ptr: Long) +} 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 new file mode 100644 index 0000000..03d3a9e --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaConnection.kt @@ -0,0 +1,32 @@ +package org.the_jk.cleversync.io.samba + +internal class SambaConnection(uri: String) { + private val context = NativeSamba.newContext() + private val url = context.parseUrl(uri) + + val connected = if (url != null) { context.connect(url) } else false + + val error: String + get() = context.lastError() + + fun openDir(path: String): NativeSamba.Dir? = + if (connected) context.openDir(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 new file mode 100644 index 0000000..b82e745 --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaDirectory.kt @@ -0,0 +1,88 @@ +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 + +internal open class SambaDirectory( + private val conn: SambaConnection, + private val dir: NativeSamba.Dir, +) : ModifiableDirectory { + override fun modifiableOpenDir(name: String): ModifiableDirectory? { + val dir = conn.openDir(SambaConnection.join(dir.path, name)) ?: return null + return SambaDirectory(conn, dir) + } + + override fun modifiableOpenFile(name: String): ModifiableFile? { + TODO("Not yet implemented") + } + + override fun modifiableOpenLink(name: String): ModifiableLink? { + TODO("Not yet implemented") + } + + override fun modifiableList(): ModifiableDirectory.Content { + TODO("Not yet implemented") + } + + override fun modifiableLiveList(): LiveData<ModifiableDirectory.Content> { + TODO("Not yet implemented") + } + + override fun createDirectory(name: String): ModifiableDirectory { + TODO("Not yet implemented") + } + + override fun createFile(name: String): ModifiableFile { + TODO("Not yet implemented") + } + + override fun createLink(name: String, target: Directory): ModifiableLink { + TODO("Not yet implemented") + } + + override fun createLink(name: String, target: File): ModifiableLink { + TODO("Not yet implemented") + } + + override fun createLink(name: String, target: String): ModifiableLink { + TODO("Not yet implemented") + } + + override fun removeDirectory(name: String): Boolean { + TODO("Not yet implemented") + } + + override fun removeFile(name: String): Boolean { + TODO("Not yet implemented") + } + + override fun removeLink(name: String): Boolean { + TODO("Not yet implemented") + } + + 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 openLink(name: String): Link? { + TODO("Not yet implemented") + } + + override fun list(): Directory.Content { + TODO("Not yet implemented") + } + + override fun liveList(): LiveData<Directory.Content> { + TODO("Not yet implemented") + } +} 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 new file mode 100644 index 0000000..8b4a86d --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/io/samba/SambaTree.kt @@ -0,0 +1,10 @@ +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 { + 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 new file mode 100644 index 0000000..3455de8 --- /dev/null +++ b/libs/samba/src/main/java/org/the_jk/cleversync/samba/SambaTreeFactory.kt @@ -0,0 +1,17 @@ +package org.the_jk.cleversync.samba + +import org.the_jk.cleversync.io.ModifiableTree +import org.the_jk.cleversync.io.Tree +import org.the_jk.cleversync.io.samba.SambaConnection +import org.the_jk.cleversync.io.samba.SambaTree + +object SambaTreeFactory { + fun tree(uri: String): Result<Tree> = modifiableTree(uri) + + fun modifiableTree(uri: String): Result<ModifiableTree> { + val connection = SambaConnection(uri) + 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)) + } +} |
