omega restructuring + a bunch of changes i have made over the last weeks

This commit is contained in:
Sebastian Moser
2026-07-31 00:48:15 +02:00
parent c57849f748
commit b2ed9efff2
201 changed files with 4331 additions and 49073 deletions

1
.envrc
View File

@@ -1 +0,0 @@
use flake . --impure

View File

@@ -16,7 +16,7 @@ packages/
## Key Components ## Key Components
To understand the main components of the architecture you canr efer to `architecture.md`. To understand the main components of the architecture you can refer to `docs/architecture.md`.
## Require clarification and plan approval before making code changes ## Require clarification and plan approval before making code changes

9319
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
[workspace]
members = [
"packages/ac_mize_macros",
"packages/marts",
"packages/mize",
"packages/ppc",
"packages/spacetime",
"packages/generated",
]
resolver = "2"

View File

@@ -0,0 +1,10 @@
{
"id": "mize",
"name": "Mize",
"version": "0.1.0",
"minAppVersion": "1.5.0",
"main": "main.js",
"author": "c2vi",
"isDesktopOnly": true,
"description": "Turns Obsidian into a full Mize Explorer"
}

View File

@@ -0,0 +1,61 @@
use dioxus::prelude::*;
use marts::ClientAuth;
use mize::Mize;
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use mize::{MizeResult, mize_part, MizePart, MizePartCreate, error::IntoMizeResultJsValue};
use mize::platform::js::JsMize;
// console_log macro
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
}
#[wasm_bindgen]
pub fn test_fn(mize: &mut JsMize) {
let mize = mize.inner();
console_log!("test fn...");
if let Err(e) = try_test_fn(mize) {
console_log!("err: {:?}", e);
}
}
fn try_test_fn(mize: &mut Mize) -> MizeResult<()> {
//let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
//console_log!("secrets: {:?}", auth.secrets());
let parts = mize.parts.lock().unwrap();
for part in parts.keys() {
console_log!("part: {}", part)
}
Ok(())
}
#[wasm_bindgen]
pub fn part_obsidian_init(mize: &mut JsMize, plugin: JsValue, app: JsValue) {
let mut mize = mize.inner();
if let Err(e) = obsidian_mize_main(&mut mize, plugin, app) {
console_log!("Failed to start the obsidian mize part: {:?}", e);
}
}
fn obsidian_mize_main(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
marts::part_obsidian(mize, plugin, app)?;
marts::part_client_auth(mize)?;
marts::spacetime(mize)?;
Ok(())
}

View File

@@ -0,0 +1,173 @@
import { Plugin, ItemView, TFile, Modal } from "obsidian";
import wasmBinary from "./rust_dist/ppc_bg.wasm";
import * as wasmNamespace from "./rust_dist/ppc";
import { c2vi_obsidian_canvas_patch } from "@ppc/marts";
import { task_obsidian_otask_parse } from "@ppc/marts";
class Mize {
jsInstance: wasmNamespace.JsMize;
jsInstancePtr: number;
wasmModule: any;
constructor(wasmModule: any, jsInstance: wasmNamespace.JsMize) {
this.jsInstance = jsInstance
this.jsInstancePtr = jsInstance.__wbg_ptr;
this.wasmModule = wasmModule
}
test() {
this.wasmModule.test_fn(this.jsInstance);
}
get_part_native(name: string) {
if (name == "obsidian") {
return this.obsidianPlugin
}
}
}
async function mize_init() {
// init the wasm_bindgen module
const wasmModule = await import("./rust_dist/ppc.js");
await wasmModule.default({ module_or_path: wasmBinary });
wasmModule.initSync();
const jsInstance = await wasmModule.new_js_instance(JSON.stringify({
auth: {
client: {
issuer: "https://auth.ppc.social",
client_id: "363768685120258051",
},
},
}));
jsInstance.init();
console.log("jsInstance: ", jsInstance)
let mize = new Mize(wasmModule, jsInstance);
console.log("jsInstance: ", jsInstance)
console.log("mize.jsInstance: ", mize.jsInstance)
// set global mize
window.mize = mize;
console.log("window.mize.jsInstance: ", window.mize.jsInstance)
return mize;
}
export default class MizeObsidianPlugin extends Plugin {
async onload() {
let mize = await mize_init();
console.log("mize.jsInstance: ", mize.jsInstance)
mize.wasmModule.part_obsidian_init(mize.jsInstance, this, this.app);
console.log("mize.jsInstance: ", mize.jsInstance)
mize.obsidianPlugin = this;
console.log("mize.jsInstance: ", mize.jsInstance)
// c2vi part which patches canvas movement
c2vi_obsidian_canvas_patch(mize);
task_obsidian_otask_parse(mize);
this.registerView("test-view", (leaf) => new TestView(leaf, this));
this.addCommand({
id: "mize-test-view",
name: "Mize TestView",
callback: async () => {
const leaf = this.app.workspace.getLeaf(true);
await leaf.setViewState({
type: "test-view",
active: true,
});
this.app.workspace.revealLeaf(leaf);
},
});
}
onunload() {
this.app.workspace.detachLeavesOfType("ppc-app-view");
}
showDeviceCode(code: string, verificationUri: string) {
const modal = new Modal(this.app);
modal.onOpen = () => {
const { contentEl } = modal;
contentEl.createEl("h2", { text: "Device Login" });
// URL
contentEl.createEl("p", {
text: "Open this URL:"
});
const urlContainer = contentEl.createDiv({
cls: "device-login-row"
});
const urlInput = urlContainer.createEl("input", {
type: "text",
value: verificationUri,
});
urlInput.readOnly = true;
urlInput.style.width = "100%";
urlContainer.createEl("button", {
text: "Copy"
}).onclick = async () => {
await navigator.clipboard.writeText(verificationUri);
new Notice("URL copied to clipboard");
};
// Code
contentEl.createEl("p", {
text: "Enter this code:"
});
const codeInput = contentEl.createEl("input", {
type: "text",
value: code,
});
codeInput.readOnly = true;
codeInput.style.width = "100%";
codeInput.select();
};
modal.open();
}
}
export class TestView extends ItemView {
constructor(leaf: WorkspaceLeaf, plugin: PPCObsidianPlugin) {
super(leaf);
this.plugin = plugin;
}
getViewType(): string {
return "ppc-app-view";
}
getDisplayText(): string {
return "PPC App";
}
override async onOpen() {
this.root = document.createElement("div");
this.root.id = "ppc-app-root";
this.containerEl.children[1].appendChild(this.root);
this.plugin.mize.wasmModule.show_main_ui();
}
override async onClose() {
this.root.remove();
}
}

View File

@@ -1,48 +0,0 @@
# Source code architecture
## 1. Core Mize Framework (`packages/mize/`)
**Architecture layers:**
- `core/`: Platform-independent logic
- `config/`: Configuration management
- `error/`: Error handling
- `id/`: Unique identifier system
- `instance/`: Mize runtime instances
- `item/`: Filesystem item abstractions
- `memstore/`: In-memory storage
- `proto/`: Protocol definitions
- `types/`: Core type definitions
- `platform/`: Platform-specific implementations
- `os/`: Native OS integration
- `wasm/`: WebAssembly support
- `bare/`: Bare metal targets
- `jvm/`: Java Virtual Machine integration
### 2. Marts (`packages/marts/`)
- **Language**: Rust + TypeScript
- **Purpose**: Collection of Mize framework parts and utilities
- **Key features**:
- CLI tools
- JavaScript/TypeScript integration
- Habitica integration
- Deno-based scripting support
## 3. MME - Mize Module Environment (`packages/mme/`)
- **Language**: Rust
- **Purpose**: Module execution environment
- **Key features**:
- Cross-platform module loading
- Qt and Slint UI support
- Web view integration (tao/wry)
- Command system (comandr)
## 4. PPC - Platform Components (`packages/ppc/`)
- **Language**: Rust + TypeScript
- **Purpose**: Platform-specific implementations
- **Targets**: Obsidian plugin, OS integration
## 5. Vic - Victor CLI (`packages/vic/`)
- **Language**: Rust
- **Purpose**: Command-line interface tool
- **Features**: Build, run, test, and manage Mize applications

74
deno.lock generated
View File

