mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
chore: update subproject commit and add blank lines for consistency
- Updated the subproject commit reference to indicate a dirty state. - Added blank lines to various files for improved readability and consistency across the codebase.
This commit is contained in:
parent
8d751b0713
commit
d31ba54080
@ -18,3 +18,4 @@
|
|||||||
../../test_snapshots/**
|
../../test_snapshots/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -68,3 +68,4 @@ enum VideoMediaData {
|
|||||||
Blurhash,
|
Blurhash,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -47,3 +47,4 @@ enum Entries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -8,3 +8,4 @@ pub use action::LibraryOpenAction;
|
|||||||
pub use input::LibraryOpenInput;
|
pub use input::LibraryOpenInput;
|
||||||
pub use output::LibraryOpenOutput;
|
pub use output::LibraryOpenOutput;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -122,3 +122,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,3 +14,4 @@ pub struct VolumeTrackInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,3 +11,4 @@ pub struct VolumeUntrackInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
crates/log-analyzer/.gitignore
vendored
1
crates/log-analyzer/.gitignore
vendored
@ -5,3 +5,4 @@ analysis.md
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -39,3 +39,4 @@ required-features = ["cli"]
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -50,3 +50,4 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,3 +17,4 @@ pub fn export_json(templates: &[Template], groups: &[LogGroup]) -> Result<String
|
|||||||
Ok(serde_json::to_string_pretty(&export)?)
|
Ok(serde_json::to_string_pretty(&export)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -239,3 +239,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -104,3 +104,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@sd/assets": "workspace:*",
|
"@sd/assets": "workspace:*",
|
||||||
|
"@sd/ts-client": "workspace:*",
|
||||||
"@sd/ui": "workspace:*",
|
"@sd/ui": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.90.7",
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"@tanstack/react-query-devtools": "^5.90.2",
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
@ -35,12 +36,12 @@
|
|||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.53.2",
|
"react-hook-form": "^7.53.2",
|
||||||
"react-router-dom": "^6.20.1",
|
"react-router-dom": "^6.20.1",
|
||||||
|
"react-scan": "^0.4.3",
|
||||||
"rooks": "^9.3.0",
|
"rooks": "^9.3.0",
|
||||||
"sonner": "^1.0.3",
|
"sonner": "^1.0.3",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"zod": "^3.23",
|
"zod": "^3.23",
|
||||||
"zustand": "^5.0.8",
|
"zustand": "^5.0.8"
|
||||||
"@sd/ts-client": "workspace:*"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "npm:types-react@rc",
|
"@types/react": "npm:types-react@rc",
|
||||||
|
|||||||
@ -57,3 +57,4 @@ export function useJobDispatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
export { SpacedriveClient } from "./client";
|
export { SpacedriveClient } from "./client";
|
||||||
export type { Transport } from "./transport";
|
export type { Transport } from "./transport";
|
||||||
export { UnixSocketTransport, TauriTransport } from "./transport";
|
export { UnixSocketTransport, TauriTransport } from "./transport";
|
||||||
|
export { SubscriptionManager } from "./subscriptionManager";
|
||||||
|
|
||||||
// Event filtering utilities
|
// Event filtering utilities
|
||||||
export {
|
export {
|
||||||
|
|||||||
163
packages/ts-client/src/subscriptionManager.ts
Normal file
163
packages/ts-client/src/subscriptionManager.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/**
|
||||||
|
* Subscription Manager - Multiplexes event subscriptions
|
||||||
|
*
|
||||||
|
* Problem: Each useNormalizedQuery creates its own subscription, causing:
|
||||||
|
* - Hundreds of subscriptions during render cycles
|
||||||
|
* - Rapid subscribe/unsubscribe churn
|
||||||
|
* - Multiple subscriptions with identical filters
|
||||||
|
*
|
||||||
|
* Solution: Pool subscriptions by filter signature:
|
||||||
|
* - One backend subscription serves many hooks
|
||||||
|
* - Reference counting prevents premature cleanup
|
||||||
|
* - Automatic cleanup when last listener unsubscribes
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Transport } from "./transport";
|
||||||
|
import type { Event } from "./generated/types";
|
||||||
|
|
||||||
|
interface EventFilter {
|
||||||
|
library_id?: string;
|
||||||
|
resource_type?: string;
|
||||||
|
path_scope?: any;
|
||||||
|
include_descendants?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubscriptionEntry {
|
||||||
|
/** Cleanup function from transport */
|
||||||
|
unsubscribe: () => void;
|
||||||
|
/** All listeners for this subscription */
|
||||||
|
listeners: Set<(event: Event) => void>;
|
||||||
|
/** Reference count (number of hooks using this) */
|
||||||
|
refCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubscriptionManager {
|
||||||
|
private subscriptions = new Map<string, SubscriptionEntry>();
|
||||||
|
private transport: Transport;
|
||||||
|
|
||||||
|
constructor(transport: Transport) {
|
||||||
|
this.transport = transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate stable key for filter
|
||||||
|
* Same filters = same key = shared subscription
|
||||||
|
*/
|
||||||
|
private getFilterKey(filter: EventFilter): string {
|
||||||
|
return JSON.stringify({
|
||||||
|
library_id: filter.library_id ?? null,
|
||||||
|
resource_type: filter.resource_type ?? null,
|
||||||
|
path_scope: filter.path_scope ?? null,
|
||||||
|
include_descendants: filter.include_descendants ?? false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to filtered events
|
||||||
|
* Reuses existing subscription if filter matches
|
||||||
|
*/
|
||||||
|
async subscribe(
|
||||||
|
filter: EventFilter,
|
||||||
|
callback: (event: Event) => void,
|
||||||
|
): Promise<() => void> {
|
||||||
|
const key = this.getFilterKey(filter);
|
||||||
|
let entry = this.subscriptions.get(key);
|
||||||
|
|
||||||
|
// Create new subscription if needed
|
||||||
|
if (!entry) {
|
||||||
|
console.log(
|
||||||
|
`[SubscriptionManager] Creating new subscription for key: ${key}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsubscribe = await this.transport.subscribe(
|
||||||
|
(event) => {
|
||||||
|
// Broadcast event to all listeners
|
||||||
|
const currentEntry = this.subscriptions.get(key);
|
||||||
|
if (currentEntry) {
|
||||||
|
currentEntry.listeners.forEach((listener) => listener(event));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event_types: [
|
||||||
|
"ResourceChanged",
|
||||||
|
"ResourceChangedBatch",
|
||||||
|
"ResourceDeleted",
|
||||||
|
"Refresh",
|
||||||
|
],
|
||||||
|
filter: {
|
||||||
|
resource_type: filter.resource_type,
|
||||||
|
path_scope: filter.path_scope,
|
||||||
|
library_id: filter.library_id,
|
||||||
|
include_descendants: filter.include_descendants,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
unsubscribe,
|
||||||
|
listeners: new Set(),
|
||||||
|
refCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.subscriptions.set(key, entry);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[SubscriptionManager] Reusing existing subscription for key: ${key} (refCount: ${entry.refCount})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add listener and increment ref count
|
||||||
|
entry.listeners.add(callback);
|
||||||
|
entry.refCount++;
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => {
|
||||||
|
const currentEntry = this.subscriptions.get(key);
|
||||||
|
if (!currentEntry) return;
|
||||||
|
|
||||||
|
// Remove listener and decrement ref count
|
||||||
|
currentEntry.listeners.delete(callback);
|
||||||
|
currentEntry.refCount--;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[SubscriptionManager] Listener removed from ${key} (refCount: ${currentEntry.refCount})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cleanup subscription if no more listeners
|
||||||
|
if (currentEntry.refCount === 0) {
|
||||||
|
console.log(
|
||||||
|
`[SubscriptionManager] Destroying subscription for key: ${key}`,
|
||||||
|
);
|
||||||
|
currentEntry.unsubscribe();
|
||||||
|
this.subscriptions.delete(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stats for debugging
|
||||||
|
*/
|
||||||
|
getStats() {
|
||||||
|
return {
|
||||||
|
activeSubscriptions: this.subscriptions.size,
|
||||||
|
subscriptions: Array.from(this.subscriptions.entries()).map(
|
||||||
|
([key, entry]) => ({
|
||||||
|
key,
|
||||||
|
refCount: entry.refCount,
|
||||||
|
listenerCount: entry.listeners.size,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force cleanup all subscriptions (for testing/cleanup)
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
console.log(
|
||||||
|
`[SubscriptionManager] Destroying ${this.subscriptions.size} subscriptions`,
|
||||||
|
);
|
||||||
|
this.subscriptions.forEach((entry) => entry.unsubscribe());
|
||||||
|
this.subscriptions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user