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/**
|
||||
|
||||
|
||||
|
||||
|
||||
@ -68,3 +68,4 @@ enum VideoMediaData {
|
||||
Blurhash,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -47,3 +47,4 @@ enum Entries {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -8,3 +8,4 @@ pub use action::LibraryOpenAction;
|
||||
pub use input::LibraryOpenInput;
|
||||
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)?)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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-tooltip": "^1.0.7",
|
||||
"@sd/assets": "workspace:*",
|
||||
"@sd/ts-client": "workspace:*",
|
||||
"@sd/ui": "workspace:*",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
@ -35,12 +36,12 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-scan": "^0.4.3",
|
||||
"rooks": "^9.3.0",
|
||||
"sonner": "^1.0.3",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"zod": "^3.23",
|
||||
"zustand": "^5.0.8",
|
||||
"@sd/ts-client": "workspace:*"
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "npm:types-react@rc",
|
||||
|
||||
@ -57,3 +57,4 @@ export function useJobDispatch() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
export { SpacedriveClient } from "./client";
|
||||
export type { Transport } from "./transport";
|
||||
export { UnixSocketTransport, TauriTransport } from "./transport";
|
||||
export { SubscriptionManager } from "./subscriptionManager";
|
||||
|
||||
// Event filtering utilities
|
||||
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