@@ -11,6 +11,7 @@
"npm:esbuild@*": "0.27.3", "npm:esbuild@*": "0.27.3",
"npm:esbuild@~0.25.5": "0.25.12", "npm:esbuild@~0.25.5": "0.25.12",
"npm:esbuild@~0.27.3": "0.27.3", "npm:esbuild@~0.27.3": "0.27.3",
"npm:matrix-js-sdk@42": "42.0.0",
"npm:mdast@3": "3.0.0", "npm:mdast@3": "3.0.0",
"npm:obsidian@^1.12.2": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6", "npm:obsidian@^1.12.2": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6",
"npm:obsidian@^1.12.3": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6", "npm:obsidian@^1.12.3": "1.12.3_@codemirror+state@6.5.0_@codemirror+view@6.38.6",
@@ -45,6 +46,9 @@
} }
}, },
"npm": { "npm": {
"@babel/runtime@8.0.0": {
"integrity": "sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw=="
},
"@codemirror/state@6.5.0": { "@codemirror/state@6.5.0": {
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
"dependencies": [ "dependencies": [
@@ -332,6 +336,9 @@
"@marijn/find-cluster-break@1.0.2": { "@marijn/find-cluster-break@1.0.2": {
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
}, },
"@matrix-org/matrix-sdk-crypto-wasm@18.3.1": {
"integrity": "sha512-VRjWhE1UgHnPpJ3b9B5+8z71ZC/HICFngPPFIN6ktzmUBKI5RusPujzbAQUoB3CgZ0yU58L99AfSQS4YTztSWw=="
},
"@types/codemirror@5.60.8": { "@types/codemirror@5.60.8": {
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
"dependencies": [ "dependencies": [
@@ -347,6 +354,9 @@
"@types/estree@1.0.8": { "@types/estree@1.0.8": {
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
}, },
"@types/events@3.0.3": {
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g=="
},
"@types/mdast@4.0.4": { "@types/mdast@4.0.4": {
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
"dependencies": [ "dependencies": [
@@ -365,9 +375,21 @@
"@types/unist@3.0.3": { "@types/unist@3.0.3": {
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
}, },
"another-json@0.2.0": {
"integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg=="
},
"bail@2.0.2": { "bail@2.0.2": {
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
}, },
"base-x@5.0.1": {
"integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="
},
"bs58@6.0.0": {
"integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
"dependencies": [
"base-x"
]
},
"builtin-modules@5.0.0": { "builtin-modules@5.0.0": {
"integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==" "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg=="
}, },
@@ -377,6 +399,9 @@
"character-entities@2.0.2": { "character-entities@2.0.2": {
"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
}, },
"content-type@2.0.0": {
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="
},
"crelt@1.0.6": { "crelt@1.0.6": {
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
}, },
@@ -470,6 +495,9 @@
"escape-string-regexp@5.0.0": { "escape-string-regexp@5.0.0": {
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
}, },
"events@3.3.0": {
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
},
"extend@3.0.2": { "extend@3.0.2": {
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
}, },
@@ -485,15 +513,47 @@
"github-slugger@2.0.0": { "github-slugger@2.0.0": {
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
}, },
"is-network-error@1.3.2": {
"integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="
},
"is-plain-obj@4.1.0": { "is-plain-obj@4.1.0": {
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
}, },
"loglevel@1.9.2": {
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="
},
"longest-streak@3.1.0": { "longest-streak@3.1.0": {
"integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
}, },
"markdown-table@3.0.4": { "markdown-table@3.0.4": {
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="
}, },
"matrix-events-sdk@0.0.1": {
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
},
"matrix-js-sdk@42.0.0": {
"integrity": "sha512-i3SnhdkgnKA7O0iVlaZCo96GGaIZ0FGD93LQ34N2+Hj/kaC7Ffbch7OEkwYA//mYGCKRAMxduBacoy8WscfqVw==",
"dependencies": [
"@babel/runtime",
"@matrix-org/matrix-sdk-crypto-wasm",
"another-json",
"bs58",
"content-type",
"loglevel",
"matrix-events-sdk",
"matrix-widget-api",
"p-retry",
"sdp-transform",
"unhomoglyph"
]
},
"matrix-widget-api@1.17.0": {
"integrity": "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==",
"dependencies": [
"@types/events",
"events"
]
},
"mdast-util-find-and-replace@3.0.2": { "mdast-util-find-and-replace@3.0.2": {
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
"dependencies": [ "dependencies": [
@@ -886,6 +946,12 @@
"moment" "moment"
] ]
}, },
"p-retry@8.0.0": {
"integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==",
"dependencies": [
"is-network-error"
]
},
"remark-frontmatter@5.0.0": { "remark-frontmatter@5.0.0": {
"integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==",
"dependencies": [ "dependencies": [
@@ -923,6 +989,10 @@
"unified" "unified"
] ]
}, },
"sdp-transform@3.0.0": {
"integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==",
"bin": true
},
"style-mod@4.1.3": { "style-mod@4.1.3": {
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==" "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
}, },
@@ -933,6 +1003,9 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"bin": true "bin": true
}, },
"unhomoglyph@1.0.6": {
"integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg=="
},
"unified@11.0.5": { "unified@11.0.5": {
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
"dependencies": [ "dependencies": [
@@ -1019,6 +1092,7 @@
"npm:@flowershow/remark-wiki-link@^3.4.0", "npm:@flowershow/remark-wiki-link@^3.4.0",
"npm:builtin-modules@5", "npm:builtin-modules@5",
"npm:esbuild@~0.27.3", "npm:esbuild@~0.27.3",
"npm:matrix-js-sdk@42",
"npm:mdast@3", "npm:mdast@3",
"npm:obsidian@^1.12.2", "npm:obsidian@^1.12.2",
"npm:remark-frontmatter@5", "npm:remark-frontmatter@5",

0
mize → docs/architecture.md Executable file → Normal file
View File

6
flake.lock generated
View File

@@ -72,11 +72,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1774709303, "lastModified": 1782467914,
"narHash": "sha256-D3Q07BbIA2KnTcSXIqqu9P586uWxN74zNoCH3h2ESHg=", "narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685", "rev": "e73de5be04e0eff4190a1432b946d469c794e7b4",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,6 +1,5 @@
{ {
inputs = { inputs = {
# nixpkgs 35.11 still contains rust 1.73
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
fenix = { fenix = {
@@ -30,7 +29,6 @@
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
#os
osToolchain = fenix.packages.${system}.latest.toolchain; osToolchain = fenix.packages.${system}.latest.toolchain;
osCrane = (crane.mkLib pkgs).overrideToolchain osToolchain; osCrane = (crane.mkLib pkgs).overrideToolchain osToolchain;
@@ -177,6 +175,10 @@ in {
pkgs.pkg-config pkgs.pkg-config
pkgs.gobject-introspection pkgs.gobject-introspection
pkgs.gobject-introspection.dev pkgs.gobject-introspection.dev
pkgs.systemd
pkgs.rsync
pkgs.deno
pkgs.openssh
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes # thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
(pkgs.buildWasmBindgenCli rec { (pkgs.buildWasmBindgenCli rec {
@@ -233,7 +235,7 @@ in {
]; ];
}; };
default = pkgs.mkShell { default = pkgs.mkShell rec {
MIZE_BUILD_CONFIG = let MIZE_BUILD_CONFIG = let
settingsFormat = pkgs.formats.toml { }; settingsFormat = pkgs.formats.toml { };
@@ -252,6 +254,15 @@ in {
pkgs.pkg-config pkgs.pkg-config
pkgs.gobject-introspection pkgs.gobject-introspection
pkgs.gobject-introspection.dev pkgs.gobject-introspection.dev
pkgs.systemd
pkgs.rsync
pkgs.openssh
pkgs.git
# to build ../grid
pkgs.rustPlatform.bindgenHook
pkgs.pipewire.dev
pkgs.alsa-lib.dev
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes # thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
(pkgs.buildWasmBindgenCli rec { (pkgs.buildWasmBindgenCli rec {
@@ -305,8 +316,18 @@ in {
harfbuzz harfbuzz
librsvg librsvg
libsoup_3 libsoup_3
# to build ../grid
libxkbcommon
alsa-lib
libgcc.lib
libxcb
pipewire
wayland
]; ];
LIBRARY_PATH = LD_LIBRARY_PATH;
}; };
}; };

View File

@@ -9,3 +9,5 @@ proc-macro = true
[dependencies] [dependencies]
quote = "1.0.44" quote = "1.0.44"
syn = "2.0.114" syn = "2.0.114"
proc-macro2 = "1.0"
ciborium = "0.2"

1
macros/src/data.rs Normal file
View File

@@ -0,0 +1 @@

280
macros/src/lib.rs Normal file
View File

@@ -0,0 +1,280 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, parse_macro_input};
#[proc_macro_attribute]
pub fn mize_part(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let struct_name = &input.ident;
// Parse name from attribute
let part_name = if !attr.is_empty() {
let lit: syn::LitStr = parse_macro_input!(attr);
lit.value()
} else {
struct_name.to_string().to_lowercase()
};
// Verify mize field exists
let has_mize = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => f
.named
.iter()
.any(|field| field.ident.as_ref().map(|i| i == "mize").unwrap_or(false)),
_ => false,
},
_ => false,
};
if !has_mize {
return syn::Error::new_spanned(struct_name, "Struct must have a 'mize: Mize' field")
.to_compile_error()
.into();
}
let part_getter_fn_name_str = format!("make_mize_part_{}", part_name);
let part_getter_fn_name = syn::Ident::new(&part_getter_fn_name_str, struct_name.span());
let expanded = quote! {
#input
// impl the default new impl
impl mize::MizePartGenerated for #struct_name {
fn name_generated(&self) -> &'static str {
#part_name
}
fn get_mize_generated(&mut self) -> &mut Mize {
&mut self.mize
}
fn as_any_generated(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut_generated(&mut self) -> &mut dyn std::any::Any {
self
}
fn into_any_generated(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
impl mize::MizePartCreateGenerated for #struct_name {
fn create_generated(mize: Mize) -> Self {
Self {
mize,
.. Default::default()
}
}
}
use mize::instance::MizePartCreateThisSucks;
impl MizePartCreateThisSucks for #struct_name {}
// the function to get the part from a .so file
#[unsafe(no_mangle)]
extern "C" fn #part_getter_fn_name(part: &mut Box<dyn MizePart + Send + Sync>, mut mize: Mize) {
#struct_name::create_this_sucks(&mut mize);
}
/*
impl std::ops::Deref for mize::MizePartGuard<#struct_name> {
type Target = #struct_name;
fn deref(&self) -> &Self::Target {
&*self.part.as_ref().unwrap()
}
}
impl std::ops::DerefMut for mize::MizePartGuard<#struct_name> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.part.as_mut().unwrap()
}
}
*/
};
TokenStream::from(expanded)
}
//////////////////////////////////// data! macro ///////////////////////////////////////
// A procedural macro to define data in Nix-style and expand to a `ciborium::Value` (CBOR).
//
// Add this to your `Cargo.toml`:
// ```toml
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = { version = "2.0", features = ["full", "extra-traits"] }
// quote = "1.0"
// proc-macro2 = "1.0"
// ciborium = "0.2"
// ```
use syn::{Ident, Lit, Token, parse::Parse, parse::ParseStream, punctuated::Punctuated, token};
// --- AST ---
#[derive(Debug)]
enum NixExpr {
Map(Vec<(String, NixExpr)>),
List(Vec<NixExpr>),
Literal(Literal),
}
#[derive(Debug)]
enum Literal {
String(String),
Int(i128),
Float(f64),
Bool(bool),
Null,
}
// --- Code Generation ---
impl NixExpr {
fn to_cbor_tokens(&self) -> proc_macro2::TokenStream {
match self {
NixExpr::Map(entries) => {
let entry_tokens: Vec<_> = entries
.iter()
.map(|(k, v)| {
let key = syn::LitStr::new(k, proc_macro2::Span::call_site());
let value_tokens = v.to_cbor_tokens();
quote! {
(mize::ciborium::Value::Text(#key.to_string()), #value_tokens)
}
})
.collect();
quote! {
mize::ciborium::Value::Map({
let mut m = Vec::<(mize::ciborium::Value, mize::ciborium::Value)>::new();
#(m.push(#entry_tokens);)*
m
})
}
}
NixExpr::List(items) => {
let item_tokens: Vec<_> = items.iter().map(|item| item.to_cbor_tokens()).collect();
quote! {
mize::ciborium::Value::Array({
let mut v = Vec::<mize::ciborium::Value>::new();
#(v.push(#item_tokens);)*
v
})
}
}
NixExpr::Literal(lit) => lit.to_cbor_tokens(),
}
}
}
impl Literal {
fn to_cbor_tokens(&self) -> proc_macro2::TokenStream {
match self {
Literal::String(s) => {
let s = syn::LitStr::new(s, proc_macro2::Span::call_site());
quote! { mize::ciborium::Value::Text(#s.to_string()) }
}
Literal::Int(i) => quote! { mize::ciborium::Value::Integer(#i) },
Literal::Float(f) => quote! { mize::ciborium::Value::Float(#f) },
Literal::Bool(b) => quote! { mize::ciborium::Value::Bool(#b) },
Literal::Null => quote! { mize::ciborium::Value::Null },
}
}
}
// --- Parsing ---
struct NixKeyValue {
key: Ident,
_eq: Token![=],
value: NixExpr,
}
impl Parse for NixKeyValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(NixKeyValue {
key: input.parse()?,
_eq: input.parse()?,
value: input.parse()?,
})
}
}
impl Parse for NixExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Parse a map: { a = 1; b = 2; }
if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
let pairs = Punctuated::<NixKeyValue, Token![;]>::parse_terminated(&content)?;
let entries = pairs
.into_iter()
.map(|kv| (kv.key.to_string(), kv.value))
.collect();
return Ok(NixExpr::Map(entries));
}
// Parse a list: [ 1, 2, 3 ]
if input.peek(token::Bracket) {
let content;
syn::bracketed!(content in input);
let items = Punctuated::<NixExpr, Token![,]>::parse_terminated(&content)?;
return Ok(NixExpr::List(items.into_iter().collect()));
}
// Parse literals
if input.peek(Lit) {
let lit = input.parse::<Lit>()?;
return match lit {
Lit::Str(s) => Ok(NixExpr::Literal(Literal::String(s.value()))),
Lit::Int(i) => Ok(NixExpr::Literal(Literal::Int(i.base10_parse::<i128>()?))),
Lit::Float(f) => Ok(NixExpr::Literal(Literal::Float(f.base10_parse::<f64>()?))),
Lit::Bool(b) => Ok(NixExpr::Literal(Literal::Bool(b.value))),
_ => Err(input.error("Unsupported literal")),
};
}
Err(input.error("Expected a Nix expression (map, list, or literal)"))
}
}
// --- Top-level AttrSet Parsing ---
struct NixAttrSet(Vec<NixKeyValue>);
impl syn::parse::Parse for NixAttrSet {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pairs = Punctuated::<NixKeyValue, Token![;]>::parse_terminated(input)?;
Ok(NixAttrSet(pairs.into_iter().collect()))
}
}
// --- Macro Entry ---
#[proc_macro]
pub fn data(input: TokenStream) -> TokenStream {
// Try to parse top-level attrset (without outer braces)
let expr = syn::parse::<NixAttrSet>(input.clone())
.map(|attrset| {
NixExpr::Map(
attrset.0
.into_iter()
.map(|kv| (kv.key.to_string(), kv.value))
.collect(),
)
})
.or_else(|_| {
// fallback: parse legacy behavior (braced map, list, or literal)
syn::parse::<NixExpr>(input)
});
let tokens = match expr {
Ok(expr) => expr.to_cbor_tokens(),
Err(e) => return e.to_compile_error().into(),
};
TokenStream::from(tokens)
}

50
marts/Cargo.toml Normal file
View File

@@ -0,0 +1,50 @@
[package]
name = "marts"
description = "marts = mize_parts - parts collection of the Mize framework"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.59", optional = true, features = ["string"] }
flume = "0.12.0"
futures = "0.3.32"
mize = { path = "../mize/", default-features = false, features = [] }
tokio = { version = "1.49.0", default-features = false, features = ["time"] }
# https://github.com/rscarson/rustyscript/pull/415
#rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
#deno_core = { version = "0.380.1", optional = true }
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
serde = "1.0.228"
serde_json = "1.0.149"
rusqlite = { version = "0.31", features = ["bundled"], optional = true }
dioxus = { version = "0.7.1", optional = true }
spacetimedb-sdk = { version = "2.6.0", default-features = false }
generated = { path = "./generated" }
webbrowser = { version = "1.2.0", optional = true }
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
tracing = "0.1.40"
wasm-bindgen = { version = "0.2.84", optional = true }
wasm-bindgen-futures = { version = "0.4.49", optional = true }
web-sys = { version = "0.3.70", optional = true, features = [
"Window",
"Storage",
"console"
]}
js-sys = { version = "0.3.70", optional = true }
spacetimedb = { version = "2.6.0", features = ["unstable"] }
log = "0.4"
chrono = { version = "0.4.45", features = [], default-features = false }
chrono-tz = { version = "0.10.4", default-features = false }
url = "2.5.8"
[features]
default = ["target-os", "desktop", "server", "client"]
#default = ["target-js", "target-obsidian"] # to get rust analyzer to analyzer js only parts
target-spacetime = [ "mize/target-js", "spacetimedb-sdk/browser" ]
target-os = [ "clap", "mize/target-os", "rusqlite", "dioxus", "dep:webbrowser" ]
target-js = [ "mize/target-js", "spacetimedb-sdk/browser", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "js-sys", "generated/target-js", "web-sys/Storage", "web-sys/Window" ]
target-obsidian = [ "target-js" ]
desktop = ["dioxus/desktop"]
client = []
server = []
clap = ["dep:clap"]

View File

@@ -2,7 +2,7 @@
"name": "@ppc/marts", "name": "@ppc/marts",
"imports": {}, "imports": {},
"exports": { "exports": {
".": "./index.ts" ".": "./src/index.ts"
}, },
"tasks": {} "tasks": {}
} }

View File

@@ -8,4 +8,7 @@ name = "generated"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
spacetimedb-sdk = "2.1.0" spacetimedb-sdk = "2.6.0"
[features]
target-js = [ "spacetimedb-sdk/browser" ]

View File

@@ -1,2 +1 @@
pub mod spacetime; pub mod spacetime;

View File

@@ -0,0 +1,68 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct AddThoughtArgs {
pub thought: String,
}
impl From<AddThoughtArgs> for super::Reducer {
fn from(args: AddThoughtArgs) -> Self {
Self::AddThought {
thought: args.thought,
}
}
}
impl __sdk::InModule for AddThoughtArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `add_thought`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait add_thought {
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`add_thought:add_thought_then`] to run a callback after the reducer completes.
fn add_thought(&self, thought: String) -> __sdk::Result<()> {
self.add_thought_then(thought, |_, _| {})
}
/// Request that the remote module invoke the reducer `add_thought` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn add_thought_then(
&self,
thought: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}
impl add_thought for super::RemoteReducers {
fn add_thought_then(
&self,
thought: String,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp
.invoke_reducer_with_callback(AddThoughtArgs { thought }, callback)
}
}

View File

@@ -0,0 +1,111 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::daily_task_log_type::DailyTaskLog;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `daily_task_log`.
///
/// Obtain a handle from the [`DailyTaskLogTableAccess::daily_task_log`] method on [`super::RemoteTables`],
/// like `ctx.db.daily_task_log()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.daily_task_log().on_insert(...)`.
pub struct DailyTaskLogTableHandle<'ctx> {
imp: __sdk::TableHandle<DailyTaskLog>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `daily_task_log`.
///
/// Implemented for [`super::RemoteTables`].
pub trait DailyTaskLogTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`DailyTaskLogTableHandle`], which mediates access to the table `daily_task_log`.
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_>;
}
impl DailyTaskLogTableAccess for super::RemoteTables {
fn daily_task_log(&self) -> DailyTaskLogTableHandle<'_> {
DailyTaskLogTableHandle {
imp: self.imp.get_table::<DailyTaskLog>("daily_task_log"),
ctx: std::marker::PhantomData,
}
}
}
pub struct DailyTaskLogInsertCallbackId(__sdk::CallbackId);
pub struct DailyTaskLogDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for DailyTaskLogTableHandle<'ctx> {
type Row = DailyTaskLog;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = DailyTaskLog> + '_ {
self.imp.iter()
}
type InsertCallbackId = DailyTaskLogInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskLogInsertCallbackId {
DailyTaskLogInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: DailyTaskLogInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = DailyTaskLogDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskLogDeleteCallbackId {
DailyTaskLogDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: DailyTaskLogDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<DailyTaskLog>("daily_task_log");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<DailyTaskLog>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<DailyTaskLog>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `DailyTaskLog`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait daily_task_logQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `DailyTaskLog`.
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog>;
}
impl daily_task_logQueryTableAccess for __sdk::QueryTableAccessor {
fn daily_task_log(&self) -> __sdk::__query_builder::Table<DailyTaskLog> {
__sdk::__query_builder::Table::new("daily_task_log")
}
}

View File

@@ -0,0 +1,48 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct DailyTaskLog {
pub daily_task_id: i64,
pub done: __sdk::Timestamp,
}
impl __sdk::InModule for DailyTaskLog {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `DailyTaskLog`.
///
/// Provides typed access to columns for query building.
pub struct DailyTaskLogCols {
pub daily_task_id: __sdk::__query_builder::Col<DailyTaskLog, i64>,
pub done: __sdk::__query_builder::Col<DailyTaskLog, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for DailyTaskLog {
type Cols = DailyTaskLogCols;
fn cols(table_name: &'static str) -> Self::Cols {
DailyTaskLogCols {
daily_task_id: __sdk::__query_builder::Col::new(table_name, "daily_task_id"),
done: __sdk::__query_builder::Col::new(table_name, "done"),
}
}
}
/// Indexed column accessor struct for the table `DailyTaskLog`.
///
/// Provides typed access to indexed columns for query building.
pub struct DailyTaskLogIxCols {}
impl __sdk::__query_builder::HasIxCols for DailyTaskLog {
type IxCols = DailyTaskLogIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
DailyTaskLogIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for DailyTaskLog {}

View File

@@ -0,0 +1,111 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::daily_task_type::DailyTask;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `daily_task`.
///
/// Obtain a handle from the [`DailyTaskTableAccess::daily_task`] method on [`super::RemoteTables`],
/// like `ctx.db.daily_task()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.daily_task().on_insert(...)`.
pub struct DailyTaskTableHandle<'ctx> {
imp: __sdk::TableHandle<DailyTask>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `daily_task`.
///
/// Implemented for [`super::RemoteTables`].
pub trait DailyTaskTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`DailyTaskTableHandle`], which mediates access to the table `daily_task`.
fn daily_task(&self) -> DailyTaskTableHandle<'_>;
}
impl DailyTaskTableAccess for super::RemoteTables {
fn daily_task(&self) -> DailyTaskTableHandle<'_> {
DailyTaskTableHandle {
imp: self.imp.get_table::<DailyTask>("daily_task"),
ctx: std::marker::PhantomData,
}
}
}
pub struct DailyTaskInsertCallbackId(__sdk::CallbackId);
pub struct DailyTaskDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for DailyTaskTableHandle<'ctx> {
type Row = DailyTask;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = DailyTask> + '_ {
self.imp.iter()
}
type InsertCallbackId = DailyTaskInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskInsertCallbackId {
DailyTaskInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: DailyTaskInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = DailyTaskDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> DailyTaskDeleteCallbackId {
DailyTaskDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: DailyTaskDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<DailyTask>("daily_task");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<DailyTask>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<DailyTask>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `DailyTask`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait daily_taskQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `DailyTask`.
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask>;
}
impl daily_taskQueryTableAccess for __sdk::QueryTableAccessor {
fn daily_task(&self) -> __sdk::__query_builder::Table<DailyTask> {
__sdk::__query_builder::Table::new("daily_task")
}
}

View File

@@ -0,0 +1,51 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct DailyTask {
pub id: i64,
pub name: String,
pub owner: __sdk::Identity,
}
impl __sdk::InModule for DailyTask {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `DailyTask`.
///
/// Provides typed access to columns for query building.
pub struct DailyTaskCols {
pub id: __sdk::__query_builder::Col<DailyTask, i64>,
pub name: __sdk::__query_builder::Col<DailyTask, String>,
pub owner: __sdk::__query_builder::Col<DailyTask, __sdk::Identity>,
}
impl __sdk::__query_builder::HasCols for DailyTask {
type Cols = DailyTaskCols;
fn cols(table_name: &'static str) -> Self::Cols {
DailyTaskCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
name: __sdk::__query_builder::Col::new(table_name, "name"),
owner: __sdk::__query_builder::Col::new(table_name, "owner"),
}
}
}
/// Indexed column accessor struct for the table `DailyTask`.
///
/// Provides typed access to indexed columns for query building.
pub struct DailyTaskIxCols {}
impl __sdk::__query_builder::HasIxCols for DailyTask {
type IxCols = DailyTaskIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
DailyTaskIxCols {}
}
}
impl __sdk::__query_builder::CanBeLookupTable for DailyTask {}

View File

@@ -0,0 +1,59 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct ExportTimetrackArgs {
pub username: String,
pub date: String,
}
impl __sdk::InModule for ExportTimetrackArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `export_timetrack`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait export_timetrack {
fn export_timetrack(&self, username: String, date: String) {
self.export_timetrack_then(username, date, |_, _| {});
}
fn export_timetrack_then(
&self,
username: String,
date: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl export_timetrack for super::RemoteProcedures {
fn export_timetrack_then(
&self,
username: String,
date: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
"export_timetrack",
ExportTimetrackArgs { username, date },
__callback,
);
}
}

View File

@@ -0,0 +1,56 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct FirstTimetrackDateArgs {
pub username: String,
}
impl __sdk::InModule for FirstTimetrackDateArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `first_timetrack_date`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait first_timetrack_date {
fn first_timetrack_date(&self, username: String) {
self.first_timetrack_date_then(username, |_, _| {});
}
fn first_timetrack_date_then(
&self,
username: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl first_timetrack_date for super::RemoteProcedures {
fn first_timetrack_date_then(
&self,
username: String,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<Result<String, String>, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, Result<String, String>>(
"first_timetrack_date",
FirstTimetrackDateArgs { username },
__callback,
);
}
}

View File

@@ -13,6 +13,7 @@ pub struct LogEvent {
pub start: __sdk::Timestamp, pub start: __sdk::Timestamp,
pub end: __sdk::Timestamp, pub end: __sdk::Timestamp,
pub action: Action, pub action: Action,
pub user: __sdk::Identity,
} }
impl __sdk::InModule for LogEvent { impl __sdk::InModule for LogEvent {
@@ -27,6 +28,7 @@ pub struct LogEventCols {
pub start: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>, pub start: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
pub end: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>, pub end: __sdk::__query_builder::Col<LogEvent, __sdk::Timestamp>,
pub action: __sdk::__query_builder::Col<LogEvent, Action>, pub action: __sdk::__query_builder::Col<LogEvent, Action>,
pub user: __sdk::__query_builder::Col<LogEvent, __sdk::Identity>,
} }
impl __sdk::__query_builder::HasCols for LogEvent { impl __sdk::__query_builder::HasCols for LogEvent {
@@ -37,6 +39,7 @@ impl __sdk::__query_builder::HasCols for LogEvent {
start: __sdk::__query_builder::Col::new(table_name, "start"), start: __sdk::__query_builder::Col::new(table_name, "start"),
end: __sdk::__query_builder::Col::new(table_name, "end"), end: __sdk::__query_builder::Col::new(table_name, "end"),
action: __sdk::__query_builder::Col::new(table_name, "action"), action: __sdk::__query_builder::Col::new(table_name, "action"),
user: __sdk::__query_builder::Col::new(table_name, "user"),
} }
} }
} }

View File

@@ -0,0 +1,44 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct MigrateArgs {}
impl __sdk::InModule for MigrateArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `migrate`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait migrate {
fn migrate(&self) {
self.migrate_then(|_, _| {});
}
fn migrate_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
);
}
impl migrate for super::RemoteProcedures {
fn migrate_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, String>("migrate", MigrateArgs {}, __callback);
}
}

View File

@@ -1,7 +1,7 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
// This was generated using spacetimedb cli version 2.1.0 (commit refs/tags/v2.1.0). // This was generated using spacetimedb cli version 2.6.0 (commit refs/tags/v2.6.0).
#![allow(unused, clippy::all)] #![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
@@ -10,8 +10,16 @@ pub mod action_type;
pub mod activities_table; pub mod activities_table;
pub mod activity_type; pub mod activity_type;
pub mod add_activity_reducer; pub mod add_activity_reducer;
pub mod add_thought_reducer;
pub mod daily_task_log_table;
pub mod daily_task_log_type;
pub mod daily_task_table;
pub mod daily_task_type;
pub mod export_timetrack_procedure;
pub mod first_timetrack_date_procedure;
pub mod log_event_type; pub mod log_event_type;
pub mod log_events_table; pub mod log_events_table;
pub mod migrate_procedure;
pub mod next_action_reducer; pub mod next_action_reducer;
pub mod profile_type; pub mod profile_type;
pub mod profiles_table; pub mod profiles_table;
@@ -21,6 +29,9 @@ pub mod task_part_table;
pub mod task_part_type; pub mod task_part_type;
pub mod task_type; pub mod task_type;
pub mod tasks_table; pub mod tasks_table;
pub mod test_procedure;
pub mod thought_type;
pub mod thoughts_table;
pub mod update_activity_reducer; pub mod update_activity_reducer;
pub mod update_upcoming_reducer; pub mod update_upcoming_reducer;
@@ -28,8 +39,16 @@ pub use action_type::Action;
pub use activities_table::*; pub use activities_table::*;
pub use activity_type::Activity; pub use activity_type::Activity;
pub use add_activity_reducer::add_activity; pub use add_activity_reducer::add_activity;
pub use add_thought_reducer::add_thought;
pub use daily_task_log_table::*;
pub use daily_task_log_type::DailyTaskLog;
pub use daily_task_table::*;
pub use daily_task_type::DailyTask;
pub use export_timetrack_procedure::export_timetrack;
pub use first_timetrack_date_procedure::first_timetrack_date;
pub use log_event_type::LogEvent; pub use log_event_type::LogEvent;
pub use log_events_table::*; pub use log_events_table::*;
pub use migrate_procedure::migrate;
pub use next_action_reducer::next_action; pub use next_action_reducer::next_action;
pub use profile_type::Profile; pub use profile_type::Profile;
pub use profiles_table::*; pub use profiles_table::*;
@@ -39,6 +58,9 @@ pub use task_part_table::*;
pub use task_part_type::TaskPart; pub use task_part_type::TaskPart;
pub use task_type::Task; pub use task_type::Task;
pub use tasks_table::*; pub use tasks_table::*;
pub use test_procedure::test;
pub use thought_type::Thought;
pub use thoughts_table::*;
pub use update_activity_reducer::update_activity; pub use update_activity_reducer::update_activity;
pub use update_upcoming_reducer::update_upcoming; pub use update_upcoming_reducer::update_upcoming;
@@ -50,12 +72,29 @@ pub use update_upcoming_reducer::update_upcoming;
/// to indicate which reducer caused the event. /// to indicate which reducer caused the event.
pub enum Reducer { pub enum Reducer {
AddActivity { name: String }, AddActivity {
NextAction, name: String,
RemoveActivity { activity: Activity }, },
SetActivityName { id: u64, name: String }, AddThought {
UpdateActivity { activity: Activity }, thought: String,
UpdateUpcoming { upcoming: Vec<Action> }, },
NextAction {
break_flag: bool,
action: Option<Action>,
},
RemoveActivity {
activity: Activity,
},
SetActivityName {
id: u64,
name: String,
},
UpdateActivity {
activity: Activity,
},
UpdateUpcoming {
upcoming: Vec<Action>,
},
} }
impl __sdk::InModule for Reducer { impl __sdk::InModule for Reducer {
@@ -66,7 +105,8 @@ impl __sdk::Reducer for Reducer {
fn reducer_name(&self) -> &'static str { fn reducer_name(&self) -> &'static str {
match self { match self {
Reducer::AddActivity { .. } => "add_activity", Reducer::AddActivity { .. } => "add_activity",
Reducer::NextAction => "next_action", Reducer::AddThought { .. } => "add_thought",
Reducer::NextAction { .. } => "next_action",
Reducer::RemoveActivity { .. } => "remove_activity", Reducer::RemoveActivity { .. } => "remove_activity",
Reducer::SetActivityName { .. } => "set_activity_name", Reducer::SetActivityName { .. } => "set_activity_name",
Reducer::UpdateActivity { .. } => "update_activity", Reducer::UpdateActivity { .. } => "update_activity",
@@ -80,7 +120,17 @@ impl __sdk::Reducer for Reducer {
Reducer::AddActivity { name } => { Reducer::AddActivity { name } => {
__sats::bsatn::to_vec(&add_activity_reducer::AddActivityArgs { name: name.clone() }) __sats::bsatn::to_vec(&add_activity_reducer::AddActivityArgs { name: name.clone() })
} }
Reducer::NextAction => __sats::bsatn::to_vec(&next_action_reducer::NextActionArgs {}), Reducer::AddThought { thought } => {
__sats::bsatn::to_vec(&add_thought_reducer::AddThoughtArgs {
thought: thought.clone(),
})
}
Reducer::NextAction { break_flag, action } => {
__sats::bsatn::to_vec(&next_action_reducer::NextActionArgs {
break_flag: break_flag.clone(),
action: action.clone(),
})
}
Reducer::RemoveActivity { activity } => { Reducer::RemoveActivity { activity } => {
__sats::bsatn::to_vec(&remove_activity_reducer::RemoveActivityArgs { __sats::bsatn::to_vec(&remove_activity_reducer::RemoveActivityArgs {
activity: activity.clone(), activity: activity.clone(),
@@ -112,10 +162,13 @@ impl __sdk::Reducer for Reducer {
#[doc(hidden)] #[doc(hidden)]
pub struct DbUpdate { pub struct DbUpdate {
activities: __sdk::TableUpdate<Activity>, activities: __sdk::TableUpdate<Activity>,
daily_task: __sdk::TableUpdate<DailyTask>,
daily_task_log: __sdk::TableUpdate<DailyTaskLog>,
log_events: __sdk::TableUpdate<LogEvent>, log_events: __sdk::TableUpdate<LogEvent>,
profiles: __sdk::TableUpdate<Profile>, profiles: __sdk::TableUpdate<Profile>,
task_part: __sdk::TableUpdate<TaskPart>, task_part: __sdk::TableUpdate<TaskPart>,
tasks: __sdk::TableUpdate<Task>, tasks: __sdk::TableUpdate<Task>,
thoughts: __sdk::TableUpdate<Thought>,
} }
impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
@@ -127,6 +180,12 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
"activities" => db_update "activities" => db_update
.activities .activities
.append(activities_table::parse_table_update(table_update)?), .append(activities_table::parse_table_update(table_update)?),
"daily_task" => db_update
.daily_task
.append(daily_task_table::parse_table_update(table_update)?),
"daily_task_log" => db_update
.daily_task_log
.append(daily_task_log_table::parse_table_update(table_update)?),
"log_events" => db_update "log_events" => db_update
.log_events .log_events
.append(log_events_table::parse_table_update(table_update)?), .append(log_events_table::parse_table_update(table_update)?),
@@ -139,6 +198,9 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
"tasks" => db_update "tasks" => db_update
.tasks .tasks
.append(tasks_table::parse_table_update(table_update)?), .append(tasks_table::parse_table_update(table_update)?),
"thoughts" => db_update
.thoughts
.append(thoughts_table::parse_table_update(table_update)?),
unknown => { unknown => {
return Err(__sdk::InternalError::unknown_name( return Err(__sdk::InternalError::unknown_name(
@@ -168,6 +230,9 @@ impl __sdk::DbUpdate for DbUpdate {
diff.activities = cache diff.activities = cache
.apply_diff_to_table::<Activity>("activities", &self.activities) .apply_diff_to_table::<Activity>("activities", &self.activities)
.with_updates_by_pk(|row| &row.id); .with_updates_by_pk(|row| &row.id);
diff.daily_task = cache.apply_diff_to_table::<DailyTask>("daily_task", &self.daily_task);
diff.daily_task_log =
cache.apply_diff_to_table::<DailyTaskLog>("daily_task_log", &self.daily_task_log);
diff.log_events = cache diff.log_events = cache
.apply_diff_to_table::<LogEvent>("log_events", &self.log_events) .apply_diff_to_table::<LogEvent>("log_events", &self.log_events)
.with_updates_by_pk(|row| &row.id); .with_updates_by_pk(|row| &row.id);
@@ -180,6 +245,9 @@ impl __sdk::DbUpdate for DbUpdate {
diff.tasks = cache diff.tasks = cache
.apply_diff_to_table::<Task>("tasks", &self.tasks) .apply_diff_to_table::<Task>("tasks", &self.tasks)
.with_updates_by_pk(|row| &row.id); .with_updates_by_pk(|row| &row.id);
diff.thoughts = cache
.apply_diff_to_table::<Thought>("thoughts", &self.thoughts)
.with_updates_by_pk(|row| &row.id);
diff diff
} }
@@ -190,6 +258,12 @@ impl __sdk::DbUpdate for DbUpdate {
"activities" => db_update "activities" => db_update
.activities .activities
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"daily_task" => db_update
.daily_task
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"daily_task_log" => db_update
.daily_task_log
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"log_events" => db_update "log_events" => db_update
.log_events .log_events
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
@@ -202,6 +276,9 @@ impl __sdk::DbUpdate for DbUpdate {
"tasks" => db_update "tasks" => db_update
.tasks .tasks
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"thoughts" => db_update
.thoughts
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
unknown => { unknown => {
return Err( return Err(
__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into(), __sdk::InternalError::unknown_name("table", unknown, "QueryRows").into(),
@@ -218,6 +295,12 @@ impl __sdk::DbUpdate for DbUpdate {
"activities" => db_update "activities" => db_update
.activities .activities
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"daily_task" => db_update
.daily_task
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"daily_task_log" => db_update
.daily_task_log
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"log_events" => db_update "log_events" => db_update
.log_events .log_events
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
@@ -230,6 +313,9 @@ impl __sdk::DbUpdate for DbUpdate {
"tasks" => db_update "tasks" => db_update
.tasks .tasks
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"thoughts" => db_update
.thoughts
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
unknown => { unknown => {
return Err( return Err(
__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into(), __sdk::InternalError::unknown_name("table", unknown, "QueryRows").into(),
@@ -246,10 +332,13 @@ impl __sdk::DbUpdate for DbUpdate {
#[doc(hidden)] #[doc(hidden)]
pub struct AppliedDiff<'r> { pub struct AppliedDiff<'r> {
activities: __sdk::TableAppliedDiff<'r, Activity>, activities: __sdk::TableAppliedDiff<'r, Activity>,
daily_task: __sdk::TableAppliedDiff<'r, DailyTask>,
daily_task_log: __sdk::TableAppliedDiff<'r, DailyTaskLog>,
log_events: __sdk::TableAppliedDiff<'r, LogEvent>, log_events: __sdk::TableAppliedDiff<'r, LogEvent>,
profiles: __sdk::TableAppliedDiff<'r, Profile>, profiles: __sdk::TableAppliedDiff<'r, Profile>,
task_part: __sdk::TableAppliedDiff<'r, TaskPart>, task_part: __sdk::TableAppliedDiff<'r, TaskPart>,
tasks: __sdk::TableAppliedDiff<'r, Task>, tasks: __sdk::TableAppliedDiff<'r, Task>,
thoughts: __sdk::TableAppliedDiff<'r, Thought>,
__unused: std::marker::PhantomData<&'r ()>, __unused: std::marker::PhantomData<&'r ()>,
} }
@@ -264,10 +353,17 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
callbacks: &mut __sdk::DbCallbacks<RemoteModule>, callbacks: &mut __sdk::DbCallbacks<RemoteModule>,
) { ) {
callbacks.invoke_table_row_callbacks::<Activity>("activities", &self.activities, event); callbacks.invoke_table_row_callbacks::<Activity>("activities", &self.activities, event);
callbacks.invoke_table_row_callbacks::<DailyTask>("daily_task", &self.daily_task, event);
callbacks.invoke_table_row_callbacks::<DailyTaskLog>(
"daily_task_log",
&self.daily_task_log,
event,
);
callbacks.invoke_table_row_callbacks::<LogEvent>("log_events", &self.log_events, event); callbacks.invoke_table_row_callbacks::<LogEvent>("log_events", &self.log_events, event);
callbacks.invoke_table_row_callbacks::<Profile>("profiles", &self.profiles, event); callbacks.invoke_table_row_callbacks::<Profile>("profiles", &self.profiles, event);
callbacks.invoke_table_row_callbacks::<TaskPart>("task_part", &self.task_part, event); callbacks.invoke_table_row_callbacks::<TaskPart>("task_part", &self.task_part, event);
callbacks.invoke_table_row_callbacks::<Task>("tasks", &self.tasks, event); callbacks.invoke_table_row_callbacks::<Task>("tasks", &self.tasks, event);
callbacks.invoke_table_row_callbacks::<Thought>("thoughts", &self.thoughts, event);
} }
} }
@@ -929,11 +1025,22 @@ impl __sdk::SpacetimeModule for RemoteModule {
fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) { fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {
activities_table::register_table(client_cache); activities_table::register_table(client_cache);
daily_task_table::register_table(client_cache);
daily_task_log_table::register_table(client_cache);
log_events_table::register_table(client_cache); log_events_table::register_table(client_cache);
profiles_table::register_table(client_cache); profiles_table::register_table(client_cache);
task_part_table::register_table(client_cache); task_part_table::register_table(client_cache);
tasks_table::register_table(client_cache); tasks_table::register_table(client_cache);
thoughts_table::register_table(client_cache);
} }
const ALL_TABLE_NAMES: &'static [&'static str] = const ALL_TABLE_NAMES: &'static [&'static str] = &[
&["activities", "log_events", "profiles", "task_part", "tasks"]; "activities",
"daily_task",
"daily_task_log",
"log_events",
"profiles",
"task_part",
"tasks",
"thoughts",
];
} }

View File

@@ -4,13 +4,21 @@
#![allow(unused, clippy::all)] #![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::action_type::Action;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] #[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)] #[sats(crate = __lib)]
pub(super) struct NextActionArgs {} pub(super) struct NextActionArgs {
pub break_flag: bool,
pub action: Option<Action>,
}
impl From<NextActionArgs> for super::Reducer { impl From<NextActionArgs> for super::Reducer {
fn from(args: NextActionArgs) -> Self { fn from(args: NextActionArgs) -> Self {
Self::NextAction Self::NextAction {
break_flag: args.break_flag,
action: args.action,
}
} }
} }
@@ -29,8 +37,8 @@ pub trait next_action {
/// The reducer will run asynchronously in the future, /// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status. /// and this method provides no way to listen for its completion status.
/// /// Use [`next_action:next_action_then`] to run a callback after the reducer completes. /// /// Use [`next_action:next_action_then`] to run a callback after the reducer completes.
fn next_action(&self) -> __sdk::Result<()> { fn next_action(&self, break_flag: bool, action: Option<Action>) -> __sdk::Result<()> {
self.next_action_then(|_, _| {}) self.next_action_then(break_flag, action, |_, _| {})
} }
/// Request that the remote module invoke the reducer `next_action` to run as soon as possible, /// Request that the remote module invoke the reducer `next_action` to run as soon as possible,
@@ -41,6 +49,8 @@ pub trait next_action {
/// and its status can be observed with the `callback`. /// and its status can be observed with the `callback`.
fn next_action_then( fn next_action_then(
&self, &self,
break_flag: bool,
action: Option<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>) callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send + Send
@@ -51,12 +61,14 @@ pub trait next_action {
impl next_action for super::RemoteReducers { impl next_action for super::RemoteReducers {
fn next_action_then( fn next_action_then(
&self, &self,
break_flag: bool,
action: Option<Action>,
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>) callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send + Send
+ 'static, + 'static,
) -> __sdk::Result<()> { ) -> __sdk::Result<()> {
self.imp self.imp
.invoke_reducer_with_callback(NextActionArgs {}, callback) .invoke_reducer_with_callback(NextActionArgs { break_flag, action }, callback)
} }
} }

View File

@@ -0,0 +1,44 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct TestArgs {}
impl __sdk::InModule for TestArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `test`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait test {
fn test(&self) {
self.test_then(|_, _| {});
}
fn test_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
);
}
impl test for super::RemoteProcedures {
fn test_then(
&self,
__callback: impl FnOnce(&super::ProcedureEventContext, Result<String, __sdk::InternalError>)
+ Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, String>("test", TestArgs {}, __callback);
}
}

View File

@@ -0,0 +1,60 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct Thought {
pub id: u64,
pub text: String,
pub user: __sdk::Identity,
pub time: __sdk::Timestamp,
}
impl __sdk::InModule for Thought {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `Thought`.
///
/// Provides typed access to columns for query building.
pub struct ThoughtCols {
pub id: __sdk::__query_builder::Col<Thought, u64>,
pub text: __sdk::__query_builder::Col<Thought, String>,
pub user: __sdk::__query_builder::Col<Thought, __sdk::Identity>,
pub time: __sdk::__query_builder::Col<Thought, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for Thought {
type Cols = ThoughtCols;
fn cols(table_name: &'static str) -> Self::Cols {
ThoughtCols {
id: __sdk::__query_builder::Col::new(table_name, "id"),
text: __sdk::__query_builder::Col::new(table_name, "text"),
user: __sdk::__query_builder::Col::new(table_name, "user"),
time: __sdk::__query_builder::Col::new(table_name, "time"),
}
}
}
/// Indexed column accessor struct for the table `Thought`.
///
/// Provides typed access to indexed columns for query building.
pub struct ThoughtIxCols {
pub id: __sdk::__query_builder::IxCol<Thought, u64>,
pub time: __sdk::__query_builder::IxCol<Thought, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasIxCols for Thought {
type IxCols = ThoughtIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
ThoughtIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
time: __sdk::__query_builder::IxCol::new(table_name, "time"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for Thought {}

View File

@@ -0,0 +1,159 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::thought_type::Thought;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `thoughts`.
///
/// Obtain a handle from the [`ThoughtsTableAccess::thoughts`] method on [`super::RemoteTables`],
/// like `ctx.db.thoughts()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.thoughts().on_insert(...)`.
pub struct ThoughtsTableHandle<'ctx> {
imp: __sdk::TableHandle<Thought>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `thoughts`.
///
/// Implemented for [`super::RemoteTables`].
pub trait ThoughtsTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`ThoughtsTableHandle`], which mediates access to the table `thoughts`.
fn thoughts(&self) -> ThoughtsTableHandle<'_>;
}
impl ThoughtsTableAccess for super::RemoteTables {
fn thoughts(&self) -> ThoughtsTableHandle<'_> {
ThoughtsTableHandle {
imp: self.imp.get_table::<Thought>("thoughts"),
ctx: std::marker::PhantomData,
}
}
}
pub struct ThoughtsInsertCallbackId(__sdk::CallbackId);
pub struct ThoughtsDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for ThoughtsTableHandle<'ctx> {
type Row = Thought;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = Thought> + '_ {
self.imp.iter()
}
type InsertCallbackId = ThoughtsInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ThoughtsInsertCallbackId {
ThoughtsInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: ThoughtsInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = ThoughtsDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> ThoughtsDeleteCallbackId {
ThoughtsDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: ThoughtsDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct ThoughtsUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for ThoughtsTableHandle<'ctx> {
type UpdateCallbackId = ThoughtsUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> ThoughtsUpdateCallbackId {
ThoughtsUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: ThoughtsUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `id` unique index on the table `thoughts`,
/// which allows point queries on the field of the same name
/// via the [`ThoughtsIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.thoughts().id().find(...)`.
pub struct ThoughtsIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<Thought, u64>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> ThoughtsTableHandle<'ctx> {
/// Get a handle on the `id` unique index on the table `thoughts`.
pub fn id(&self) -> ThoughtsIdUnique<'ctx> {
ThoughtsIdUnique {
imp: self.imp.get_unique_constraint::<u64>("id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> ThoughtsIdUnique<'ctx> {
/// Find the subscribed row whose `id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &u64) -> Option<Thought> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<Thought>("thoughts");
_table.add_unique_constraint::<u64>("id", |row| &row.id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<Thought>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<Thought>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `Thought`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait thoughtsQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `Thought`.
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought>;
}
impl thoughtsQueryTableAccess for __sdk::QueryTableAccessor {
fn thoughts(&self) -> __sdk::__query_builder::Table<Thought> {
__sdk::__query_builder::Table::new("thoughts")
}
}

View File

@@ -9,5 +9,8 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
spacetimedb = "2.1.0" spacetimedb = { version = "2.4.0", features = ["unstable"] }
log = "0.4" log = "0.4"
chrono = { version = "0.4.45", features = [], default-features = false }
chrono-tz = { version = "0.10.4", default-features = false }
url = "2.5.8"

393
marts/spacetime/src/lib.rs Normal file
View File

@@ -0,0 +1,393 @@
use std::collections::HashMap;
use std::time::Duration;
use chrono::{DateTime, TimeZone};
use chrono_tz::Europe;
use log::*;
use spacetimedb::SpacetimeType;
use spacetimedb::sys::raw::index_id_from_name;
use spacetimedb::{Identity, ReducerContext, ProcedureContext, Table, Timestamp, reducer, table, procedure};
use spacetimedb::http::*;
#[table(accessor = profiles, public)]
pub struct Profile {
#[primary_key]
identity: Identity,
#[unique]
username: String,
}
#[table(accessor = task_part, public)]
pub struct TaskPart {
#[primary_key]
identity: Identity,
start_of_current: Timestamp,
current: Action,
upcoming: Vec<Action>,
}
#[table(accessor = activities, public)]
pub struct Activity {
#[primary_key]
#[auto_inc]
id: u64,
name: String,
task: Option<u64>,
parents_ids: Vec<u64>,
}
#[table(accessor = tasks, public)]
pub struct Task {
#[primary_key]
#[auto_inc]
id: u64,
done: bool,
name: String,
parent: Option<u64>,
}
#[table(accessor = thoughts, public)]
pub struct Thought {
#[primary_key]
#[auto_inc]
id: u64,
text: String,
user: Identity,
#[index(btree)]
time: Timestamp,
}
#[table(accessor = action)]
#[derive(Clone)]
pub struct Action {
name: String,
activity: Option<u64>,
task: Option<u64>,
start: Option<Timestamp>,
end: Option<Timestamp>,
duration: Option<u64>,
}
impl Action {
pub fn name_only(name: String) -> Self {
Action {
name,
activity: None,
task: None,
start: None,
end: None,
duration: None,
}
}
}
#[reducer]
pub fn add_thought(ctx: &ReducerContext, thought: String) -> Result<(), String> {
ctx.db.thoughts().try_insert(Thought {
id: 0,
text: thought,
user: ctx.sender(),
time: ctx.timestamp,
})?;
Ok(())
}
#[table(accessor = log_events, public, index(accessor = log_events_index, btree(columns = [user, end])))]
pub struct LogEvent {
#[primary_key]
#[auto_inc]
id: u64,
start: Timestamp,
end: Timestamp,
action: Action,
user: Identity,
}
#[reducer(init)]
pub fn init(_ctx: &ReducerContext) {
// Called when the module is initially published
info!("hiiiiiii from ppc module");
}
// the one from ppc, hardcoded rn, as we for now only support config at run time.
// this needs to be loaded at build time. (this code runs as a wasm module on the spacetimedb server)
const PPC_AUTH_CLIENT_ID: &str = "363331597203734531";
#[reducer(client_connected)]
pub fn connect(ctx: &ReducerContext) -> Result<(), String> {
check_auth(ctx)?;
info!("hooooo");
// add user
let _ = ctx.db.profiles().try_insert(Profile {
identity: ctx.sender(),
username: ctx.sender().to_string(),
});
Ok(())
}
fn check_auth(ctx: &ReducerContext) -> Result<(), String> {
let jwt = ctx
.sender_auth()
.jwt()
.ok_or("Authentication required".to_string())?;
if jwt.issuer() != "https://auth.ppc.social" {
return Err("Invalid issuer".to_string());
}
if !jwt.audience().iter().any(|a| a == PPC_AUTH_CLIENT_ID) {
return Err("Invalid audience".to_string());
}
Ok(())
}
#[reducer(client_disconnected)]
pub fn identity_disconnected(_ctx: &ReducerContext) {
// Called everytime a client disconnects
}
#[reducer]
pub fn add_activity(ctx: &ReducerContext, name: String) {
ctx.db.activities().insert(Activity {
name,
task: None,
id: 0, // auto-increments
parents_ids: Vec::new(),
});
}
#[reducer]
pub fn set_activity_name(ctx: &ReducerContext, id: u64, name: String) {
if let Some(old_ac) = ctx.db.activities().id().find(id) {
ctx.db.activities().id().update(Activity { name, ..old_ac });
} else {
return ();
}
}
#[reducer]
pub fn update_upcoming(ctx: &ReducerContext, upcoming: Vec<Action>) {
if ctx.db.task_part().identity().find(ctx.sender()).is_none() {
ctx.db.task_part().insert(TaskPart {
upcoming,
identity: ctx.sender(),
current: Action {
name: "filler".to_string(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
},
start_of_current: ctx.timestamp,
});
} else {
ctx.db.task_part().identity().update(TaskPart {
upcoming,
..ctx.db.task_part().identity().find(&ctx.sender()).unwrap()
});
}
}
#[reducer]
pub fn next_action(ctx: &ReducerContext, break_flag: bool, action: Option<Action>) -> Result<(), String> {
if let Some(mut task_part) = ctx.db.task_part().identity().find(&ctx.sender()) {
if task_part.upcoming.is_empty() {
return Err("No upcoming actions".to_string());
}
let new_action = match action {
Some(action) => action,
// when we do "ppc nextAction -b" ... we want to do a generic break "b" action.
None => if break_flag { Action::name_only("idk".to_string()) } else { task_part.upcoming.remove(0) },
};
// when break flag, push current_action again to the front of upcoming
if break_flag {
// insert generic "b" action when no one is specified
task_part.upcoming.insert(0, task_part.current.clone());
}
let start_of_old_action = task_part.start_of_current;
ctx.db.task_part().identity().update(TaskPart {
upcoming: task_part.upcoming,
identity: task_part.identity,
current: new_action.clone(),
start_of_current: ctx.timestamp,
});
// create log event
ctx.db.log_events().insert(LogEvent {
start: start_of_old_action,
end: ctx.timestamp,
action: task_part.current,
id: 0,
user: ctx.sender(),
});
Ok(())
} else {
Err("Task part data not found for this user".to_string())
}
}
#[reducer]
pub fn update_activity(ctx: &ReducerContext, activity: Activity) {
if activity.id == 0 {
ctx.db.activities().insert(activity);
} else {
ctx.db.activities().id().update(activity);
}
}
#[reducer]
pub fn remove_activity(ctx: &ReducerContext, activity: Activity) {
ctx.db.activities().delete(activity);
}
//////////////////// daily metric ///////////////////
#[table(accessor = daily_task, public)]
pub struct DailyTask {
id: i64,
name: String,
owner: Identity,
}
#[table(accessor = daily_task_log, public)]
pub struct DailyTaskLog {
daily_task_id: i64,
done: Timestamp,
}
#[procedure]
fn first_timetrack_date(ctx: &mut ProcedureContext, username: String) -> Result<String, String> {
let timestamp = ctx.try_with_tx(|tx| {
let profile = tx.db.profiles().username().find(username.clone()).ok_or("No such Profile".to_string())?;
let event = tx.db.log_events()
.log_events_index()
.filter(&profile.identity)
.nth(0)
.ok_or("Profile has no row in log_events")?;
let time = event.start
.to_chrono_date_time().map_err(|e| format!("chrono error: {e}"))?
.with_timezone(&chrono_tz::Europe::Vienna);
Ok::<String, String>(time.format("%Y-%m-%d").to_string())
})?;
Ok(timestamp)
}
#[procedure]
fn export_timetrack(ctx: &mut ProcedureContext, username: String, date: String) -> Result<String, String> {
// CEST only rn
let start = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(5, 0, 0);
let start_local = chrono_tz::Europe::Vienna.from_local_datetime(&start).unwrap();
let start_utc = start_local.with_timezone(&chrono::Utc);
let start_timestamp = Timestamp::from_micros_since_unix_epoch(start_utc.timestamp_micros());
let end = chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").unwrap().and_hms(4, 0, 0) + Duration::from_hours(24);
let end_local = chrono_tz::Europe::Vienna.from_local_datetime(&end).unwrap();
let end_utc = end_local.with_timezone(&chrono::Utc);
let end_timestamp = Timestamp::from_micros_since_unix_epoch(end_utc.timestamp_micros());
info!("start_of_day: {}", start_timestamp);
info!("end_of_day: {}", end_timestamp);
let mut export = String::new();
export.push_str(&format!("# export for {}\n\n", date));
export.push_str(&format!("## actions\n"));
let profile = ctx.with_tx(|tx| tx.db.profiles().username().find(&username)).ok_or(format!("Profile with username {} not found", username))?;
let events = ctx.with_tx(|tx|
tx.db.log_events()
.log_events_index()
.filter((profile.identity, start_timestamp..end_timestamp))
);
for event in events {
let start = event.start
.to_chrono_date_time()
.map_err(|err| err.to_string())?
.with_timezone(&Europe::Vienna)
.format("%H:%M:%S");
let end = event.end
.to_chrono_date_time()
.map_err(|err| err.to_string())?
.with_timezone(&Europe::Vienna)
.format("%H:%M:%S");
let line = format!("{} to {} ... {}\n", start, end, event.action.name);
export.push_str(line.as_str());
}
export.push_str("\n");
export.push_str("## thoughts\n");
let thoughts = ctx.with_tx(|tx| {
tx.db.thoughts().time().filter(start_timestamp..end_timestamp)
});
for thought in thoughts {
let line = format!("- {}\n\n", thought.text);
export.push_str(line.as_str());
}
Ok(export)
}
#[procedure]
pub fn migrate(ctx: &mut ProcedureContext) -> String {
info!("migrate procedure called");
//let msg = "Nothing to migrate".to_string();
let msg: Result<String, String> = ctx.try_with_tx(|tx| {
let mut out = String::new();
let mut count = 0;
//for event in tx.db.log_events_tmp().iter() {
//count += 1;
//}
out.push_str(format!("Migrated {} rows................", count).as_str());
Ok(out)
});
return match msg {
Ok(msg) => msg,
Err(err) => err,
};
}
#[procedure]
pub fn test(ctx: &mut ProcedureContext) -> String {
let mut s = String::from("hoooooooooooooooooooooo\n");
let a: String = ctx.with_tx(|tx| {
let mut s = String::new();
return s;
});
s.push_str(&format!("{}\n\n", a));
s.push_str("\n");
s.push_str(&format!("sender: {}", ctx.sender()));
s
}
#[handler]
pub fn test_handler(ctx: &mut HandlerContext, _req: Request<Body>) -> Response<Body> {
let res = ctx.try_with_tx(|tx| {
let mut s = String::new();
for a in tx.db.profiles().iter() {
s.push_str(&format!("{} ... {}\n", a.identity, a.username));
}
s.push_str(&format!("sender: {}", tx.sender()));
return Ok(s);
});
return Response::new(Body::from_bytes(match res {
Ok(body) => body,
Err(e) => e,
}));
}
#[spacetimedb::http::router]
fn router() -> Router {
Router::new()
.get("/test", test_handler)
}

671
marts/src/auth/client.rs Normal file
View File

@@ -0,0 +1,671 @@
use chrono::{DateTime, TimeDelta, Utc};
use mize::out;
use mize::{
item::ItemData, mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult,
};
use openidconnect::{
ClientId, DeviceAuthorizationResponse, EmptyExtraDeviceAuthorizationFields, IssuerUrl,
OAuth2TokenResponse, RefreshToken, Scope, TokenResponse,
};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::{path::PathBuf, thread};
use tracing::info;
// console_log macro
// that can be copied into other files for debugging purposes
#[cfg(feature = "target-obsidian")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "target-obsidian")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[cfg(feature = "target-obsidian")]
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string())})
}
#[cfg(feature = "target-os")]
use clap::Command;
#[derive(Default)]
#[mize_part]
pub struct ClientAuth {
mize: Mize,
secrets: Option<ClientSecrets>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct ClientSecrets {
id_token: String,
access_token: String,
refresh_token: String,
expiry: DateTime<Utc>,
}
impl MizePartCreate for ClientAuth {}
impl MizePart for ClientAuth {
fn name(&self) -> &'static str {
"client_auth"
}
}
#[cfg(feature = "target-js")]
pub fn store_secrets_js(mize: &Mize, secrets: &ClientSecrets) -> MizeResult<()> {
use web_sys::window;
// Serialize struct to JSON string
let serialized = serde_json::to_string(&secrets)?;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Store the JSON string
storage.set_item("mize_client_secrets", &serialized).map_err(|_| mize_err!("couldn't set item in local_storage"))?;
Ok(())
}
impl ClientAuth {
pub fn secrets(&self) -> Option<&ClientSecrets> {
self.secrets.as_ref()
}
pub fn id_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().id_token)
}
pub fn access_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().access_token)
}
pub fn refresh_token(&self) -> MizeResult<&str> {
if self.secrets.is_none() {
return Err(mize_err!("Client not logged in"));
}
Ok(&self.secrets.as_ref().unwrap().refresh_token)
}
pub fn refresh(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-js")] {
return Err(mize_err!("refresh is not supported on wasm"));
}
#[cfg(feature = "target-os")] {
let secrets = self.secrets.clone().ok_or(mize_err!(
"no client secrets, meaning client_auth.refresh() thread can't use the refresh token"
))?;
let new_secrets = self.mize.spawn_async_blocking(
"auth.refresh_flow",
refresh_flow(self.mize.clone(), secrets),
)?;
store_secrets(&mut self.mize, &new_secrets)?;
self.secrets = Some(new_secrets);
}
Ok(())
}
pub fn login(&mut self) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
if self.secrets().is_none() {
let secrets = self
.mize
.spawn_async_blocking("auth.login_flow", auth_flow(self.mize.clone()))?;
store_secrets(&mut self.mize, &secrets)?;
self.secrets = Some(secrets);
}
}
#[cfg(feature = "target-js")] {
if self.secrets().is_none() {
let mut mize_clone = self.mize.clone();
wasm_bindgen_futures::spawn_local(async move {
console_log!("before auth_flow");
let secrets = match auth_flow(mize_clone.clone()).await {
Ok(secrets) => secrets,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
console_log!("after auth_flow");
if let Err(e) = store_secrets_js(&mut mize_clone, &secrets) {
mize_clone.report_err(e);
return ();
}
let mut auth = match mize_clone.get_part_native::<ClientAuth>("client_auth") {
Ok(auth) => auth,
Err(e) => {
mize_clone.report_err(e);
return ();
}
};
auth.secrets = Some(secrets);
});
}
}
Ok(())
}
pub fn start_refresh_thread(&mut self) -> MizeResult<()> {
// background thread for os targets
#[cfg(not(feature = "target-js"))]
{
let mut mize_clone = self.mize.clone();
self.mize.spawn_background("auth.refresh", move || loop {
info!("hi from auth.refresh thread...");
thread::sleep(Duration::from_secs(33)); // TERRIBLE hack
let auth = mize_clone.get_part_native::<ClientAuth>("client_auth")?;
let expiry = auth.secrets.as_ref().unwrap().expiry.clone();
info!("auth.refresh thread: now: {}", Utc::now());
info!("auth.refresh thread: expiry: {}", expiry);
let to_sleep = expiry - Utc::now() - TimeDelta::minutes(20);
/*
if to_sleep < TimeDelta::from_std(Duration::from_secs(0))? {
if let Err(e) = auth.refresh() {
e.log();
}
}
*/
drop(auth);
info!("auth.refresh: thread going to sleep for: {}", to_sleep);
thread::sleep(to_sleep.to_std()?);
info!("refresh_thread: sleeping done");
let mut auth = mize_clone.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
drop(auth);
})?;
Ok(())
}
// spawn an async background task doing the refresh
#[cfg(feature = "target-js")] {
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
// Get the window object
let window = web_sys::window().expect("no global `window` exists");
// Clone mize for the closure
let mize_clone = self.mize.clone();
// Create a closure that will be called periodically
let refresh_closure = Closure::wrap(Box::new(move || {
let mut mize_clone = mize_clone.clone();
wasm_bindgen_futures::spawn_local(async move {
let mut auth = mize_clone.get_part_native::<ClientAuth>("client_auth");
match auth {
Ok(mut auth_part) => {
if let Some(secrets) = auth_part.secrets() {
let now = Utc::now();
let expiry = secrets.expiry;
let time_until_expiry = expiry - now;
// Refresh if token will expire in less than 20 minutes
if time_until_expiry < TimeDelta::minutes(20) {
if let Err(e) = auth_part.refresh() {
console_log!("Error refreshing token: {:?}", e);
} else {
console_log!("Token refreshed successfully");
}
}
}
}
Err(e) => {
console_log!("Error getting auth part: {:?}", e);
}
}
});
}) as Box<dyn FnMut()>);
// Set up interval to run every 50 minutes (300000 ms)
let interval_ms = 3000000;
let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
refresh_closure.as_ref().unchecked_ref(),
interval_ms,
);
// Leak the closure to prevent it from being dropped
refresh_closure.forget();
Ok(())
}
}
}
pub fn part_client_auth(mize: &mut Mize) -> MizeResult<()> {
let mize_clone = mize.clone();
let mut auth_part = ClientAuth {
secrets: None,
mize: mize.clone(),
};
let mut secrets = match load_secrets(mize) {
Ok(secrets) => Some(secrets),
Err(err) => {
tracing::warn!("err reading secrets: {:?}", err);
None
}
};
auth_part.secrets = secrets.clone();
// do a refresh if the tokens have expired
if secrets.is_some() && secrets.clone().unwrap().expiry < Utc::now() {
auth_part.refresh()?;
}
// start refresh tread if configured
if mize
.get_config("auth.client.refresh_thread")
.unwrap_or(ItemData::new())
.value_bool()
.unwrap_or(false)
{
auth_part.start_refresh_thread()?;
}
// cli command for os
#[cfg(feature = "target-os")] {
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("ppc");
cli.subcommand(Command::new("login"), move |_, mut mize| {
let mut auth = mize.get_part_native::<ClientAuth>("client_auth")?;
let secrets = if auth.secrets.is_some() {
match mize.spawn_async_blocking(
"client_auth",
refresh_flow(mize.clone(), auth.secrets.clone().unwrap()),
) {
Ok(secrets) => secrets,
Err(err) => {
out!("err refreshing secrets: {:?}", err);
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
}
}
} else {
mize.spawn_async_blocking("client_auth", auth_flow(mize_clone.clone()))?
};
store_secrets(&mut mize, &secrets)?;
auth.secrets = Some(secrets);
Ok(())
})?;
}
// register command for obsidian
#[cfg(feature = "target-obsidian")] {
use crate::MartsCommand;
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
obsidian.add_command(MartsCommand {
id: "mize-login".to_string(),
name: "Mize Login".to_string(),
callback: Box::new(|mut mize| {
let mut auth = mize.get_part_native::<crate::ClientAuth>("client_auth")?;
let res = auth.login();
console_log!("auth.login done: {:?}", res);
res
}),
})?;
}
auth_part.secrets = secrets;
mize.register_part(Box::new(auth_part))?;
Ok(())
}
fn load_secrets(mize: &mut Mize) -> MizeResult<ClientSecrets> {
#[cfg(feature = "target-os")] {
let mize_dir = mize.get_config("data_dir")?.value_string()?;
let secrets_file_path = PathBuf::from(mize_dir).join("client_secrets.json");
let secrets_string = std::fs::read_to_string(secrets_file_path)?;
let secrets: ClientSecrets = serde_json::from_str(&secrets_string)?;
return Ok(secrets);
}
#[cfg(feature = "target-js")] {
use web_sys::window;
// Get browser window object
let window = window().ok_or_else(|| mize_err!("No global window found"))?;
// Access local storage
let storage = window
.local_storage().map_err(|_| mize_err!("couldn't get local_storage"))?
.ok_or_else(|| mize_err!("LocalStorage is not available"))?;
// Retrieve the string item
return match storage.get_item("mize_client_secrets").map_err(|_| mize_err!("couldn't get item from local_storage"))? {
Some(json_str) => {
// Deserialize JSON string back into Client struct
let secrets: ClientSecrets = serde_json::from_str(json_str.as_str())?;
Ok(secrets)
}
None => Err(mize_err!("no secrets")), // Key does not exist
}
}
}
fn store_secrets(mize: &mut Mize, secrets: &ClientSecrets) -> MizeResult<()> {
#[cfg(feature = "target-os")] {
let mize_dir = mize.get_config("store")?.value_string()?;
let secrets_file_path = PathBuf::from(mize_dir.clone())
.join("misc")
.join("client_secrets.json");
std::fs::create_dir_all(PathBuf::from(mize_dir).join("misc"))?;
let secrets_string = serde_json::to_string(secrets)?;
log::info!("secrets_file_path: {}", secrets_file_path.display());
//std::fs::write(secrets_file_path, secrets_string)?;
OpenOptions::new()
.write(true)
.create(true)
.open(secrets_file_path)?
.write_all(&secrets_string.as_bytes())?;
// update the spacetimedb_token in XDG_CONFIG_HOME/spacetime/cli.toml config file if configured to do so
if mize
.get_config("auth.client.update_spacetime_token")
.unwrap_or(ItemData::empty())
.as_bool()
.unwrap_or(false)
{
update_spacetimedb_token(&secrets.id_token.as_str())?;
}
}
Ok(())
}
use openidconnect::reqwest::Client as HttpClient;
use openidconnect::DeviceAuthorizationUrl;
use std::time::Duration;
//use wasm_bindgen::JsCast;
async fn refresh_flow(mut mize: Mize, old_secrets: ClientSecrets) -> MizeResult<ClientSecrets> {
out!("Refresh flow...");
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
let refresh_token = RefreshToken::new(old_secrets.refresh_token);
let res = client
.exchange_refresh_token(&refresh_token)?
.request_async(&http_client)
.await?;
let expires_in = res.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
return Ok(ClientSecrets {
id_token: res.id_token().unwrap().to_string(),
access_token: res.access_token().secret().to_owned(),
refresh_token: res.refresh_token().unwrap().secret().to_owned(),
expiry: Utc::now() + expires_in,
});
}
async fn auth_flow(mut mize: Mize) -> MizeResult<ClientSecrets> {
let issuer = mize.get_config("auth.client.issuer")?.value_string()?;
let client_id = mize.get_config("auth.client.client_id")?.value_string()?;
let issuer = IssuerUrl::new(issuer)?;
let client_id = ClientId::new(client_id);
let http_client = HttpClient::new();
// Teach openidconnect-rs about the device authorization url.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct MyExtraProviderMetadata {
device_authorization_endpoint: String,
}
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod,
CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType,
CoreSubjectIdentifierType,
};
use openidconnect::AdditionalProviderMetadata;
use openidconnect::ProviderMetadata;
impl AdditionalProviderMetadata for MyExtraProviderMetadata {}
type MyProviderMetadata = ProviderMetadata<
MyExtraProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
// Discover provider metadata
let provider_metadata: MyProviderMetadata =
MyProviderMetadata::discover_async(issuer, &http_client).await?;
let device_auth_url = provider_metadata
.additional_metadata()
.device_authorization_endpoint
.clone();
let client = CoreClient::from_provider_metadata(
provider_metadata,
client_id,
None, // public client
)
.set_device_authorization_url(DeviceAuthorizationUrl::new(device_auth_url)?);
// ===== DEVICE AUTH REQUEST =====
let details: Result<DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>, _> =
client
.exchange_device_code()
.add_scope(Scope::new("openid".into()))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("offline_access".into()))
.request_async(&http_client)
.await;
let details = match details {
Ok(val) => val,
Err(err) => {
out!("err: {:?}", err);
out!("err: {}", err);
panic!();
}
};
#[cfg(feature = "target-obsidian")] {
console_log!("details: {:?}", details);
use std::str::FromStr;
use js_sys::{Array, Function, JsString, Reflect};
use mize::error::IntoMizeResultJsValue;
use wasm_bindgen::convert::TryFromJsValue;
let obsidian = mize.get_part_native::<crate::ObsidianPart>("obsidian")?;
let show_device_code = Reflect::get(&obsidian.plugin(), &JsValue::from_str("showDeviceCode")).mres()?;
let args = Array::new();
args.push(&JsValue::from_str(details.user_code().secret().as_str()));
args.push(&JsValue::from_str(details.verification_uri_complete().unwrap().secret().as_str()));
Reflect::apply(&show_device_code.dyn_into::<Function>().mres()?, obsidian.plugin(), &args).mres()?;
}
#[cfg(feature = "target-os")] {
out!(
"If your browser does not open automatically, open this URL in it:\n{}\n",
details.verification_uri_complete().unwrap().secret()
);
out!(
"Or enter this PPC login code:\n{}\n",
details.user_code().secret()
);
if let Some(_) = details.verification_uri_complete() {
let _ = webbrowser::open(details.verification_uri_complete().unwrap().secret());
}
}
let token_response = client
.exchange_device_access_token(&details)?
.request_async(
&http_client,
async |dur: Duration| {
#[cfg(feature = "target-os")] {
tokio::time::sleep(dur).await;
}
#[cfg(feature = "target-js")] {
console_log!("sleeping");
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
let promise = js_sys::Promise::new(&mut |resolve, _| {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, dur.as_millis() as i32)
.unwrap();
});
// Await the JS Promise like a Rust Future
let _ = JsFuture::from(promise).await;
}
},
None,
)
.await?;
// ===== TOKENS =====
let id_token = token_response
.extra_fields()
.id_token()
.ok_or(mize_err!("no id_token"))?;
let refresh = token_response
.refresh_token()
.ok_or(mize_err!("no refresh token present"))?;
let expires_in = token_response.expires_in().ok_or(mize_err!(
"no expiry information was in token response of the refresh flow"
))?;
Ok(ClientSecrets {
id_token: id_token.to_string(),
access_token: token_response.access_token().secret().to_string(),
refresh_token: refresh.secret().to_owned(),
expiry: Utc::now() + expires_in,
})
}
fn update_spacetimedb_token(new_token: &str) -> MizeResult<()> {
// Resolve config directory
let config_dir = match std::env::var("XDG_CONFIG_HOME") {
Ok(val) => PathBuf::from(val),
Err(_) => {
let home = std::env::var("HOME")?;
std::path::PathBuf::from(home).join(".config")
}
};
let file_path = config_dir.join("spacetime").join("cli.toml");
// Read existing file (or create empty document if it doesn't exist)
let content = std::fs::read_to_string(&file_path).unwrap_or_default();
let mut data = ItemData::from_toml(content.as_str())?;
// Update the field
data.set_path("spacetimedb_token", new_token)?;
// Write back
std::fs::write(&file_path, data.to_toml()?)?;
Ok(())
}

View File

@@ -9,22 +9,19 @@ use serde_json::{json, Value};
use dioxus::prelude::*; use dioxus::prelude::*;
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult}; use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizePartCreate, MizeResult};
use tracing::instrument::WithSubscriber;
use crate::cli::CliPart; use crate::cli::CliPart;
use crate::habitica::Habitica; use crate::habitica::Habitica;
use crate::SpacetimePart; use crate::SpacetimePart;
use generated::spacetime::{ use generated::spacetime::{
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, Action, ActivitiesTableAccess, Activity, DbConnection, LogEventsTableAccess, TaskPart, TaskPartTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, update_activity, update_upcoming
update_activity, update_upcoming, Action, ActivitiesTableAccess, Activity, DbConnection,
TaskPart, TaskPartTableAccess,
}; };
use spacetimedb_sdk::{ use spacetimedb_sdk::{
credentials, DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table, credentials, DbContext, Error, Event, Identity, Status, SubscriptionHandle, Table,
TableWithPrimaryKey, TableWithPrimaryKey,
}; };
mod dashboard;
#[mize_part] #[mize_part]
#[derive(Default)] #[derive(Default)]
pub struct C2vi { pub struct C2vi {
@@ -63,14 +60,6 @@ pub fn c2vi(mize: &mut Mize) -> MizeResult<()> {
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
// refresh the credentials every 23h
// as this is basically running as a daemon
use crate::auth::client::ClientAuth;
let mut auth = mize.get_part_native::<ClientAuth>("auth")?;
auth.refresh()?;
auth.start_refresh_thread()?;
drop(auth);
launch_c2vi_dashboard(mize); launch_c2vi_dashboard(mize);
} }
@@ -89,8 +78,35 @@ pub fn launch_c2vi_dashboard(mize: &mut Mize) {
.launch(c2vi_dash_main); .launch(c2vi_dash_main);
} }
//use spacetimedb_sdk::spacetime_module::QueryBuilder;
/*
pub fn use_spacetime<T: Fn(QueryBuilder)>(query: T) {
let mut mize = use_context::<Mize>();
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
con.subscription_builder()
.on_applied(move |ctx| {
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
current.set(data.current.name.clone());
})
.add_query(query)
.subscribe();
con.db
.task_part()
.on_update(move |_, old: &TaskPart, new: &TaskPart| {
current.set(new.current.name.clone());
});
}
*/
//pub fn use_by_pk(identity: Identity)
//pub fn use_one<T>
pub fn c2vi_dash_main() -> Element { pub fn c2vi_dash_main() -> Element {
let mut current = use_signal_sync(|| "".to_string()); let mut current = use_signal_sync(|| "".to_string());
//TaskPart::
//use_one()
use_effect(move || { use_effect(move || {
let mut mize = use_context::<Mize>(); let mut mize = use_context::<Mize>();
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap(); let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
@@ -100,7 +116,7 @@ pub fn c2vi_dash_main() -> Element {
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap(); let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
current.set(data.current.name.clone()); current.set(data.current.name.clone());
}) })
.add_query(|q| q.from.task_part()) .add_query(|q| q.from.task_part().r#where(|t| t.identity.eq(con.identity())))
.subscribe(); .subscribe();
con.db con.db
.task_part() .task_part()
@@ -167,7 +183,7 @@ fn setup_tables(conn: &Connection) -> MizeResult<()> {
fn register_subcommands(cli: &mut CliPart) { fn register_subcommands(cli: &mut CliPart) {
cli.subcommand( cli.subcommand(
Command::new("c2vi-dash").about("Run the c2vi dashboard gui"), Command::new("c2vi-dash").about("Run the c2vi dashboard gui"),
|_matches, mut _mize| Ok(dioxus::launch(dashboard::main)), |_matches, mut _mize| Ok(dioxus::launch(super::dashboard::main)),
); );
cli.subcommand( cli.subcommand(
Command::new("bed").about("Command to run when c2vi goes to bed"), Command::new("bed").about("Command to run when c2vi goes to bed"),

277
marts/src/cli/mod.rs Normal file
View File

@@ -0,0 +1,277 @@
use clap::{ArgMatches, Command};
use dioxus::core::SpawnIfAsync;
use std::{cell::RefCell, collections::HashMap};
use mize::{
item::ItemData, mize_err, mize_part, proto::MizeMessage, Mize, MizeError, MizePart,
MizePartCreate, MizeResult,
};
thread_local! {
pub static MIZE_CLI_SPAWN: RefCell<Option<CliSpawn>> = const { RefCell::new(None) };
}
struct CliSpawn {
wait_rx: flume::Receiver<()>,
conn_id: Option<u64>,
mize: Mize,
}
pub fn cli_spawn_setup(mize: Mize, conn_id: u64, wait_rx: flume::Receiver<()>) -> MizeResult<()> {
MIZE_CLI_SPAWN.with(|cell| {
*cell.borrow_mut() = Some(CliSpawn {
wait_rx,
conn_id: Some(conn_id),
mize,
});
Ok(())
})
}
pub fn cli_spawn(cmd: String) -> MizeResult<()> {
MIZE_CLI_SPAWN.with(|cell| {
if let Some(spawn) = cell.borrow().as_ref() {
let conn_id = spawn.conn_id.ok_or(mize_err!("no conn_id set"))?;
let conn = spawn.mize.get_connection(conn_id)?;
conn.send(MizeMessage::new_custom("cli.spawn", cmd, conn_id))?;
log::info!("before wait_rx.recv()");
spawn.wait_rx.recv()?;
log::info!("after wait_rx.recv()");
Ok(())
} else {
Err(mize_err!("no cli spawn thread_local available"))
}
})
}
#[mize_part("cli")]
#[derive(Default)]
pub struct CliPart {
mize: Mize,
cmds: HashMap<String, Command>,
current_cmd_name: String,
actions:
HashMap<String, Box<dyn Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>>,
parsers: Option<Vec<Box<dyn Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
}
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
let command = Command::new("marts-cli").allow_external_subcommands(true);
let (spawn_wait_tx, spawn_wait_rx) = flume::unbounded::<()>();
let mut cli_part = CliPart {
mize: mize.clone(),
cmds: HashMap::new(),
current_cmd_name: "marts-cli".to_string(),
actions: HashMap::new(),
parsers: Some(Vec::new()),
};
cli_part.cmds.insert("marts-cli".to_string(), command);
mize.add_listener(
"cli",
Box::new(move |msg, mize| cli_msg_handler(msg, mize, spawn_wait_rx.clone())),
)?;
mize.add_listener(
"cli.spawn_done",
Box::new(move |mut msg, mize| {
log::info!("before send");
spawn_wait_tx.send(());
log::info!("after send");
Ok(())
}),
)?;
mize.add_part(Box::new(cli_part))
}
impl MizePartCreate for CliPart {}
impl MizePart for CliPart {
fn init(&mut self, _mize: &mut Mize) -> MizeResult<()> {
Ok(())
}
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
if mize
.get_config("cli.daemonMode")
.unwrap_or(ItemData::new())
.as_bool()
.unwrap_or(false)
{
return Ok(());
}
let matches = self
.cmds
.remove(&self.current_cmd_name)
.unwrap()
.get_matches();
let sub_cmd = match matches.subcommand_name() {
Some(sub_cmd) => sub_cmd,
None => {
log::info!("No subcommand provided");
return Ok(());
}
};
let action = match self.actions.remove(sub_cmd) {
Some(action) => action,
None => {
// external parsers
for parser in self.parsers.take().unwrap() {
parser(
mize.clone(),
matches
.subcommand_matches(sub_cmd)
.unwrap()
.get_many::<String>("")
.unwrap()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
)?;
}
return Ok(());
}
};
action(matches.subcommand_matches(sub_cmd).unwrap(), mize.clone())?;
Ok(())
}
fn opts(&self, mize: &mut Mize) {
mize.new_opt("cli.name");
}
}
fn cli_msg_handler(
mut msg: MizeMessage,
mize: &mut Mize,
spawn_wait_rx: flume::Receiver<()>,
) -> MizeResult<()> {
let binding = msg.data()?;
let data = binding
.cbor()
.as_array()
.ok_or(mize_err!("cli message data was not an array"))?;
let full_cmd = data
.iter()
.nth(0)
.cloned()
.ok_or(mize_err!("cli message data was empty"))?;
let full_cmd_text = full_cmd
.as_text()
.ok_or(mize_err!("cli cmd was not text"))?;
let cmd = full_cmd_text
.split("/")
.last()
.ok_or(mize_err!("failed to get cmd"))?;
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
let command = cli
.cmds
.get(cmd)
.ok_or(mize_err!("no command {cmd} in cli part"))?;
let matches = command
.clone()
.get_matches_from(data.iter().map(|v| v.as_text().unwrap()));
let sub_cmd_name = match matches.subcommand_name() {
Some(sub_cmd) => sub_cmd,
None => {
cli.send_stderr(&msg, "No subcommand provided".to_string())?;
return Ok(());
}
};
let action = match cli.actions.get_mut(sub_cmd_name) {
Some(action) => action,
None => {
return Ok(());
}
};
let original_msg = msg.clone();
let mize_clone = mize.clone();
mize::MIZE_STDOUT.with(move |stdout| {
*stdout.borrow_mut() = Some(Box::new(move |string| {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stdout", string, conn_id);
let conn = mize_clone.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}))
});
cli_spawn_setup(mize.clone(), msg.conn_id, spawn_wait_rx)?;
action(
matches.subcommand_matches(sub_cmd_name).unwrap(),
mize.clone(),
)?;
cli.send_done(&msg)?;
Ok(())
}
impl CliPart {
pub fn set_command(&mut self, cmd: &str) {
self.current_cmd_name = cmd.to_owned();
if !self.cmds.contains_key(cmd) {
self.cmds
.insert(cmd.to_owned(), Command::new(cmd.to_owned()));
}
}
pub fn send_stderr(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stderr", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_stdout(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.stdout", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_spawn(&mut self, original_msg: &MizeMessage, msg: String) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.spawn", msg, conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn send_done(&mut self, original_msg: &MizeMessage) -> MizeResult<()> {
let conn_id = original_msg.conn_id;
let msg = MizeMessage::new_custom("cli.done", ItemData::new(), conn_id);
let conn = self.mize.get_connection(conn_id)?;
conn.send(msg)?;
Ok(())
}
pub fn subcommand<T: Fn(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>(
&mut self,
subcmd: Command,
action: T,
) -> MizeResult<()> {
let name = subcmd.get_name().to_string();
let mut cmd = self
.cmds
.remove(&self.current_cmd_name)
.unwrap()
.subcommand(subcmd);
self.cmds.insert(self.current_cmd_name.clone(), cmd);
self.actions.insert(name, Box::new(action));
Ok(())
}
pub fn add_sub_parser<T: Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>(
&mut self,
parser: T,
) -> MizeResult<()> {
self.parsers.as_mut().unwrap().push(Box::new(parser));
Ok(())
}
pub fn with_cmd(
&mut self,
func: fn(mize: Mize, cmd: Command) -> MizeResult<Command>,
) -> MizeResult<()> {
let cmd = self.cmds.remove(&self.current_cmd_name).ok_or(mize_err!(
"current command {} not found",
self.current_cmd_name
))?;
let cmd = func(self.mize.clone(), cmd)?;
self.cmds.insert(self.current_cmd_name.clone(), cmd);
Ok(())
}
}

View File

@@ -61,7 +61,7 @@ impl Habitica {
if !response.status().is_success() { if !response.status().is_success() {
let status = response.status(); let status = response.status();
let text = response.text().unwrap_or_default(); let text = response.text().unwrap_or_default();
println!("request_url: {}", url); log::info!("request_url: {}", url);
return Err(mize_err!( return Err(mize_err!(
"Habitica API error: {} {} - {}", "Habitica API error: {} {} - {}",
status.as_u16(), status.as_u16(),
@@ -102,7 +102,7 @@ fn handle_rate_limit(response: &Response) {
.get("X-RateLimit-Reset") .get("X-RateLimit-Reset")
.and_then(|v| v.to_str().ok()); .and_then(|v| v.to_str().ok());
println!( log::info!(
"RateLimit: {} | Remaining: {} | Reset: {}", "RateLimit: {} | Remaining: {} | Reset: {}",
limit, limit,
remaining, remaining,
@@ -116,7 +116,7 @@ fn handle_rate_limit(response: &Response) {
let wait_duration = reset_date.signed_duration_since(now).to_std(); let wait_duration = reset_date.signed_duration_since(now).to_std();
if let Ok(wait_duration) = wait_duration { if let Ok(wait_duration) = wait_duration {
let wait_ms = wait_duration.as_millis() as u64 + 1000; let wait_ms = wait_duration.as_millis() as u64 + 1000;
println!( log::info!(
"Waiting {} secs for next rate limit window...", "Waiting {} secs for next rate limit window...",
(wait_ms / 1000) (wait_ms / 1000)
); );

View File

@@ -9,7 +9,7 @@ use crate::JsPart;
#[op2(fast)] #[op2(fast)]
fn op_mize_get_part(#[string] name: &str) { fn op_mize_get_part(#[string] name: &str) {
println!("js wants the part {}", name); log::info!("js wants the part {}", name);
} }
#[op2] #[op2]
@@ -22,7 +22,7 @@ fn op_mize_get_config(state: &mut OpState, #[string] key: &str) -> String {
#[op2(fast)] #[op2(fast)]
fn op_mize_add_part(state: &mut OpState, #[string] key: &str) { fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
let mize: Mize = state.borrow::<Mize>().clone(); let mize: Mize = state.borrow::<Mize>().clone();
println!("adding part {}", key); log::info!("adding part {}", key);
} }
#[op2(fast)] #[op2(fast)]

View File

@@ -31,7 +31,7 @@ pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a>
impl MizePart for JsPart { impl MizePart for JsPart {
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> { fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
println!("js part run"); log::info!("js part run");
self.sender self.sender
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -100,27 +100,27 @@ fn js_runtime_thread(
.unwrap(); .unwrap();
loop { loop {
println!("js thread waiting for smth"); log::info!("js thread waiting for smth");
let msg = receiver.recv().unwrap(); let msg = receiver.recv().unwrap();
println!("js thread got smth"); log::info!("js thread got smth");
match msg { match msg {
JsRuntimeThreadMessage::RunInitJs(js_code) => { JsRuntimeThreadMessage::RunInitJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[init]", js_code) { if let Err(err) = js_runtime.execute_script("[init]", js_code) {
println!("err: {}", err); log::info!("err: {}", err);
} }
println!("done running js"); log::info!("done running js");
} }
JsRuntimeThreadMessage::RunJs(js_code) => { JsRuntimeThreadMessage::RunJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[idk]", js_code) { if let Err(err) = js_runtime.execute_script("[idk]", js_code) {
println!("err: {}", err); log::info!("err: {}", err);
} }
println!("done running js"); log::info!("done running js");
} }
JsRuntimeThreadMessage::DoRunPhase => { JsRuntimeThreadMessage::DoRunPhase => {
if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") { if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") {
println!("err: {}", err); log::info!("err: {}", err);
} }
println!("done with runPhase"); log::info!("done with runPhase");
return Ok(()); return Ok(());
} }
} }

View File

@@ -1,5 +1,7 @@
#![feature(oneshot_channel)]
#[cfg(feature = "target-os")] #[cfg(feature = "target-os")]
mod cli; pub mod cli;
#[cfg(feature = "target-os")] #[cfg(feature = "target-os")]
pub use cli::*; pub use cli::*;
@@ -9,13 +11,19 @@ pub mod habitica;
pub use habitica::*; pub use habitica::*;
#[cfg(feature = "target-os")] #[cfg(feature = "target-os")]
pub mod c2vi; pub mod c2vi {
pub mod part;
pub mod dashboard;
}
#[cfg(feature = "target-os")] #[cfg(feature = "target-os")]
pub use c2vi::*; pub use c2vi::part::*;
#[cfg(feature = "target-obsidian")]
pub mod obsidian;
#[cfg(feature = "target-obsidian")]
pub use obsidian::*;
#[cfg(feature = "target-os")]
pub mod spacetime; pub mod spacetime;
#[cfg(feature = "target-os")]
pub use spacetime::*; pub use spacetime::*;
pub mod auth { pub mod auth {
@@ -24,7 +32,13 @@ pub mod auth {
} }
pub use auth::*; pub use auth::*;
pub mod ppc;
pub mod timetrack;
//#[cfg(feature = "target-os")] //#[cfg(feature = "target-os")]
//pub mod js; //pub mod js;
//#[cfg(feature = "target-os")] //#[cfg(feature = "target-os")]
//pub use js::*; //pub use js::*;
pub use generated;

88
marts/src/obsidian.rs Normal file
View File

@@ -0,0 +1,88 @@
use mize::{Mize, MizeResult, MizePart, MizePartCreate, mize_part};
use wasm_bindgen::JsValue;
use mize::error::IntoMizeResultJsValue;
use wasm_bindgen::prelude::*;
// console_log macro
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
}
pub fn part_obsidian(mize: &mut Mize, plugin: JsValue, app: JsValue) -> MizeResult<()> {
mize.register_part(Box::new(ObsidianPart {
mize: mize.clone(),
plugin,
app,
}));
Ok(())
}
#[mize_part("obsidian")]
#[derive(Default)]
pub struct ObsidianPart {
mize: Mize,
plugin: JsValue, // the jsValue of the obsidian plugin
app: JsValue, // the jsValue of the obsidian instance
}
pub struct MartsCommand {
pub id: String,
pub name: String,
pub callback: Box<dyn FnMut(Mize) -> MizeResult<()>>,
}
impl MizePart for ObsidianPart {}
impl MizePartCreate for ObsidianPart {}
impl ObsidianPart {
pub fn plugin(&self) -> &JsValue {
&self.plugin
}
pub fn add_command(&self, mut cmd: MartsCommand) -> MizeResult<()> {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use js_sys::{Function, Reflect, Object};
let obsidian_cmd = Object::new();
Reflect::set(&obsidian_cmd, &"id".into(), &"login".into()).mres()?;
Reflect::set(&obsidian_cmd, &"name".into(), &"Login".into()).mres()?;
// callback: () => run_login()
let mize_clone = self.mize.clone();
let cb = Closure::<dyn FnMut()>::wrap(Box::new(move || {
if let Err(e) = (cmd.callback)(mize_clone.clone()) {
mize_clone.report_err(e);
}
}));
Reflect::set(&obsidian_cmd, &"callback".into(), cb.as_ref()).mres()?;
// plugin.addCommand(cmd)
let add_command_fn = Reflect::get(&self.plugin, &"addCommand".into()).mres()?
.dyn_into::<Function>().mres()?;
add_command_fn
.call1(&self.plugin, &obsidian_cmd).mres()?;
// IMPORTANT: prevent callback from being dropped
cb.forget();
Ok(())
}
}

View File

@@ -1,14 +1,12 @@
// PPC Website UI Module
// This module contains the user interface components for the PPC website
use dioxus::prelude::*; use dioxus::prelude::*;
/// Main PPC website component
pub fn ppc_website() -> Element { pub fn main_page() -> Element {
rsx! { rsx! {
"hello world"
div { div {
class: "min-h-screen bg-gray-100", class: "min-h-screen bg-gray-100 items-center flex h-screen items-center justify-center",
// Header // Header
header { header {

4
marts/src/ppc/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod main_page;
pub use main_page::main_page;

107
marts/src/spacetime.rs Normal file
View File

@@ -0,0 +1,107 @@
use std::sync::oneshot::Sender;
use mize::{mize_part, Mize, MizePart, MizePartCreate, MizeResult};
use crate::auth::client::ClientAuth;
use generated::spacetime::DbConnection;
#[mize_part]
#[derive(Default)]
pub struct SpacetimePart {
mize: Mize,
con: Option<DbConnection>,
}
impl MizePartCreate for SpacetimePart {}
impl MizePart for SpacetimePart {
fn name(&self) -> &'static str {
"spacetime"
}
}
impl SpacetimePart {
pub fn con_mut(&mut self) -> &mut DbConnection {
self.con.as_mut().unwrap()
}
pub fn con(&self) -> &DbConnection {
self.con.as_ref().unwrap()
}
// does not work, because i can't use the QueryBuilder type in my code.... it's private
/*
pub fn get<T>(
&self,
on_applied: impl FnOnce(&generated::spacetime::SubscriptionEventContext, Sender<T>)+ Send + 'static,
query: impl FnOnce(QueryBuilder)+ Send + 'static
) -> MizeResult<T> {
let (done_tx, done_rx) = std::sync::oneshot::channel::<T>();
use spacetimedb_sdk::DbContext;
self.con().subscription_builder().on_applied(move |ctx| {
on_applied(ctx, done_tx);
}).add_query(build);
}
*/
}
pub fn spacetime(mize: &mut Mize) -> MizeResult<()> {
let auth = mize.get_part_native::<ClientAuth>("client_auth")?;
let db_url = mize.get_config("spacetime.url")?.value_string()?;
let con: Option<DbConnection>;
#[cfg(feature = "target-os")] {
con = Some(DbConnection::builder()
.with_token(Some(auth.id_token()?))
.with_database_name("mize")
.with_uri(db_url)
//.on_connect(callback)
//.on_disconnect(callback)
//.on_connect_error(callback)
//.with_compression(...)
.build()?);
con.as_ref().unwrap().run_threaded();
}
#[cfg(feature = "target-js")] {
let mut mize = mize.clone();
con = None;
wasm_bindgen_futures::spawn_local(async move {
let id_token = match auth.id_token() {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
let con = match DbConnection::builder()
.with_token(Some(id_token))
.with_database_name("mize")
.with_uri(db_url)
//.on_connect(callback)
//.on_disconnect(callback)
//.on_connect_error(callback)
//.with_compression(...)
.build().await {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
let mut spacetime = match mize.get_part_native::<SpacetimePart>("spacetime") {
Ok(val) => val,
Err(e) => {
mize.report_err(e.into());
return;
}
};
con.run_async();
spacetime.con = Some(con);
});
}
mize.register_part(Box::new(SpacetimePart {
mize: mize.clone(),
con: con,
}))
}

View File

@@ -1,6 +1,10 @@
use std::sync::oneshot; use std::sync::oneshot;
#[cfg(feature = "target-os")]
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use generated::spacetime::{Profile, TaskPart};
use mize::out;
use mize::proto::MizeMessage;
use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part}; use mize::{Mize, MizeError, MizePart, MizePartCreate, MizeResult, mize_err, mize_part};
use spacetimedb_sdk::{DbContext, Table}; use spacetimedb_sdk::{DbContext, Table};
@@ -9,7 +13,8 @@ use generated::spacetime::{
activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess, activitiesQueryTableAccess, next_action, remove_activity, task_partQueryTableAccess,
update_activity, update_upcoming, update_activity, update_upcoming,
}; };
use marts::spacetime::SpacetimePart; use crate::spacetime::SpacetimePart;
use spacetimedb_sdk::TableWithPrimaryKey;
#[mize_part] #[mize_part]
#[derive(Default)] #[derive(Default)]
@@ -18,7 +23,9 @@ pub struct TimeTrack {
} }
pub fn timetrack(mize: &mut Mize) -> MizeResult<()> { pub fn timetrack(mize: &mut Mize) -> MizeResult<()> {
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?; #[cfg(feature = "target-os")] {
let mut cli = mize.get_part_native::<crate::CliPart>("cli")?;
cli.set_command("ppc");
cli.subcommand( cli.subcommand(
Command::new("editActionList").alias("aal").arg( Command::new("editActionList").alias("aal").arg(
@@ -27,29 +34,109 @@ pub fn timetrack(mize: &mut Mize) -> MizeResult<()> {
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
), ),
command_edit_action_list, command_edit_action_list,
); )?;
cli.subcommand(Command::new("nextAction").alias("ne"), command_next_action); cli.subcommand(Command::new("nextAction").alias("ne").arg(
Arg::new("break")
.short('b')
.action(ArgAction::SetTrue),
).arg(
Arg::new("action")
.trailing_var_arg(true)
.allow_hyphen_values(true)
.num_args(0..), // Accepts 0 or more arguments
), command_next_action)?;
cli.subcommand( cli.subcommand(
Command::new("printCurrentAction").alias("cu"), Command::new("printCurrentAction").alias("cu"),
command_current_action, command_current_action,
); )?;
cli.subcommand( cli.subcommand(
Command::new("listActions").alias("la"), Command::new("listActions").alias("la"),
command_list_actions, command_list_actions,
); )?;
cli.subcommand( cli.subcommand(
Command::new("listActivities").alias("lac"), Command::new("listActivities").alias("lac"),
command_list_activities, command_list_activities,
); )?;
cli.subcommand( cli.subcommand(
Command::new("editActivities").alias("eac"), Command::new("editActivities").alias("eac"),
command_edit_activities, command_edit_activities,
); )?;
mize.add_listener(
"timetrack.sub_current",
Box::new(timetrack_sub_current_listener),
)?;
}
mize.register_part(Box::new(TimeTrack { mize: mize.clone() }))?; mize.register_part(Box::new(TimeTrack { mize: mize.clone() }))?;
Ok(()) Ok(())
} }
fn send_current_update_msg(conn_id: u64, data: TaskPart, mize: &mut Mize) -> MizeResult<()> {
let now = fixed_len(data.current.name.clone(), 48);
let next = fixed_len(
data.upcoming
.iter()
.nth(0)
.unwrap_or(&Action {
name: "".to_string(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
})
.name
.clone(),
48,
);
let msg = MizeMessage::new_custom(
"timetrack.current",
format!(" {} NEXT: {} ", now, next),
conn_id,
);
let con = mize.get_connection(conn_id)?;
con.send(msg)?;
Ok(())
}
fn fixed_len(mut buf: String, len: usize) -> String {
if buf.len() > len {
buf.truncate(len - 4); // Cut off extra characters
buf.push_str(" ...");
} else {
while buf.len() < len {
buf.push(' '); // Fill with whitespace
}
}
buf
}
pub fn timetrack_sub_current_listener(msg: MizeMessage, mize: &mut Mize) -> MizeResult<()> {
let spacetime = mize.get_part_native::<SpacetimePart>("spacetime").unwrap();
let con = spacetime.con();
let mut mize_clone = mize.clone();
let mut mize_clone_two = mize.clone();
con.subscription_builder()
.on_applied(move |ctx| {
let data = ctx.db.task_part().identity().find(&ctx.identity()).unwrap();
if let Err(e) = send_current_update_msg(msg.conn_id, data, &mut mize_clone) {
e.log();
}
})
.add_query(|q| q.from.task_part())
.subscribe();
con.db
.task_part()
.on_update(move |_, _old: &TaskPart, new: &TaskPart| {
if let Err(e) = send_current_update_msg(msg.conn_id, new.clone(), &mut mize_clone_two) {
e.log();
}
});
Ok(())
}
impl MizePartCreate for TimeTrack {} impl MizePartCreate for TimeTrack {}
impl MizePart for TimeTrack { impl MizePart for TimeTrack {
@@ -58,22 +145,45 @@ impl MizePart for TimeTrack {
} }
} }
fn command_next_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { #[cfg(feature = "target-os")]
fn command_next_action(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let con = spacetime.con(); let con = spacetime.con();
let break_flag = matches.get_flag("break");
let action = matches.get_many::<String>("action")
.map(|vals| vals.map(|s| s.as_str()).collect::<Vec<&str>>().join(" "));
let action = match action {
Some(action) => Some(Action {
name: action.clone(),
activity: None,
task: None,
start: None,
end: None,
duration: None,
}),
None => None,
};
let old_action = get_current_action(&mut mize, con)?;
out!("before action: {}", old_action.name);
let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>(); let (wait_tx, waiter_rx) = std::sync::oneshot::channel::<()>();
con.reducers.next_action_then(|_, _| { con.reducers.next_action_then(break_flag, action, |_, _| {
wait_tx.send(()).unwrap(); wait_tx.send(()).unwrap();
})?; })?;
waiter_rx.recv()?; waiter_rx.recv()?;
let new_action = get_current_action(&mut mize, con)?;
out!("new action: {}", new_action.name);
Ok(()) Ok(())
} }
#[cfg(feature = "target-os")]
fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { fn command_current_action(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let action = get_current_action(&mut mize, spacetime.con())?; let action = get_current_action(&mut mize, spacetime.con())?;
println!("{}", action.name); out!("{}", action.name);
Ok(()) Ok(())
} }
@@ -106,6 +216,7 @@ fn get_current_action(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Action
Ok(action) Ok(action)
} }
#[cfg(feature = "target-os")]
fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let activities = get_activities(&mut mize, spacetime.con())?; let activities = get_activities(&mut mize, spacetime.con())?;
@@ -124,24 +235,26 @@ fn command_list_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "target-os")]
fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { fn command_list_actions(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
let actions = get_actions(spacetime.con())?; let actions = get_actions(spacetime.con())?;
for action in actions { for action in actions {
println!("{}", action.name) out!("{}", action.name)
} }
Ok(()) Ok(())
} }
#[cfg(feature = "target-os")]
fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> { fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
// maybe append a template to the action list // maybe append a template to the action list
if matches.get_flag("defaultTemplate") { if matches.get_flag("defaultTemplate") {
println!("Appending template..."); out!("Appending template...");
return Ok(()); return Ok(());
} }
@@ -158,10 +271,7 @@ fn command_edit_action_list(matches: &ArgMatches, mut mize: Mize) -> MizeResult<
.map_err(|e| mize_err!("Failed to write temp file: {}", e))?; .map_err(|e| mize_err!("Failed to write temp file: {}", e))?;
// open $EDITOR blocking // open $EDITOR blocking
std::process::Command::new("nvim") crate::cli_spawn(format!("nvim {}", tmp_file.display()))?;
.arg(&tmp_file)
.status()
.map_err(|e| mize_err!("Failed to run nvim: {}", e))?;
// read file and update db // read file and update db
let new_file_content = std::fs::read_to_string(&tmp_file) let new_file_content = std::fs::read_to_string(&tmp_file)
@@ -239,6 +349,7 @@ fn parse_action(action: &mut Action) {
let _parts: Vec<&str> = action.name.split_whitespace().collect(); let _parts: Vec<&str> = action.name.split_whitespace().collect();
} }
#[cfg(feature = "target-os")]
fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> { fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?; let mut spacetime = mize.get_part_native::<SpacetimePart>("spacetime")?;
@@ -270,10 +381,7 @@ fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
loop { loop {
// open $EDITOR blocking // open $EDITOR blocking
std::process::Command::new("nvim") crate::cli_spawn(format!("nvim {}", tmp_file.display()))?;
.arg(&tmp_file)
.status()
.map_err(|e| mize_err!("Failed to run nvim: {}", e))?;
let new_file_content = std::fs::read_to_string(&tmp_file) let new_file_content = std::fs::read_to_string(&tmp_file)
.map_err(|e| mize_err!("Failed to read temp file: {}", e))?; .map_err(|e| mize_err!("Failed to read temp file: {}", e))?;
@@ -284,8 +392,8 @@ fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
if let Err(err) = if let Err(err) =
parse_activities_from_text(&mut mize, con, new_file_content, old_activities) parse_activities_from_text(&mut mize, con, new_file_content, old_activities)
{ {
println!("error parsing the activities from the file: {:?}", err); out!("error parsing the activities from the file: {:?}", err);
println!("press ENTER to open the file again"); out!("press ENTER to open the file again");
// wait for user input // wait for user input
// so that they can se the err msg, before running nvim again // so that they can se the err msg, before running nvim again
@@ -301,7 +409,7 @@ fn command_edit_activities(_: &ArgMatches, mut mize: Mize) -> MizeResult<()> {
Ok(()) Ok(())
} }
fn get_activities(_mize: &mut Mize, con: &mut DbConnection) -> MizeResult<Vec<Activity>> { fn get_activities(_mize: &mut Mize, con: &DbConnection) -> MizeResult<Vec<Activity>> {
let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Activity>>(); let (exit_tx, exit_rx) = std::sync::oneshot::channel::<Vec<Activity>>();
con.subscription_builder() con.subscription_builder()
.on_applied(|ctx| { .on_applied(|ctx| {
@@ -315,7 +423,7 @@ fn get_activities(_mize: &mut Mize, con: &mut DbConnection) -> MizeResult<Vec<Ac
fn parse_activities_from_text( fn parse_activities_from_text(
mize: &mut Mize, mize: &mut Mize,
con: &mut DbConnection, con: &DbConnection,
text: String, text: String,
old_activities: Vec<Activity>, old_activities: Vec<Activity>,
) -> MizeResult<()> { ) -> MizeResult<()> {
@@ -385,7 +493,7 @@ fn parse_activities_from_text(
// check for declared parents // check for declared parents
let new_activities = get_activities(mize, con)?; let new_activities = get_activities(mize, con)?;
println!("new_activities: {:?}", new_activities); out!("new_activities: {:?}", new_activities);
for line in text.lines() { for line in text.lines() {
if line == "" { if line == "" {
continue; continue;

View File

@@ -21,9 +21,9 @@ required-features = [ "target-os" ]
[features] [features]
default = [ "target-os" ] default = [ "target-os" ]
target-os= [ "clap", "home", "nix", "sysinfo", "ciborium/default", "tracing-subscriber", "async", "tracing-subscriber/env-filter", "libloading", "ciborium-io", "tracing-core", "tar", "flate2", "http_req" ] target-os = [ "clap", "home", "nix", "sysinfo", "ciborium/default", "tracing-subscriber", "async", "tracing-subscriber/env-filter", "libloading", "ciborium-io", "tracing-core", "tar", "flate2", "http_req", "tokio/net", "tokio", "tokio/rt-multi-thread", "tokio/io-util" ]
target-js = [ "wasm-bindgen", "console_error_panic_hook", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/Request", "web-sys/RequestInit", "web-sys/RequestMode", "web-sys/Response", "web-sys/WorkerOptions", "web-sys/WorkerType", "serde-wasm-bindgen", "wasm-bindgen-futures", "wee_alloc"] target-js = [ "wasm-bindgen", "console_error_panic_hook", "web-sys", "web-sys/Worker", "web-sys/Window", "web-sys/Request", "web-sys/RequestInit", "web-sys/RequestMode", "web-sys/Response", "web-sys/WorkerOptions", "web-sys/WorkerType", "serde-wasm-bindgen", "wasm-bindgen-futures", "wee_alloc", "ciborium/default" ]
async = ["tokio/net", "tokio", "tokio/rt-multi-thread", "tokio/io-util"] async = [ "tokio", ]
[dependencies] [dependencies]
@@ -38,13 +38,10 @@ serde_json = { version = "1.0.117", optional = false }
toml = { version = "0.8.13", features = ["parse"], optional = false } toml = { version = "0.8.13", features = ["parse"], optional = false }
dyn-clone = "1.0.17" dyn-clone = "1.0.17"
sha2 = "0.10.8" sha2 = "0.10.8"
flume = "0.11.1" flume = { version = "0.11.1", default-features = false, features = [ "async" ] }
mize_macros = { path = "../ac_mize_macros" } mize_macros = { path = "../macros" }
async-trait = "0.1.89" async-trait = "0.1.89"
# builtin modules
#mize_module_blob = { path = "./modules/modules/Blob" }
# wasm dependencies # wasm dependencies
wasm-bindgen = { version = "0.2.84", optional = true } wasm-bindgen = { version = "0.2.84", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
@@ -60,7 +57,7 @@ daemonize = { version = "0.5.0", optional = true }
nix = { version = "0.27.1", features = ["signal"], optional = true } nix = { version = "0.27.1", features = ["signal"], optional = true }
sysinfo = { version = "0.30.12", optional = true } sysinfo = { version = "0.30.12", optional = true }
tracing-subscriber = { version = "0.3.18", optional = true } tracing-subscriber = { version = "0.3.18", optional = true }
tokio = { version = "1.46.1", features = ["sync"], optional = true } tokio = { version = "1.46.1", features = ["sync", "rt" ], optional = true, default-features = false }
libloading = { version = "0.8.5", optional = true } libloading = { version = "0.8.5", optional = true }
ciborium-io = { version = "0.2.2", optional = true } ciborium-io = { version = "0.2.2", optional = true }
tracing-core = { version = "0.1.32", optional = true } tracing-core = { version = "0.1.32", optional = true }
@@ -95,3 +92,8 @@ wasm-bindgen-test = { version = "0.3.34" }
[profile.release] [profile.release]
# Tell `rustc` to optimize for small code size. # Tell `rustc` to optimize for small code size.
opt-level = "s" opt-level = "s"
[target.'cfg(target_arch = "wasm32")'.dependencies]
# to make uuid be able to use browser apis for generating random numbers
# needs to be the same version as uuid uses
getrandom = { version = "0.4.3", features = ["wasm_js"] }

View File

@@ -1,4 +1,6 @@
use colored::Colorize; use colored::Colorize;
#[cfg(feature = "target-js")]
use wasm_bindgen::JsValue;
use core::result::Result; use core::result::Result;
use core::result::Result::Ok; use core::result::Result::Ok;
use std::collections::HashMap; use std::collections::HashMap;
@@ -24,8 +26,8 @@ pub trait MizeResultTrait<T> {
} }
pub trait IntoMizeResult<T, S> { pub trait IntoMizeResult<T, S> {
fn mize_result(self) -> MizeResult<T>; fn mres(self) -> MizeResult<T>;
fn mize_result_msg(self, msg: S) -> MizeResult<T> fn mres_msg(self, msg: S) -> MizeResult<T>
where where
S: std::fmt::Display; S: std::fmt::Display;
} }
@@ -125,16 +127,16 @@ impl MizeError {
} }
pub fn log(self) -> MizeError { pub fn log(self) -> MizeError {
error!("MizeError encountered!"); println!("[ {} ] MizeError encountered!", "ERROR".red());
if let Some(ref location) = self.code_location { if let Some(ref location) = self.code_location {
error!("[ {} ] {}", "LOCATION".yellow(), location); println!("[ {} ] {}", "LOCATION".yellow(), location);
}; };
error!("[ {} ] {}", "BACKTRACE".yellow(), self.backtrace); println!("[ {} ] {}", "BACKTRACE".yellow(), self.backtrace);
for msg in &self.messages { for msg in &self.messages {
error!("[ {} ] {}", "MSG".yellow(), msg); println!("[ {} ] {}", "MSG".yellow(), msg);
} }
self self
@@ -261,7 +263,7 @@ where
E: std::fmt::Display, E: std::fmt::Display,
{ {
#[track_caller] #[track_caller]
fn mize_result(self) -> MizeResult<T> { fn mres(self) -> MizeResult<T> {
let caller_location = std::panic::Location::caller(); let caller_location = std::panic::Location::caller();
match self { match self {
@@ -274,7 +276,7 @@ where
} }
} }
#[track_caller] #[track_caller]
fn mize_result_msg(self, msg: S) -> MizeResult<T> fn mres_msg(self, msg: S) -> MizeResult<T>
where where
S: std::fmt::Display, S: std::fmt::Display,
{ {
@@ -346,3 +348,20 @@ impl From<MizeError> for MizeErrorWhichImplementsError {
} }
impl std::error::Error for MizeErrorWhichImplementsError {} impl std::error::Error for MizeErrorWhichImplementsError {}
// i hate rust sometimes.
// why can't i impl IntoMizeResult for any type. and the extra concrete impl of it for JsValue overrides the more generic one.
#[cfg(feature = "target-js")]
pub trait IntoMizeResultJsValue<T> {
fn mres(self) -> MizeResult<T>;
}
#[cfg(feature = "target-js")]
impl<T> IntoMizeResultJsValue<T> for Result<T, JsValue> {
fn mres(self) -> MizeResult<T> {
match self {
Ok(value) => Ok(value),
Err(err) => Err(mize_err!("From JsValue: {:?}", err)),
}
}
}

View File

@@ -11,6 +11,7 @@ use std::fs::File;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::panicking;
use std::{thread, vec}; use std::{thread, vec};
use tracing::{debug, error, info, trace, warn, Instrument}; use tracing::{debug, error, info, trace, warn, Instrument};
use uuid::Uuid; use uuid::Uuid;
@@ -63,7 +64,11 @@ pub struct Mize {
pub(crate) id_pool: Arc<Mutex<VecStringPool>>, pub(crate) id_pool: Arc<Mutex<VecStringPool>>,
pub(crate) namespace_pool: Arc<Mutex<StringPool>>, pub(crate) namespace_pool: Arc<Mutex<StringPool>>,
pub(crate) parts: Arc<Mutex<HashMap<&'static str, Option<Box<dyn MizePart + Sync + Send>>>>>, pub(crate) listeners: Arc<
Mutex<HashMap<String, Arc<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>>>,
>,
pub parts: Arc<Mutex<HashMap<&'static str, Option<Box<dyn MizePart + Sync + Send>>>>>,
//pub(crate) platform: Arc<Mutex<Box<dyn MizePlatform>>>, // should we do that instead of all the #cfg(faturs=targt-smth) in lib.rs //pub(crate) platform: Arc<Mutex<Box<dyn MizePlatform>>>, // should we do that instead of all the #cfg(faturs=targt-smth) in lib.rs
part_names: Arc<Mutex<Vec<&'static str>>>, // exists, so that we know the ordering in which parts were loaded part_names: Arc<Mutex<Vec<&'static str>>>, // exists, so that we know the ordering in which parts were loaded
@@ -256,6 +261,7 @@ impl Mize {
parts: Arc::new(Mutex::new(HashMap::new())), parts: Arc::new(Mutex::new(HashMap::new())),
part_names: Arc::new(Mutex::new(Vec::new())), part_names: Arc::new(Mutex::new(Vec::new())),
config_opts: Arc::new(Mutex::new(HashMap::new())), config_opts: Arc::new(Mutex::new(HashMap::new())),
listeners: Arc::new(Mutex::new(HashMap::new())),
connections, connections,
subs, subs,
id_pool, id_pool,
@@ -270,8 +276,9 @@ impl Mize {
create_msg_wait, create_msg_wait,
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg(not(feature = "target-js"))]
runtime: Arc::new(Mutex::new( runtime: Arc::new(Mutex::new(
Runtime::new().mize_result_msg("Could not create async runtime")?, Runtime::new().mres_msg("Could not create async runtime")?,
)), )),
}; };
@@ -354,6 +361,18 @@ impl Mize {
self.parts.lock().unwrap().insert(part.name(), Some(part)); self.parts.lock().unwrap().insert(part.name(), Some(part));
Ok(()) Ok(())
} }
pub fn add_listener(
&mut self,
name: &str,
handler: Box<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>,
) -> MizeResult<()> {
println!("adding listener: {}", name);
let name = name.to_owned();
let mut listeners = self.listeners.lock().unwrap();
listeners.insert(name, Arc::new(handler));
Ok(())
}
fn part_names(&mut self) -> Vec<&'static str> { fn part_names(&mut self) -> Vec<&'static str> {
self.part_names.lock().unwrap().clone() self.part_names.lock().unwrap().clone()
} }
@@ -372,9 +391,16 @@ impl Mize {
// end of platform specific init code // end of platform specific init code
// load the modules, as specified in the parts config // load the modules, as specified in the parts config
match self.get_config("parts")?.value_string() { match self
.get_config("parts")
.unwrap_or(ItemData::new())
.value_string()
{
Ok(parts_to_load) => { Ok(parts_to_load) => {
for part_name in parts_to_load.split(" ") { for part_name in parts_to_load.split(" ") {
if part_name.trim().is_empty() {
continue;
}
self.load_part(part_name)?; self.load_part(part_name)?;
} }
} }
@@ -390,14 +416,35 @@ impl Mize {
pub fn with_config(config: ItemData) -> MizeResult<Mize> { pub fn with_config(config: ItemData) -> MizeResult<Mize> {
trace!("[ {} ] Instance::with_config()", "CALL".yellow()); trace!("[ {} ] Instance::with_config()", "CALL".yellow());
trace!("config: {}", config); trace!("config: {}", config);
println!("here ....");
let mut instance = Mize::empty()?; let mut mize = Mize::empty()?;
instance.set_blocking("0", config); let mut opts = mize.config_opts.lock().unwrap();
let mut config = config.clone();
for mut path in config.get_paths_recursive()? {
if path.starts_with("/") {
path.remove(0);
}
let path_split = path
.split("/")
.map(|s| s.to_owned())
.collect::<Vec<String>>();
let path_string = path_split.join(".");
if let Ok(val) = config.get_path(path_split) {
let opt = ConfigOpt {
name: path.clone(),
val: Some(val),
thunk: None,
};
opts.insert(path_string, opt);
}
}
drop(opts);
instance.init()?; mize.init()?;
Ok(instance) Ok(mize)
} }
pub fn migrate_to_store(&self, new_store: Box<dyn Store>) -> MizeResult<()> { pub fn migrate_to_store(&self, new_store: Box<dyn Store>) -> MizeResult<()> {
@@ -587,7 +634,7 @@ impl Mize {
Ok(self.get_namespace()? == self.get_self_namespace()?) Ok(self.get_namespace()? == self.get_self_namespace()?)
} }
pub fn add_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> { pub fn add_conn_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> {
let mut instance_clone = self.clone(); let mut instance_clone = self.clone();
self.spawn_background("some_listener", move || listener.listen(instance_clone)); self.spawn_background("some_listener", move || listener.listen(instance_clone));
Ok(()) Ok(())
@@ -637,6 +684,21 @@ impl Mize {
} }
pub fn report_err(&self, err: MizeError) { pub fn report_err(&self, err: MizeError) {
#[cfg(feature = "target-js")] unsafe {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => ({ log(&format_args!($($t)*).to_string())})
}
console_log!("reported_err: {:?}", err);
}
err.log(); err.log();
} }
@@ -751,13 +813,16 @@ impl Mize {
let my_thread_id_no_mutex_guard = *next_thread_id; let my_thread_id_no_mutex_guard = *next_thread_id;
let thread_mutex = self.threads.clone(); let thread_mutex = self.threads.clone();
let name_to_move = name.to_owned(); let name_to_move = name.to_owned();
let to_spawn = move || -> MizeResult<()> { let to_spawn = move || -> () {
debug!("spawning thread: {}", name_to_move); debug!("spawning thread: {}", name_to_move);
let my_thread_id = my_thread_id_no_mutex_guard; let my_thread_id = my_thread_id_no_mutex_guard;
func()?; let res = func();
if let Err(e) = res {
e.log();
}
let mut threads_inner = thread_mutex.lock()?; //let mut threads_inner = thread_mutex.lock()?;
/* /*
*threads_inner = threads_inner *threads_inner = threads_inner
.clone() .clone()
@@ -769,7 +834,6 @@ impl Mize {
.collect(); .collect();
*/ */
debug!("thread '{}' stopped", name_to_move); debug!("thread '{}' stopped", name_to_move);
Ok(())
}; };
*next_thread_id += 1; *next_thread_id += 1;
@@ -948,7 +1012,10 @@ impl Mize {
let mut dyn_guard = self.get_part(name)?; let mut dyn_guard = self.get_part(name)?;
let part = dyn_guard.part.take().unwrap(); let part = dyn_guard.part.take().unwrap();
drop(dyn_guard); drop(dyn_guard);
let concrete_part = part.into_any().downcast::<T>().unwrap(); let concrete_part = part
.into_any()
.downcast::<T>()
.map_err(|_| mize_err!("failed to downcast"))?;
Ok(MizePartGuard { Ok(MizePartGuard {
mize: self.clone(), mize: self.clone(),
part: Some(*concrete_part), part: Some(*concrete_part),

View File

@@ -215,6 +215,19 @@ fn handle_msg(msg: &mut MizeMessage, instance: &Mize) -> MizeResult<()> {
} }
return Ok(()); return Ok(());
} }
MessageCmd::Custom(type_val) => {
let listeners = instance.listeners.lock()?;
let type_str = type_val.value_string()?;
let msg = msg.clone();
let mut mize = instance.clone();
println!("type_str: {}", type_str);
println!("listeners: {:?}", listeners.keys());
if let Some(listener) = listeners.get(&type_str) {
let listener = listener.clone();
drop(listeners);
listener(msg, &mut mize)?;
}
}
_ => { _ => {
return Err(mize_err!("got a message, that is not handeled")); return Err(mize_err!("got a message, that is not handeled"));
} }

View File

@@ -50,7 +50,7 @@ impl Item<'_> {
pub fn value_string(&self) -> MizeResult<String> { pub fn value_string(&self) -> MizeResult<String> {
let raw = self.value_raw()?; let raw = self.value_raw()?;
String::from_utf8(raw.clone()) String::from_utf8(raw.clone())
.mize_result_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw)) .mres_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw))
} }
pub fn as_data_full(&self) -> MizeResult<ItemData> { pub fn as_data_full(&self) -> MizeResult<ItemData> {
@@ -193,7 +193,7 @@ impl ItemData {
pub fn from_toml(toml_string: &str) -> MizeResult<ItemData> { pub fn from_toml(toml_string: &str) -> MizeResult<ItemData> {
let toml_deserializer = toml::Deserializer::new(&toml_string); let toml_deserializer = toml::Deserializer::new(&toml_string);
let value = CborValue::deserialize(toml_deserializer).mize_result_msg(format!( let value = CborValue::deserialize(toml_deserializer).mres_msg(format!(
"Could not deserialize the toml string: {}", "Could not deserialize the toml string: {}",
&toml_string &toml_string
))?; ))?;
@@ -246,7 +246,7 @@ impl ItemData {
pub fn from_json(json_str: String) -> MizeResult<ItemData> { pub fn from_json(json_str: String) -> MizeResult<ItemData> {
let mut json_deserializer = serde_json::Deserializer::from_str(json_str.as_str()); let mut json_deserializer = serde_json::Deserializer::from_str(json_str.as_str());
let value = CborValue::deserialize(&mut json_deserializer).mize_result_msg(format!( let value = CborValue::deserialize(&mut json_deserializer).mres_msg(format!(
"could not deserialize the json string: {}", "could not deserialize the json string: {}",
json_str json_str
))?; ))?;
@@ -682,6 +682,9 @@ pub trait IntoItemData {
impl IntoPath for &str { impl IntoPath for &str {
fn into_path(self) -> Vec<String> { fn into_path(self) -> Vec<String> {
if !self.contains('/') {
return self.split(".").map(|v| v.to_owned()).collect();
}
self.to_owned().split("/").map(|v| v.to_owned()).collect() self.to_owned().split("/").map(|v| v.to_owned()).collect()
} }
} }
@@ -704,7 +707,7 @@ impl IntoPath for Vec<&str> {
impl IntoItemData for &str { impl IntoItemData for &str {
fn into_item_data(self) -> ItemData { fn into_item_data(self) -> ItemData {
ItemData::parse(self) ItemData(self.into())
} }
} }
impl IntoItemData for String { impl IntoItemData for String {
@@ -717,6 +720,15 @@ impl IntoItemData for &String {
ItemData(self.to_owned().into()) ItemData(self.to_owned().into())
} }
} }
impl IntoItemData for Vec<String> {
fn into_item_data(self) -> ItemData {
let array = self
.into_iter()
.map(|v| v.into_item_data().cbor().to_owned())
.collect::<Vec<_>>();
ItemData(CborValue::Array(array))
}
}
impl IntoItemData for CborValue { impl IntoItemData for CborValue {
fn into_item_data(self) -> ItemData { fn into_item_data(self) -> ItemData {
ItemData(self) ItemData(self)
@@ -732,6 +744,11 @@ impl IntoItemData for i128 {
ItemData(self.into()) ItemData(self.into())
} }
} }
impl IntoItemData for bool {
fn into_item_data(self) -> ItemData {
ItemData(self.into())
}
}
impl fmt::Display for ItemData { impl fmt::Display for ItemData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@@ -145,7 +145,7 @@ fn id_to_u64(id: MizeId) -> MizeResult<u64> {
} }
fn str_to_u64(id_str: &str) -> MizeResult<u64> { fn str_to_u64(id_str: &str) -> MizeResult<u64> {
id_str.parse().mize_result_msg(format!( id_str.parse().mres_msg(format!(
"Could not parse the store_part of mizeid {} into a u64 for the MemStore", "Could not parse the store_part of mizeid {} into a u64 for the MemStore",
id_str id_str
)) ))

View File

@@ -17,6 +17,17 @@ pub struct MizeMessage {
pub conn_id: u64, pub conn_id: u64,
} }
impl MizeMessage {
pub fn bytes(&self) -> Vec<u8> {
let mut writer = Vec::new();
ciborium::into_writer(&self.value, &mut writer).unwrap();
writer
}
pub fn into_writer(&self, writer: &mut impl io::Write) -> MizeResult<()> {
Ok(ciborium::into_writer(&self.value, writer)?)
}
}
// msg items // msg items
static MSG_CMD: u16 = 1; static MSG_CMD: u16 = 1;
static MSG_ID: u16 = 2; static MSG_ID: u16 = 2;
@@ -42,6 +53,7 @@ pub enum MessageCmd {
UpdateRequest, UpdateRequest,
GetSub, GetSub,
Sub, Sub,
Custom(ItemData),
} }
impl MizeMessage { impl MizeMessage {
@@ -49,6 +61,24 @@ impl MizeMessage {
MizeMessage { value, conn_id } MizeMessage { value, conn_id }
} }
pub fn new_custom<T: IntoItemData, U: IntoItemData>(
custom_cmd: T,
value: U,
conn_id: u64,
) -> MizeMessage {
let value = value.into_item_data();
let custom_cmd = custom_cmd.into_item_data();
let cmd = (CborValue::Integer(MSG_CMD.into()), custom_cmd.0);
let data = (CborValue::Integer(MSG_DATA.into()), value.cbor().to_owned());
let msg_value = CborValue::Map(vec![cmd, data]);
MizeMessage {
value: msg_value,
conn_id,
}
}
pub fn new_get(id: MizeId, conn_id: u64) -> MizeMessage { pub fn new_get(id: MizeId, conn_id: u64) -> MizeMessage {
let id_path = id let id_path = id
.path() .path()
@@ -194,7 +224,7 @@ impl MizeMessage {
} }
}; };
//check if in this map there is a c, otherwise return err //check if in this map there is a cmd, otherwise return err
let val_of_c = { let val_of_c = {
let mut tmp_val = 0; let mut tmp_val = 0;
for (key, val) in msg_as_map { for (key, val) in msg_as_map {
@@ -204,6 +234,8 @@ impl MizeMessage {
if key_int == &one { if key_int == &one {
if let CborValue::Integer(val_int) = val { if let CborValue::Integer(val_int) = val {
tmp_val = val_int.to_owned().into(); tmp_val = val_int.to_owned().into();
} else {
return Ok(MessageCmd::Custom(ItemData(val.to_owned())));
} }
} }
} }
@@ -214,7 +246,7 @@ impl MizeMessage {
tmp_val tmp_val
}; };
// match on value of c // match on value of cmd
let cmd = match val_of_c { let cmd = match val_of_c {
1 => MessageCmd::Get, 1 => MessageCmd::Get,
2 => MessageCmd::Update, 2 => MessageCmd::Update,
@@ -380,7 +412,7 @@ impl fmt::Display for MizeMessage {
self.value self.value
.serialize(&mut serde_json::Serializer::pretty(display_writer)) .serialize(&mut serde_json::Serializer::pretty(display_writer))
.map_err(|serde_err| std::fmt::Error) .map_err(|serde_err| std::fmt::Error)
.mize_result_msg("serialize error") .mres_msg("serialize error")
.inspect_err(|e| { .inspect_err(|e| {
e.clone().log(); e.clone().log();
}); });

View File

@@ -2,6 +2,54 @@
static PROTO_VERSION: u8 = 1; static PROTO_VERSION: u8 = 1;
use std::cell::RefCell;
use std::fmt::format;
thread_local! {
pub static MIZE_STDOUT: RefCell<Option<Box<dyn Fn(String) -> MizeResult<()>>>> = const { RefCell::new(None) };
}
#[macro_export]
macro_rules! out {
($($arg:tt)*) => {
// format_args! handles string interpolation safely at compile time
if let Err(e) = $crate::write_to_stdout(format_args!($($arg)*)) {
// Choose your error handling behavior here (panic, print to stderr, or ignore)
eprintln!("out! macro failed to write!!");
}
};
}
pub fn out_macro_stdout() -> MizeResult<()> {
MIZE_STDOUT.with(|cell| {
*cell.borrow_mut() = Some(Box::new(|s| {
println!("{}", s);
Ok(())
}));
});
Ok(())
}
pub fn write_to_stdout(args: std::fmt::Arguments) -> MizeResult<()> {
MIZE_STDOUT.with(|stdout| {
if let Some(fun) = stdout.borrow().as_ref() {
fun(format!("{}", args));
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stdout not initialized",
))
}
})?;
Ok(())
}
pub mod prelude {
pub use super::*;
}
pub use flume;
#[macro_export] #[macro_export]
macro_rules! test_println { macro_rules! test_println {
($($arg:tt)*) => { ($($arg:tt)*) => {
@@ -43,6 +91,7 @@ mod core {
} }
pub use async_trait::async_trait; pub use async_trait::async_trait;
pub use ciborium;
pub use core::error::MizeError; pub use core::error::MizeError;
pub use core::error::MizeResult; pub use core::error::MizeResult;
pub use core::instance::Mize; pub use core::instance::Mize;
@@ -78,7 +127,8 @@ pub mod platform {
pub use super::os::load_part; pub use super::os::load_part;
#[cfg(feature = "target-js")] #[cfg(feature = "target-js")]
pub use super::js::load_part; //pub use super::js::load_part;
pub use super::super::load_part;
#[cfg(not(any(feature = "target-os", feature = "target-js")))] #[cfg(not(any(feature = "target-os", feature = "target-js")))]
pub use super::super::load_part; pub use super::super::load_part;

View File

@@ -11,10 +11,9 @@ use web_sys::{WorkerOptions, WorkerType};
use crate::core::item::IntoItemData; use crate::core::item::IntoItemData;
use crate::error::MizeResult; use crate::error::MizeResult;
use crate::instance::module::EmptyModule;
use crate::Mize; use crate::Mize;
use crate::MizeError; use crate::MizeError;
use crate::{mize_err, Module}; use crate::mize_err;
// console_log macro // console_log macro
// that can be copied into other files for debugging purposes // that can be copied into other files for debugging purposes
@@ -53,13 +52,14 @@ pub fn wasm_instance_init(mize: &mut Mize) -> MizeResult<()> {
} }
pub fn load_module(mize: &mut Mize, name: &str, path: Option<String>) -> MizeResult<()> { pub fn load_module(mize: &mut Mize, name: &str, path: Option<String>) -> MizeResult<()> {
console_log!("loading module: {}", name); console_log!("loading module NOT IMPLEMENTED: {}", name);
wasm_bindgen_futures::spawn_local(load_module_async(mize.clone(), name.to_owned(), path)); //wasm_bindgen_futures::spawn_local(load_module_async(mize.clone(), name.to_owned(), path));
Ok(()) Ok(())
} }
/*
async fn load_module_async(mut mize: Mize, name: String, path: Option<String>) -> () { async fn load_module_async(mut mize: Mize, name: String, path: Option<String>) -> () {
let name = name.as_str(); let name = name.as_str();
let module_url = fetch_module(&mut mize, name).unwrap(); let module_url = fetch_module(&mut mize, name).unwrap();
@@ -145,8 +145,9 @@ async fn load_module_async(mut mize: Mize, name: String, path: Option<String>) -
} }
*/ */
} }
*/
pub fn fetch_module(mize: &mut Mize, name: &str) -> MizeResult<String> { pub fn fetch_module(mize: &mut Mize, name: &str) -> MizeResult<String> {
let module_name_with_slashes = name.replace(".", "/"); let module_name_with_slashes = name.replace(".", "/");
if let Ok(module_path) = mize if let Ok(module_path) = mize
.get(format!( .get(format!(
@@ -191,6 +192,7 @@ async fn download_module(mize: &mut Mize, name: &str) -> MizeResult<js_sys::Arra
} }
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Clone)]
pub struct JsMize { pub struct JsMize {
inner: NonNull<Mize>, inner: NonNull<Mize>,
} }
@@ -207,10 +209,7 @@ pub async fn new_js_instance(config_json_str: String) -> JsMize {
let config = ItemData::from_json(config_json_str).expect("parsing of json config failed"); let config = ItemData::from_json(config_json_str).expect("parsing of json config failed");
let mut mize = Mize::empty().expect("Mize::empty() failed"); let mut mize = Mize::with_config(config).expect("Mize::new() failed");
mize.set_blocking("0/config", config)
.expect("Failed to set the config at item 0");
let mut js_instance = JsMize { let mut js_instance = JsMize {
inner: NonNull::from(Box::leak(Box::new(mize))), inner: NonNull::from(Box::leak(Box::new(mize))),

Some files were not shown because too many files have changed in this diff Show More