Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion desktop/windows/src/main/fileIndex/scanRules.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { describe, it, expect } from 'vitest'
import { shouldVisitDir, shouldIndexFile, MAX_DEPTH, MAX_FILE_SIZE } from './scanRules'
import { shouldVisitDir, shouldIndexFile, SKIP_DIRS, MAX_DEPTH, MAX_FILE_SIZE } from './scanRules'

describe('shouldVisitDir', () => {
it('skips noise directories', () => {
expect(shouldVisitDir('node_modules', 1)).toBe(false)
expect(shouldVisitDir('.git', 1)).toBe(false)
expect(shouldVisitDir('__pycache__', 1)).toBe(false)
expect(shouldVisitDir('.Trash', 1)).toBe(false)
expect(SKIP_DIRS.has('.Trash')).toBe(true)
})
it('skips noise directories case-insensitively on Windows paths', () => {
expect(shouldVisitDir('Node_Modules', 1)).toBe(false)
expect(shouldVisitDir('.GIT', 1)).toBe(false)
expect(shouldVisitDir('__PYCACHE__', 1)).toBe(false)
})
it('visits normal dirs within depth', () => {
expect(shouldVisitDir('src', 1)).toBe(true)
Expand Down
3 changes: 2 additions & 1 deletion desktop/windows/src/main/fileIndex/scanRules.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export const MAX_DEPTH = 3
export const MAX_FILE_SIZE = 500 * 1024 * 1024 // 500 MB, matching macOS
export const SKIP_DIRS = new Set(['.Trash', 'node_modules', '.git', '__pycache__'])
const NORMALIZED_SKIP_DIRS = new Set([...SKIP_DIRS].map((name) => name.toLowerCase()))

// True when a subdirectory at `depth` should be descended into.
export function shouldVisitDir(name: string, depth: number): boolean {
return depth <= MAX_DEPTH && !SKIP_DIRS.has(name)
return depth <= MAX_DEPTH && !NORMALIZED_SKIP_DIRS.has(name.toLowerCase())
}

// True when a file of `sizeBytes` should be recorded.
Expand Down
37 changes: 29 additions & 8 deletions desktop/windows/src/main/usage/userAssist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ function blob(opts: {
len?: number
}): Buffer {
const b = Buffer.alloc(opts.len ?? 72)
if (b.length >= 8) b.writeInt32LE(opts.runCount ?? 0, 4)
if (b.length >= 12) b.writeInt32LE(opts.focusCount ?? 0, 8)
if (b.length >= 16) b.writeInt32LE(opts.focusMs ?? 0, 12)
if (b.length >= 8) b.writeUInt32LE(opts.runCount ?? 0, 4)
if (b.length >= 12) b.writeUInt32LE(opts.focusCount ?? 0, 8)
if (b.length >= 16) b.writeUInt32LE(opts.focusMs ?? 0, 12)
if (b.length >= 68 && opts.lastUsedMs != null) {
// ms epoch -> Windows FILETIME (100ns ticks since 1601-01-01)
const ticks = (BigInt(opts.lastUsedMs) + 11644473600000n) * 10000n
Expand All @@ -39,6 +39,15 @@ describe('parseUserAssistData', () => {
expect(p!.focusCount).toBe(757)
expect(p!.focusSeconds).toBe(43_147) // rounded
})
it('reads high-bit DWORD counters as unsigned values', () => {
const p = parseUserAssistData(
blob({ runCount: 3_000_000_000, focusCount: 4_000_000_000, focusMs: 3_000_000_000 })
)
expect(p).not.toBeNull()
expect(p!.runCount).toBe(3_000_000_000)
expect(p!.focusCount).toBe(4_000_000_000)
expect(p!.focusSeconds).toBe(3_000_000)
})
it('reads last-used from the FILETIME at offset 60', () => {
const when = Date.UTC(2026, 5, 3, 12, 0, 0)
const p = parseUserAssistData(blob({ focusMs: 1000, lastUsedMs: when }))
Expand All @@ -63,7 +72,9 @@ describe('friendlyAppName', () => {
expect(friendlyAppName('Telegram.TelegramDesktop')).toBe('TelegramDesktop')
})
it('strips the package-family hash and !App suffix from packaged AUMIDs', () => {
expect(friendlyAppName('Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic')).toBe('ZuneMusic')
expect(friendlyAppName('Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic')).toBe(
'ZuneMusic'
)
expect(friendlyAppName('5319275A.WhatsAppDesktop_cv1g1gvanyjgm!App')).toBe('WhatsAppDesktop')
// Uses the package-name segment, not the !Activatable id. "SpotifyMusic"
// still matches an indexed "Spotify" via rankApps' containment rule.
Expand All @@ -74,11 +85,15 @@ describe('friendlyAppName', () => {
})
it('uses the exe basename (without .exe) for full paths', () => {
expect(friendlyAppName('C:\\Users\\me\\AppData\\Local\\Programs\\Warp\\Warp.exe')).toBe('Warp')
expect(friendlyAppName('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe')).toBe('chrome')
expect(friendlyAppName('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe')).toBe(
'chrome'
)
})
it('ignores a leading KNOWNFOLDERID GUID segment in a path', () => {
expect(
friendlyAppName('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe')
friendlyAppName(
'{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'
)
).toBe('powershell')
})
it('returns null for empty / GUID-only names', () => {
Expand All @@ -91,9 +106,15 @@ describe('aggregateUserAssist', () => {
it('decodes, drops control entries, and sums focus time per friendly name', () => {
const raw = [
{ name: rot13('UEME_CTLSESSION'), data: blob({ focusMs: 999_999 }) },
{ name: rot13('dev.warp.Warp'), data: blob({ focusMs: 60_000, runCount: 3, lastUsedMs: 100 }) },
{
name: rot13('dev.warp.Warp'),
data: blob({ focusMs: 60_000, runCount: 3, lastUsedMs: 100 })
},
// same friendly name via a full path -> merged
{ name: rot13('C:\\x\\Warp\\Warp.exe'), data: blob({ focusMs: 30_000, runCount: 2, lastUsedMs: 200 }) },
{
name: rot13('C:\\x\\Warp\\Warp.exe'),
data: blob({ focusMs: 30_000, runCount: 2, lastUsedMs: 200 })
},
{ name: rot13('Chrome'), data: blob({ focusMs: 120_000, lastUsedMs: 50 }) }
]
const out = aggregateUserAssist(raw)
Expand Down
6 changes: 3 additions & 3 deletions desktop/windows/src/main/usage/userAssist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ export function rot13(s: string): string {
// hold the focus-time field (control/sentinel entries can be tiny).
export function parseUserAssistData(data: Buffer): ParsedUserAssist | null {
if (data.length < OFF_FOCUS_MS + 4) return null
const focusMs = data.readInt32LE(OFF_FOCUS_MS)
const focusMs = data.readUInt32LE(OFF_FOCUS_MS)
let lastUsed = 0
if (data.length >= OFF_LAST_USED_FILETIME + 8) {
const ticks = data.readBigUInt64LE(OFF_LAST_USED_FILETIME)
if (ticks > 0n) lastUsed = Number(ticks / 10_000n - FILETIME_UNIX_OFFSET_MS)
}
return {
runCount: data.readInt32LE(OFF_RUN_COUNT),
focusCount: data.readInt32LE(OFF_FOCUS_COUNT),
runCount: data.readUInt32LE(OFF_RUN_COUNT),
focusCount: data.readUInt32LE(OFF_FOCUS_COUNT),
focusSeconds: Math.round(focusMs / 1000),
lastUsed
}
Expand Down
Loading