diff --git a/.changes/config.json b/.changes/config.json index 81a6f4ab..844eaa99 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -68,6 +68,7 @@ "os", "process", "shell", + "store", "updater" ] }, @@ -90,6 +91,7 @@ "os-js", "process-js", "shell-js", + "store-js", "updater-js" ], "postversion": "pnpm install --no-frozen-lockfile" diff --git a/.changes/store-remove-mobile-plugin.md b/.changes/store-remove-mobile-plugin.md new file mode 100644 index 00000000..64baadec --- /dev/null +++ b/.changes/store-remove-mobile-plugin.md @@ -0,0 +1,5 @@ +--- +"store": patch:breaking +--- + +Implement mobile support in Rust directly. This changes the store directories, invalidating all previously generated stores. diff --git a/Cargo.lock b/Cargo.lock index 20472251..99c7da52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,6 +227,7 @@ dependencies = [ "tauri-plugin-os", "tauri-plugin-process", "tauri-plugin-shell", + "tauri-plugin-store", "tauri-plugin-updater", "tiny_http", "window-shadows", diff --git a/examples/api/package.json b/examples/api/package.json index 8d28c501..51083ba2 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -23,6 +23,7 @@ "@tauri-apps/plugin-os": "2.0.0-rc.0", "@tauri-apps/plugin-process": "2.0.0-rc.0", "@tauri-apps/plugin-shell": "2.0.0-rc.0", + "@tauri-apps/plugin-store": "2.0.0-rc.0", "@tauri-apps/plugin-updater": "2.0.0-rc.0", "@zerodevx/svelte-json-view": "1.0.9" }, diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 5ee22765..8a9eaa98 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -34,6 +34,7 @@ tauri-plugin-notification = { path = "../../../plugins/notification", version = tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.0-rc.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.0-rc.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0-rc.2" } +tauri-plugin-store = { path = "../../../plugins/store", version = "2.0.0-rc.2" } [dependencies.tauri] workspace = true diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json index 1a030079..b76e898c 100644 --- a/examples/api/src-tauri/capabilities/base.json +++ b/examples/api/src-tauri/capabilities/base.json @@ -78,6 +78,11 @@ } ], "deny": ["$APPDATA/db/*.stronghold"] - } + }, + "store:allow-entries", + "store:allow-get", + "store:allow-set", + "store:allow-save", + "store:allow-load" ] } diff --git a/examples/api/src-tauri/gen/schemas/desktop-schema.json b/examples/api/src-tauri/gen/schemas/desktop-schema.json index fd3e2e34..ae911020 100644 --- a/examples/api/src-tauri/gen/schemas/desktop-schema.json +++ b/examples/api/src-tauri/gen/schemas/desktop-schema.json @@ -7265,6 +7265,181 @@ "shell:deny-stdin-write" ] }, + { + "description": "store:default -> This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", + "type": "string", + "enum": [ + "store:default" + ] + }, + { + "description": "store:allow-clear -> Enables the clear command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-clear" + ] + }, + { + "description": "store:allow-delete -> Enables the delete command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-delete" + ] + }, + { + "description": "store:allow-entries -> Enables the entries command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-entries" + ] + }, + { + "description": "store:allow-get -> Enables the get command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-get" + ] + }, + { + "description": "store:allow-has -> Enables the has command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-has" + ] + }, + { + "description": "store:allow-keys -> Enables the keys command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-keys" + ] + }, + { + "description": "store:allow-length -> Enables the length command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-length" + ] + }, + { + "description": "store:allow-load -> Enables the load command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-load" + ] + }, + { + "description": "store:allow-reset -> Enables the reset command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-reset" + ] + }, + { + "description": "store:allow-save -> Enables the save command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-save" + ] + }, + { + "description": "store:allow-set -> Enables the set command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-set" + ] + }, + { + "description": "store:allow-values -> Enables the values command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-values" + ] + }, + { + "description": "store:deny-clear -> Denies the clear command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-clear" + ] + }, + { + "description": "store:deny-delete -> Denies the delete command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-delete" + ] + }, + { + "description": "store:deny-entries -> Denies the entries command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-entries" + ] + }, + { + "description": "store:deny-get -> Denies the get command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-get" + ] + }, + { + "description": "store:deny-has -> Denies the has command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-has" + ] + }, + { + "description": "store:deny-keys -> Denies the keys command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-keys" + ] + }, + { + "description": "store:deny-length -> Denies the length command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-length" + ] + }, + { + "description": "store:deny-load -> Denies the load command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-load" + ] + }, + { + "description": "store:deny-reset -> Denies the reset command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-reset" + ] + }, + { + "description": "store:deny-save -> Denies the save command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-save" + ] + }, + { + "description": "store:deny-set -> Denies the set command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-set" + ] + }, + { + "description": "store:deny-values -> Denies the values command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-values" + ] + }, { "description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n", "type": "string", diff --git a/examples/api/src-tauri/gen/schemas/mobile-schema.json b/examples/api/src-tauri/gen/schemas/mobile-schema.json index ba399e4d..1cce6dd1 100644 --- a/examples/api/src-tauri/gen/schemas/mobile-schema.json +++ b/examples/api/src-tauri/gen/schemas/mobile-schema.json @@ -7341,6 +7341,181 @@ "enum": [ "shell:deny-stdin-write" ] + }, + { + "description": "store:default -> This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", + "type": "string", + "enum": [ + "store:default" + ] + }, + { + "description": "store:allow-clear -> Enables the clear command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-clear" + ] + }, + { + "description": "store:allow-delete -> Enables the delete command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-delete" + ] + }, + { + "description": "store:allow-entries -> Enables the entries command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-entries" + ] + }, + { + "description": "store:allow-get -> Enables the get command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-get" + ] + }, + { + "description": "store:allow-has -> Enables the has command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-has" + ] + }, + { + "description": "store:allow-keys -> Enables the keys command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-keys" + ] + }, + { + "description": "store:allow-length -> Enables the length command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-length" + ] + }, + { + "description": "store:allow-load -> Enables the load command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-load" + ] + }, + { + "description": "store:allow-reset -> Enables the reset command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-reset" + ] + }, + { + "description": "store:allow-save -> Enables the save command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-save" + ] + }, + { + "description": "store:allow-set -> Enables the set command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-set" + ] + }, + { + "description": "store:allow-values -> Enables the values command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:allow-values" + ] + }, + { + "description": "store:deny-clear -> Denies the clear command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-clear" + ] + }, + { + "description": "store:deny-delete -> Denies the delete command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-delete" + ] + }, + { + "description": "store:deny-entries -> Denies the entries command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-entries" + ] + }, + { + "description": "store:deny-get -> Denies the get command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-get" + ] + }, + { + "description": "store:deny-has -> Denies the has command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-has" + ] + }, + { + "description": "store:deny-keys -> Denies the keys command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-keys" + ] + }, + { + "description": "store:deny-length -> Denies the length command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-length" + ] + }, + { + "description": "store:deny-load -> Denies the load command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-load" + ] + }, + { + "description": "store:deny-reset -> Denies the reset command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-reset" + ] + }, + { + "description": "store:deny-save -> Denies the save command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-save" + ] + }, + { + "description": "store:deny-set -> Denies the set command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-set" + ] + }, + { + "description": "store:deny-values -> Denies the values command without any pre-configured scope.", + "type": "string", + "enum": [ + "store:deny-values" + ] } ] }, diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 65c6ab68..429054eb 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -37,6 +37,7 @@ pub fn run() { .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_store::Builder::default().build()) .setup(move |app| { #[cfg(desktop)] { diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index 8ffcb104..a4b6334d 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -14,6 +14,7 @@ import Notifications from "./views/Notifications.svelte"; import Shortcuts from "./views/Shortcuts.svelte"; import Shell from "./views/Shell.svelte"; + import Store from "./views/Store.svelte"; import Updater from "./views/Updater.svelte"; import Clipboard from "./views/Clipboard.svelte"; import WebRTC from "./views/WebRTC.svelte"; @@ -90,6 +91,11 @@ component: Shell, icon: "i-codicon-terminal-bash", }, + { + label: "Store", + component: Store, + icon: "i-codicon-file-code", + }, !isMobile && { label: "Updater", component: Updater, diff --git a/examples/api/src/views/Shell.svelte b/examples/api/src/views/Shell.svelte index 5f5e2d25..faaa8c4c 100644 --- a/examples/api/src/views/Shell.svelte +++ b/examples/api/src/views/Shell.svelte @@ -59,7 +59,7 @@ } function writeToStdin() { - child.write(stdin).catch(onMessage); + child.write(`${stdin}\n`).catch(onMessage); } diff --git a/examples/api/src/views/Store.svelte b/examples/api/src/views/Store.svelte new file mode 100644 index 00000000..d8e6653b --- /dev/null +++ b/examples/api/src/views/Store.svelte @@ -0,0 +1,55 @@ + + +
+
+
+ Key: + +
+ +
+ Value: + +
+ + +
+ +
+ {#each Object.entries(cache) as [k, v]} +
{k} = {v}
+ {/each} +
+
diff --git a/plugins/store/android/.gitignore b/plugins/store/android/.gitignore deleted file mode 100644 index c0f21ec2..00000000 --- a/plugins/store/android/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/.tauri diff --git a/plugins/store/android/build.gradle.kts b/plugins/store/android/build.gradle.kts deleted file mode 100644 index b548022b..00000000 --- a/plugins/store/android/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") -} - -android { - namespace = "app.tauri.store" - compileSdk = 34 - - defaultConfig { - minSdk = 24 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } -} - -dependencies { - implementation("androidx.core:core-ktx:1.9.0") - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") - implementation(project(":tauri-android")) -} diff --git a/plugins/store/android/proguard-rules.pro b/plugins/store/android/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/plugins/store/android/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/plugins/store/android/settings.gradle b/plugins/store/android/settings.gradle deleted file mode 100644 index 14a752e4..00000000 --- a/plugins/store/android/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':tauri-android' -project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/plugins/store/android/src/main/AndroidManifest.xml b/plugins/store/android/src/main/AndroidManifest.xml deleted file mode 100644 index 9a40236b..00000000 --- a/plugins/store/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/plugins/store/android/src/main/java/StorePlugin.kt b/plugins/store/android/src/main/java/StorePlugin.kt deleted file mode 100644 index 8389661f..00000000 --- a/plugins/store/android/src/main/java/StorePlugin.kt +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -package app.tauri.store - -import android.app.Activity -import app.tauri.annotation.Command -import app.tauri.annotation.TauriPlugin -import app.tauri.plugin.Invoke -import app.tauri.plugin.Plugin -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import java.io.File - -@TauriPlugin -class StorePlugin(private val activity: Activity) : Plugin(activity) { - @Command - fun load(invoke: Invoke) { - try { - val path = invoke.parseArgs(String::class.java) - val file = File(activity.applicationContext.getExternalFilesDir(null), path) - - invoke.resolveObject(ObjectMapper().readTree(file)) - } catch (ex: Exception) { - invoke.reject(ex.message) - } - } - - @Command - fun save(invoke: Invoke) { - try { - val args = invoke.parseArgs(JsonNode::class.java) - val path = args.get("store").asText() - val cache = args.get("cache") - val file = File(activity.applicationContext.getExternalFilesDir(null), path) - - if (!file.exists()) { - file.parentFile?.mkdirs() - file.createNewFile() - } - - file.writeText(cache.toString()) - - invoke.resolve() - } catch (ex: Exception) { - invoke.reject(ex.message) - } - } -} \ No newline at end of file diff --git a/plugins/store/build.rs b/plugins/store/build.rs index d9bac3bf..7b54fe42 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -10,7 +10,5 @@ const COMMANDS: &[&str] = &[ fn main() { tauri_plugin::Builder::new(COMMANDS) .global_api_script_path("./api-iife.js") - .android_path("android") - .ios_path("ios") .build(); } diff --git a/plugins/store/ios/Package.resolved b/plugins/store/ios/Package.resolved deleted file mode 100644 index 5f998e0e..00000000 --- a/plugins/store/ios/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "SwiftRs", - "repositoryURL": "https://github.com/Brendonovich/swift-rs", - "state": { - "branch": null, - "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e", - "version": "1.0.6" - } - } - ] - }, - "version": 1 -} diff --git a/plugins/store/ios/Package.swift b/plugins/store/ios/Package.swift deleted file mode 100644 index 51ba6bf6..00000000 --- a/plugins/store/ios/Package.swift +++ /dev/null @@ -1,34 +0,0 @@ -// swift-tools-version:5.3 -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -import PackageDescription - -let package = Package( - name: "tauri-plugin-store", - platforms: [ - .macOS(.v10_13), - .iOS(.v13), - ], - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "tauri-plugin-store", - type: .static, - targets: ["tauri-plugin-store"]) - ], - dependencies: [ - .package(name: "Tauri", path: "../.tauri/tauri-api") - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "tauri-plugin-store", - dependencies: [ - .byName(name: "Tauri") - ], - path: "Sources") - ] -) diff --git a/plugins/store/ios/Sources/StorePlugin.swift b/plugins/store/ios/Sources/StorePlugin.swift deleted file mode 100644 index 4f651a5e..00000000 --- a/plugins/store/ios/Sources/StorePlugin.swift +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -import Foundation - -import SwiftRs -import Tauri -import UIKit -import WebKit - - -struct SaveStore: Codable { - let store: String - let cache: [String: JSON] -} - -class StorePlugin: Plugin { - @objc public func save(_ invoke: Invoke) throws { - do { - let args = try invoke.parseArgs(SaveStore.self) - let store = args.store - let cache = args.cache - let fileURL = getUrlFromPath(path: store, createDirs: true) - - try JSONEncoder().encode(cache).write(to: fileURL) - invoke.resolve() - } catch { - invoke.reject(error.localizedDescription) - } - } - - @objc public func load(_ invoke: Invoke) throws { - do { - let path = try invoke.parseArgs(String.self) - let fileURL = getUrlFromPath(path: path, createDirs: false) - let data = try String(contentsOf: fileURL) - let passData = dictionary(text: data) - - invoke.resolve(passData) - } catch { - invoke.reject(error.localizedDescription) - } - } - - func dictionary(text: String) -> [String: Any?] { - if let data = text.data(using: .utf8) { - do { - return try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] - } catch { - fatalError(error.localizedDescription) - } - } - - return [:] - } - - func getUrlFromPath(path: String, createDirs: Bool) -> URL { - do { - var url = try FileManager.default - .url( - for: .applicationSupportDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true - ) - let components = path.split(separator: "/").map { element in String(element) } - - if components.count == 1 { - return url.appendPath(path: path, isDirectory: false) - } - - for i in 0.. 1 && createDirs { - try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) - } - - url = url.appendPath(path: components.last!, isDirectory: false) - - return url - } catch { - fatalError(error.localizedDescription) - } - } -} - - -@_cdecl("init_plugin_store") -func initPlugin() -> Plugin { - return StorePlugin() -} - -private extension URL { - func appendPath(path: String, isDirectory: Bool) -> URL { - if #available(iOS 16.0, *) { - return self.appending(path: path, directoryHint: isDirectory ? .isDirectory : .notDirectory) - } else { - return self.appendingPathComponent(path, isDirectory: isDirectory) - } - } -} - -public enum JSON : Codable { - case null - case number(NSNumber) - case string(String) - case array([JSON]) - case bool(Bool) - case dictionary([String : JSON]) - - public var value: Any? { - switch self { - case .null: return nil - case .number(let number): return number - case .string(let string): return string - case .bool(let bool): return bool - case .array(let array): return array.map { $0.value } - case .dictionary(let dictionary): return dictionary.mapValues { $0.value } - } - } - - public init?(_ value: Any?) { - guard let value = value else { - self = .null - return - } - - if let bool = value as? Bool { - self = .bool(bool) - } else if let int = value as? Int { - self = .number(NSNumber(value: int)) - } else if let double = value as? Double { - self = .number(NSNumber(value: double)) - } else if let string = value as? String { - self = .string(string) - } else if let array = value as? [Any] { - var mapped = [JSON]() - for inner in array { - guard let inner = JSON(inner) else { - return nil - } - - mapped.append(inner) - } - - self = .array(mapped) - } else if let dictionary = value as? [String : Any] { - var mapped = [String : JSON]() - for (key, inner) in dictionary { - guard let inner = JSON(inner) else { - return nil - } - - mapped[key] = inner - } - - self = .dictionary(mapped) - } else { - return nil - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - guard !container.decodeNil() else { - self = .null - return - } - - if let bool = try container.decodeIfMatched(Bool.self) { - self = .bool(bool) - } else if let int = try container.decodeIfMatched(Int.self) { - self = .number(NSNumber(value: int)) - } else if let double = try container.decodeIfMatched(Double.self) { - self = .number(NSNumber(value: double)) - } else if let string = try container.decodeIfMatched(String.self) { - self = .string(string) - } else if let array = try container.decodeIfMatched([JSON].self) { - self = .array(array) - } else if let dictionary = try container.decodeIfMatched([String : JSON].self) { - self = .dictionary(dictionary) - } else { - throw DecodingError.typeMismatch(JSON.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to decode JSON as any of the possible types.")) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .null: try container.encodeNil() - case .bool(let bool): try container.encode(bool) - case .number(let number): - if number.objCType.pointee == 0x64 /* 'd' */ { - try container.encode(number.doubleValue) - } else { - try container.encode(number.intValue) - } - case .string(let string): try container.encode(string) - case .array(let array): try container.encode(array) - case .dictionary(let dictionary): try container.encode(dictionary) - } - } -} - -fileprivate extension SingleValueDecodingContainer { - func decodeIfMatched(_ type: T.Type) throws -> T? { - do { - return try self.decode(T.self) - } catch DecodingError.typeMismatch { - return nil - } - } -} diff --git a/plugins/store/src/desktop.rs b/plugins/store/src/desktop.rs deleted file mode 100644 index 3e98080e..00000000 --- a/plugins/store/src/desktop.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::Error; -use crate::Runtime; -use crate::Store; -use std::fs::create_dir_all; -use std::fs::read; -use std::fs::File; -use std::io::Write; -use tauri::Manager; - -#[cfg(desktop)] -impl Store { - pub fn save(&self) -> Result<(), Error> { - let app_dir = self - .app - .path() - .app_data_dir() - .expect("failed to resolve app dir"); - let store_path = app_dir.join(&self.path); - - create_dir_all(store_path.parent().expect("invalid store path"))?; - - let bytes = (self.serialize)(&self.cache).map_err(Error::Serialize)?; - let mut f = File::create(&store_path)?; - f.write_all(&bytes)?; - - Ok(()) - } - - /// Update the store from the on-disk state - pub fn load(&mut self) -> Result<(), Error> { - let app_dir = self - .app - .path() - .app_data_dir() - .expect("failed to resolve app dir"); - let store_path = app_dir.join(&self.path); - - let bytes = read(store_path)?; - - self.cache - .extend((self.deserialize)(&bytes).map_err(Error::Deserialize)?); - - Ok(()) - } -} diff --git a/plugins/store/src/error.rs b/plugins/store/src/error.rs index d8ce9bb5..afd43add 100644 --- a/plugins/store/src/error.rs +++ b/plugins/store/src/error.rs @@ -11,13 +11,6 @@ pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { - #[cfg(mobile)] - #[error(transparent)] - PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), - /// Mobile plugin handled is not initialized, Probably [`StoreBuilder::mobile_plugin_handle`] was not called. - #[cfg(mobile)] - #[error("Mobile plugin handled is not initialized, Perhaps you forgot to call StoreBuilder::mobile_plugin_handle")] - MobilePluginHandleUnInitialized, #[error("Failed to serialize store. {0}")] Serialize(Box), #[error("Failed to deserialize store. {0}")] diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index f7c9eb0d..fa331eb5 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -29,18 +29,6 @@ use tauri::{ mod error; mod store; -#[cfg(mobile)] -mod mobile; -#[cfg(mobile)] -use crate::plugin::PluginHandle; -#[cfg(target_os = "android")] -const PLUGIN_IDENTIFIER: &str = "app.tauri.store"; -#[cfg(target_os = "ios")] -tauri::ios_plugin_binding!(init_plugin_store); - -#[cfg(desktop)] -mod desktop; - #[derive(Serialize, Clone)] struct ChangePayload<'a> { path: &'a Path, @@ -51,9 +39,6 @@ struct ChangePayload<'a> { pub struct StoreCollection { stores: Mutex>>, frozen: bool, - - #[cfg(mobile)] - mobile_plugin_handle: PluginHandle, } pub fn with_store) -> Result>( @@ -73,11 +58,6 @@ pub fn with_store) -> Result>( #[allow(unused_mut)] let mut builder = StoreBuilder::new(path); - #[cfg(mobile)] - { - builder = builder.mobile_plugin_handle(collection.mobile_plugin_handle.clone()); - } - let mut store = builder.build(app); // ignore loading errors, just use the default @@ -329,17 +309,9 @@ impl Builder { } } - #[cfg(target_os = "android")] - let handle = _api.register_android_plugin(PLUGIN_IDENTIFIER, "StorePlugin")?; - #[cfg(target_os = "ios")] - let handle = _api.register_ios_plugin(init_plugin_store)?; - app_handle.manage(StoreCollection { stores: Mutex::new(self.stores), frozen: self.frozen, - - #[cfg(mobile)] - mobile_plugin_handle: handle, }); Ok(()) diff --git a/plugins/store/src/mobile.rs b/plugins/store/src/mobile.rs deleted file mode 100644 index 7d999fb4..00000000 --- a/plugins/store/src/mobile.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2019-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use tauri::Runtime; - -use crate::error::Result; -use crate::Store; -use serde_json::Value; -use std::collections::HashMap; - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LoadStore { - pub cache: HashMap, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SaveStore { - pub store: String, - pub cache: HashMap, -} - -#[cfg(mobile)] -impl Store { - pub fn save(&self) -> Result<()> { - self.mobile_plugin_handle - .as_ref() - .ok_or_else(|| crate::error::Error::MobilePluginHandleUnInitialized)? - .run_mobile_plugin( - "save", - SaveStore { - store: self.path.to_string_lossy().to_string(), - cache: self.cache.clone(), - }, - ) - .map_err(Into::into) - } - - pub fn load(&mut self) -> Result<()> { - let result: Value = self - .mobile_plugin_handle - .as_ref() - .ok_or_else(|| crate::error::Error::MobilePluginHandleUnInitialized)? - .run_mobile_plugin("load", self.path.to_string_lossy().to_string())?; - - let map = serde_json::from_value::>(result)?; - self.cache.extend(map); - - Ok(()) - } -} diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 503cae76..08184439 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#[cfg(mobile)] -use crate::plugin::PluginHandle; use crate::{ChangePayload, Error}; use serde_json::Value as JsonValue; use std::{ collections::HashMap, + fs::{create_dir_all, read, File}, + io::Write, path::{Path, PathBuf}, }; -use tauri::{AppHandle, Emitter, Runtime}; +use tauri::{AppHandle, Emitter, Manager, Runtime}; type SerializeFn = fn(&HashMap) -> Result, Box>; @@ -30,20 +30,15 @@ fn default_deserialize( } /// Builds a [`Store`] -pub struct StoreBuilder { +pub struct StoreBuilder { path: PathBuf, defaults: Option>, cache: HashMap, serialize: SerializeFn, deserialize: DeserializeFn, - - #[cfg(mobile)] - mobile_plugin_handle: Option>, - #[cfg(not(mobile))] - _marker: std::marker::PhantomData, } -impl StoreBuilder { +impl StoreBuilder { /// Creates a new [`StoreBuilder`]. /// /// # Examples @@ -64,19 +59,9 @@ impl StoreBuilder { cache: Default::default(), serialize: default_serialize, deserialize: default_deserialize, - #[cfg(mobile)] - mobile_plugin_handle: None, - #[cfg(not(mobile))] - _marker: std::marker::PhantomData, } } - #[cfg(mobile)] - pub fn mobile_plugin_handle(mut self, handle: PluginHandle) -> Self { - self.mobile_plugin_handle = Some(handle); - self - } - /// Inserts a default key-value pair. /// /// # Examples @@ -164,7 +149,7 @@ impl StoreBuilder { /// Ok(()) /// }); /// ``` - pub fn build(self, app: AppHandle) -> Store { + pub fn build(self, app: AppHandle) -> Store { Store { app, path: self.path, @@ -172,9 +157,6 @@ impl StoreBuilder { cache: self.cache, serialize: self.serialize, deserialize: self.deserialize, - - #[cfg(mobile)] - mobile_plugin_handle: self.mobile_plugin_handle, } } } @@ -187,12 +169,43 @@ pub struct Store { pub(crate) cache: HashMap, pub(crate) serialize: SerializeFn, pub(crate) deserialize: DeserializeFn, - - #[cfg(mobile)] - pub(crate) mobile_plugin_handle: Option>, } impl Store { + pub fn save(&self) -> Result<(), Error> { + let app_dir = self + .app + .path() + .app_data_dir() + .expect("failed to resolve app dir"); + let store_path = app_dir.join(&self.path); + + create_dir_all(store_path.parent().expect("invalid store path"))?; + + let bytes = (self.serialize)(&self.cache).map_err(Error::Serialize)?; + let mut f = File::create(&store_path)?; + f.write_all(&bytes)?; + + Ok(()) + } + + /// Update the store from the on-disk state + pub fn load(&mut self) -> Result<(), Error> { + let app_dir = self + .app + .path() + .app_data_dir() + .expect("failed to resolve app dir"); + let store_path = app_dir.join(&self.path); + + let bytes = read(store_path)?; + + self.cache + .extend((self.deserialize)(&bytes).map_err(Error::Deserialize)?); + + Ok(()) + } + pub fn insert(&mut self, key: String, value: JsonValue) -> Result<(), Error> { self.cache.insert(key.clone(), value.clone()); self.app.emit( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e83b058..3fde875a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: '@tauri-apps/plugin-shell': specifier: 2.0.0-rc.0 version: link:../../plugins/shell + '@tauri-apps/plugin-store': + specifier: 2.0.0-rc.0 + version: link:../../plugins/store '@tauri-apps/plugin-updater': specifier: 2.0.0-rc.0 version: link:../../plugins/updater