Skip to content

Commit

Permalink
feat: add lanes to Backgrounder (#11)
Browse files Browse the repository at this point in the history
* feat: add lanes to Backgrounder

* chore: refactor project structure

* 1.1.1
  • Loading branch information
AriTheElk authored Dec 18, 2024
1 parent 65b5bb9 commit 2462654
Show file tree
Hide file tree
Showing 18 changed files with 330 additions and 193 deletions.
20 changes: 10 additions & 10 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"id": "image-picker",
"name": "Image Picker",
"version": "1.0.1",
"minAppVersion": "0.15.0",
"description": "Adds a UI panel for quickly selecting images that are in your vault.",
"author": "ari.the.elk",
"authorUrl": "https://ari.the.elk.wtf",
"fundingUrl": "https://ari.the.elk.wtf/Donate",
"isDesktopOnly": false
}
"id": "image-picker",
"name": "Image Picker",
"version": "1.1.1",
"minAppVersion": "0.15.0",
"description": "Adds a UI panel for quickly selecting images that are in your vault.",
"author": "ari.the.elk",
"authorUrl": "https://ari.the.elk.wtf",
"fundingUrl": "https://ari.the.elk.wtf/Donate",
"isDesktopOnly": false
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "image-picker",
"version": "1.0.1",
"version": "1.1.1",
"description": "Easily find any image inside your vault",
"main": "dist/main.js",
"scripts": {
Expand Down
49 changes: 49 additions & 0 deletions src/Backgrounder/Backgrounder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ImagePicker } from '../ImagePicker'

import { BackgrounderLane } from './BackgrounderLane'
import { BackgrounderLaneProps } from './types'

/**
* General purpose background job runner :)
*
* This is used mostly to alleviate the main thread from
* doing too much work at once. Things like indexing,
* image processing, or other long-running tasks can be
* run in the background.
*
* It's a FIFO queue, so jobs are run in the order they
* are enqueued.
*/
export class Backgrounder {
public lanes: Record<string, BackgrounderLane> = {}

constructor(public plugin: ImagePicker) {}

log = (...args: unknown[]) => {
this.plugin.log('Backgrounder -> ', ...args)
}

createLane = (lane: Omit<BackgrounderLaneProps, 'queue'>) => {
this.lanes[lane.type] = new BackgrounderLane(this.plugin, lane)
}

deleteLane = (type: string) => {
delete this.lanes[type]
}

stop = (type: string) => {
if (this.lanes[type]) {
this.lanes[type].clear()
this.log('Stopped lane:', type)
} else {
this.log('Lane not found:', type)
}
}

stopAll = () => {
for (const lane of Object.values(this.lanes)) {
lane.clear()
}
this.log('Stopped all lanes')
}
}
112 changes: 112 additions & 0 deletions src/Backgrounder/BackgrounderLane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import ImagePicker from 'src/main'
import { v4 } from 'uuid'

import {
BackgrounderQueue,
BackgrounderLaneProps,
BackgrounderJob,
} from './types'

