monitor and cache number of app launches

This commit is contained in:
Aylur
2023-08-18 18:53:55 +02:00
parent d26b867ef3
commit 84f48d7550
5 changed files with 116 additions and 80 deletions
+2 -2
View File
@@ -180,8 +180,8 @@ export default class App extends Gtk.Application {
});
this.emit('config-parsed');
} catch (error) {
logError(error as Error);
} catch (_) {
print(`No config found at ${App.configPath}`);
}
}
+101 -55
View File
@@ -1,79 +1,121 @@
import Gio from 'gi://Gio';
import Service from './service.js';
import GObject from 'gi://GObject';
import { CACHE_DIR, ensureDirectory, readFile, writeFile } from '../utils.js';
interface App {
app: Gio.DesktopAppInfo
name: string
desktop: string | null
description: string | null
wmClass: string | null
executable: string
iconName: string
launch: () => void
match: (term: string) => boolean
const APPS_CACHE_DIR = `${CACHE_DIR}/apps`;
const CACHE_FILE = APPS_CACHE_DIR + '/apps_frequency.json';
class Application extends GObject.Object {
static { GObject.registerClass(this); }
app: Gio.DesktopAppInfo;
frequency: number;
name: string;
desktop: string | null;
description: string | null;
wmClass: string | null;
executable: string;
iconName: string;
service: ApplicationsService;
constructor(app: Gio.DesktopAppInfo, service: ApplicationsService) {
super();
this.service = service;
this.app = app;
this.name = app.get_name();
this.desktop = app.get_id();
this.executable = app.get_executable();
this.description = app.get_description();
this.iconName = this._iconName(app);
this.wmClass = app.get_startup_wm_class();
this.frequency = this.desktop && service.frequents[this.desktop] || 0;
}
_iconName(app: any): string {
if (!app.get_icon())
return '';
if (typeof app.get_icon()?.get_names !== 'function')
return '';
const name = app.get_icon()?.get_names()[0];
return name || '';
}
_match(prop: string | null, search: string) {
if (!prop)
return false;
if (!search)
return true;
return prop?.toLowerCase().includes(search.toLowerCase());
}
match(term: string) {
const { name, desktop, description, executable } = this;
return this._match(name, term) ||
this._match(desktop, term) ||
this._match(executable, term) ||
this._match(description, term);
}
launch() {
this.frequency++;
this.service._launched(this.desktop);
this.app.launch([], null);
}
}
function _appIconName(app: any): string {
if (!app.get_icon())
return '';
if (typeof app.get_icon()?.get_names !== 'function')
return '';
const name = app.get_icon()?.get_names()[0];
return name || '';
}
function _match(prop: string | null, search: string): boolean {
if (!prop)
return false;
if (!search)
return true;
return prop?.toLowerCase().includes(search.toLowerCase());
}
function _search(app: App, search: string): boolean {
const { name, desktop, description, executable } = app;
return _match(name, search) ||
_match(desktop, search) ||
_match(executable, search) ||
_match(description, search);
}
const _wrapper = (app: Gio.DesktopAppInfo): App => ({
app,
name: app.get_name(),
desktop: app.get_id(),
executable: app.get_executable(),
description: app.get_description(),
iconName: _appIconName(app),
wmClass: app.get_startup_wm_class(),
launch: () => app.launch([], null),
match: (term: string) => _search(_wrapper(app), term),
});
class ApplicationsService extends Service {
static { Service.register(this); }
private _list!: App[];
static {
Service.register(this, {
'launched': ['string'],
});
}
private _list!: Application[];
frequents: { [app: string]: number };
query(term: string) {
return this._list.filter(app => _search(app, term));
return this._list.filter(app => app.match(term)).sort((a, b) => {
return a.frequency < b.frequency ? 1 : 0;
});
}
constructor() {
super();
Gio.AppInfoMonitor.get().connect('changed', this._sync.bind(this));
try {
this.frequents = JSON.parse(readFile(CACHE_FILE)) as { [app: string]: number };
} catch (_) {
this.frequents = {};
}
this._sync();
}
_launched(id: string | null) {
if (!id)
return;
typeof this.frequents[id] === 'number'
? this.frequents[id] += 1
: this.frequents[id] = 1;
ensureDirectory(APPS_CACHE_DIR);
const json = JSON.stringify(this.frequents, null, 2);
writeFile(json, CACHE_FILE).catch(logError);
}
_sync() {
this._list = Gio.AppInfo.get_all()
.filter(app => app.should_show())
.map(app => Gio.DesktopAppInfo.new(app.get_id() || ''))
.filter(app => app)
.map(app => _wrapper(app));
.map(app => new Application(app, this));
this.emit('changed');
}
@@ -88,6 +130,10 @@ export default class Applications {
return Applications._instance;
}
static frequents() {
return Applications.instance.frequents;
}
static query(term: string) {
return Applications.instance.query(term);
}
+4 -3
View File
@@ -5,7 +5,9 @@ import Service from './service.js';
import { ensureDirectory, timeout } from '../utils.js';
import { MprisPlayerProxy, MprisProxy, TMprisProxy, TPlayerProxy, MprisMetadata } from '../dbus/mpris.js';
import { DBusProxy, TDBusProxy } from '../dbus/dbus.js';
import { MEDIA_CACHE_PATH } from '../utils.js';
import { CACHE_DIR } from '../utils.js';
const MEDIA_CACHE_PATH = `${CACHE_DIR}/media`;
type PlaybackStatus = 'Playing' | 'Paused' | 'Stopped';
type LoopStatus = 'None' | 'Track' | 'Playlist';
@@ -144,8 +146,7 @@ class MprisPlayer extends GObject.Object {
if (GLib.file_test(coverPath, GLib.FileTest.EXISTS))
return;
ensureDirectory();
ensureDirectory(MEDIA_CACHE_PATH);
Gio.File.new_for_uri(trackCoverUrl).copy_async(
Gio.File.new_for_path(coverPath),
Gio.FileCopyFlags.OVERWRITE,
+8 -6
View File
@@ -4,7 +4,10 @@ import GLib from 'gi://GLib';
import Service from './service.js';
import App from '../app.js';
import { NotificationIFace } from '../dbus/notifications.js';
import { NOTIFICATIONS_CACHE_PATH, ensureDirectory, readFileAsync, timeout, writeFile } from '../utils.js';
import { CACHE_DIR, ensureDirectory, readFileAsync, timeout, writeFile } from '../utils.js';
const NOTIFICATIONS_CACHE_PATH = `${CACHE_DIR}/notifications`;
const CACHE_FILE = NOTIFICATIONS_CACHE_PATH + '/notifications.json';
interface action {
id: string
@@ -183,8 +186,7 @@ class NotificationsService extends Service {
async _readFromFile() {
try {
const path = NOTIFICATIONS_CACHE_PATH + '/notifications.json';
const file = await readFileAsync(path);
const file = await readFileAsync(CACHE_FILE);
const notifications = JSON.parse(file as string) as Notification[];
notifications.forEach(n => {
if (n.id > this._idCount)
@@ -207,7 +209,7 @@ class NotificationsService extends Service {
if (!image_data)
return null;
ensureDirectory();
ensureDirectory(NOTIFICATIONS_CACHE_PATH);
const fileName = NOTIFICATIONS_CACHE_PATH + '/' + name.replace(/[^a-zA-Z0-9]/g, '');
const image = image_data.recursiveUnpack();
const pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(
@@ -237,8 +239,8 @@ class NotificationsService extends Service {
notifications.push(n);
}
ensureDirectory();
writeFile(JSON.stringify(notifications, null, 2), NOTIFICATIONS_CACHE_PATH + '/notifications.json').catch();
ensureDirectory(NOTIFICATIONS_CACHE_PATH);
writeFile(JSON.stringify(notifications, null, 2), CACHE_FILE).catch(logError);
}
}
+1 -14
View File
@@ -5,8 +5,6 @@ import GObject from 'gi://GObject';
export const USER = GLib.get_user_name();
export const CACHE_DIR = `${GLib.get_user_cache_dir()}/${pkg.name}`;
export const MEDIA_CACHE_PATH = `${CACHE_DIR}/media`;
export const NOTIFICATIONS_CACHE_PATH = `${CACHE_DIR}/notifications`;
export function readFile(path: string) {
const f = Gio.File.new_for_path(path);
@@ -129,19 +127,8 @@ export function lookUpIcon(name?: string, size = 16) {
}
export function ensureDirectory(path?: string) {
if (path && !GLib.file_test(path, GLib.FileTest.EXISTS)) {
if (path && !GLib.file_test(path, GLib.FileTest.EXISTS))
Gio.File.new_for_path(path).make_directory_with_parents(null);
}
else {
[
MEDIA_CACHE_PATH,
NOTIFICATIONS_CACHE_PATH,
]
.forEach(path => {
if (!GLib.file_test(path, GLib.FileTest.EXISTS))
Gio.File.new_for_path(path).make_directory_with_parents(null);
});
}
}
export function execAsync(cmd: string | string[]): Promise<string> {