export class BackgrounderLane {
private plugin: ImagePicker
public type: string
public running: boolean = false
public unique: boolean
public uniqueIgnore: 'first' | 'last'
private queue: BackgrounderQueue
private sleepTime: number = 0

log = (...args: unknown[]) => {
this.plugin.log(`Lane [${this.type}] -> `, ...args)
}

constructor(plugin: ImagePicker, lane: Omit<BackgrounderLaneProps, 'queue'>) {
this.plugin = plugin
this.type = lane.type
this.unique = lane.unique
this.uniqueIgnore = lane.uniqueKeep
this.sleepTime = lane.sleep
this.queue = []
}

/**
* Sleeps between jobs
*
* @returns A promise that resolves after the time has passed
*/
private sleep = () =>
new Promise((resolve) => setTimeout(resolve, this.sleepTime))

/**
* Enqueues a job to be run in the background
*
* @param job The job to enqueue
*/
enqueue = async (job: Omit<BackgrounderJob, 'id'>) => {
const id = v4()
if (this.unique) {
if (
this.uniqueIgnore === 'first' &&
this.queue.some((j) => j.type === job.type)
) {
this.log('Unique job already in queue, ignoring:', job.type)
return
}
if (
this.uniqueIgnore === 'last' &&
this.queue.some((j) => j.type === job.type)
) {
this.log('Unique job already in queue, removing existing:', job.type)
this.queue = this.queue.filter((j) => j.type !== job.type)
}
}

this.queue.push({
...job,
id,
})
this.log('Enqueued:', job.type)

this.run(job.eager && this.queue.length === 1)
}

/**
* Runs the next job in the queue and immediately
* starts the next one.
*/
run = async (immediate: boolean = false) => {
if (this.running || this.queue.length === 0) return
this.running = true

if (!immediate) {
this.log('Waiting to run:', this.queue[0].type)
await this.sleep()
} else {
this.log('Running immediately:', this.queue[0].type)
}

// Verify that the queue hasn't been cleared while sleeping
if (!this.running) return

const job = this.queue.shift()
this.log('Running:', job?.type)
if (job) {
await job.action()
}

this.log('Finished:', job?.type)
this.running = false

// Subsequent jobs should *never* run immediately
this.run()
}

/**
* Clears the queue
*/
clear = () => {
this.queue = []
this.running = false
this.log('Cleared queue')
}
}
2 changes: 2 additions & 0 deletions src/Backgrounder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Backgrounder'
export * from './types'
46 changes: 46 additions & 0 deletions src/Backgrounder/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export interface BackgrounderJob {
/**
* Internally used UUID for the job
*/
id: string
/**
* The type of job, in a small string.
*
* This is used as a "unique" identifier for the job.
* If you run it in a lane with `unique: true`, only
* one job of this type will be in the queue at a time.
*/
type: string
/**
* The action that will be run when the job is executed
*/
action: () => void | Promise<void>
/**
* If true, the job will be run immediately if the lane is free
*/
eager?: boolean
}

export type BackgrounderQueue = BackgrounderJob[]

export interface BackgrounderLaneProps {
type: string
queue: BackgrounderQueue
/**
* The time to wait between jobs in milliseconds
*/
sleep: number
/**
* If true, only one job of this type can be in the queue at a time
*/
unique: boolean
/**
* Determines which job to keep when enqueuing a unique job
*
* 'first' keeps the existing job and ignores the new one
* 'last' keeps the new job and removes the existing one
*
* @default 'first'
*/
uniqueKeep: 'first' | 'last'
}
33 changes: 27 additions & 6 deletions src/ImagePicker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Plugin, TFile, WorkspaceLeaf } from 'obsidian'
import { App, Plugin, PluginManifest, TFile, WorkspaceLeaf } from 'obsidian'
import { pick } from 'lodash'

import { Indexer } from './backend/Indexer'
import { Indexer } from './Indexer'
import {
ImagePickerSettings,
ImagePickerSettingTab,
Expand All @@ -12,15 +12,22 @@ import {
VALID_IMAGE_EXTENSIONS,
VIEW_TYPE_IMAGE_PICKER,
} from './constants'
import { Backgrounder } from './backend/Backgrounder'
import { Backgrounder } from './Backgrounder'

export class ImagePicker extends Plugin {
settings: ImagePickerSettings
images: TFile[] = []
indexer: Indexer = new Indexer(this)
backgrounder: Backgrounder = new Backgrounder(this)
_backgrounder: Backgrounder
get backgrounder() {
return this._backgrounder || (this._backgrounder = new Backgrounder(this))
}

constructor(app: App, manifest: PluginManifest) {
super(app, manifest)
}

log = (...args: any[]) => {
log = (...args: unknown[]) => {
if (this.settings?.debugMode) {
console.log('ImagePicker -> ', ...args)
}
Expand All @@ -43,13 +50,20 @@ export class ImagePicker extends Plugin {
this.app.vault.on('create', this.onFileCreate)
this.app.vault.on('modify', this.onFileChange)
this.app.vault.on('delete', this.onFileDelete)

this.backgrounder.createLane({
type: 'saveSettings',
sleep: 2000,
unique: true,
uniqueKeep: 'last',
})
}

onunload() {
this.app.vault.off('create', this.onFileCreate)
this.app.vault.off('modify', this.onFileChange)
this.app.vault.off('delete', this.onFileDelete)
this.backgrounder.clear()
this.backgrounder.stopAll()
}
/**
* When a file is created, add it to the index and
Expand Down Expand Up @@ -141,4 +155,11 @@ export class ImagePicker extends Plugin {
this.log('Saving settings:', this.settings)
await this.saveData(this.settings)
}

sleepySaveSettings = async () => {
await this.backgrounder.lanes.saveSettings.enqueue({
type: 'sleepySaveSettings',
action: this.saveSettings,
})
}
}
Loading

0 comments on commit 2462654

Please sign in to comment.