mirror of
https://github.com/HeyPuter/puter.git
synced 2026-02-22 06:30:00 -06:00
dev: move recommended apps to service with cache invalidation
Adds an event for app invalidation. Moves iconify_apps to AppIconService and adds RecommenededAppsService, which holds some logic that used to be in get-launch-apps. The event for app invalidation is then used by RecommenededAppsService to determine when to clear the recommended apps cache.
This commit is contained in:
@@ -209,6 +209,7 @@ function invalidate_cached_user_by_id (id) {
|
||||
async function refresh_apps_cache(options, override){
|
||||
/** @type BaseDatabaseAccessService */
|
||||
const db = services.get('database').get(DB_READ, 'apps');
|
||||
const svc_event = services.get('event');
|
||||
|
||||
const log = services.get('log-service').create('refresh_apps_cache');
|
||||
log.tick('refresh apps cache');
|
||||
@@ -221,6 +222,9 @@ async function refresh_apps_cache(options, override){
|
||||
kv.set('apps:id:' + app.id, app);
|
||||
kv.set('apps:uid:' + app.uid, app);
|
||||
}
|
||||
svc_event.emit('apps.invalidate', {
|
||||
options, apps,
|
||||
});
|
||||
}
|
||||
// refresh only apps that are approved for listing
|
||||
else if(options.only_approved_for_listing){
|
||||
@@ -231,6 +235,9 @@ async function refresh_apps_cache(options, override){
|
||||
kv.set('apps:id:' + app.id, app);
|
||||
kv.set('apps:uid:' + app.uid, app);
|
||||
}
|
||||
svc_event.emit('apps.invalidate', {
|
||||
options, apps,
|
||||
});
|
||||
}
|
||||
// if options is provided, refresh only the app specified
|
||||
else{
|
||||
@@ -259,6 +266,10 @@ async function refresh_apps_cache(options, override){
|
||||
kv.set('apps:id:' + app.id, app);
|
||||
kv.set('apps:uid:' + app.uid, app);
|
||||
}
|
||||
|
||||
svc_event.emit('apps.invalidate', {
|
||||
options, app,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const { LLRead } = require("../../filesystem/ll_operations/ll_read");
|
||||
const { NodePathSelector } = require("../../filesystem/node/selectors");
|
||||
const { get_app } = require("../../helpers");
|
||||
const { Endpoint } = require("../../util/expressutil");
|
||||
const { buffer_to_stream } = require("../../util/streamutil");
|
||||
const { buffer_to_stream, stream_to_buffer } = require("../../util/streamutil");
|
||||
const BaseService = require("../../services/BaseService.js");
|
||||
|
||||
const ICON_SIZES = [16,32,64,128,256,512];
|
||||
@@ -30,6 +30,8 @@ class AppIconService extends BaseService {
|
||||
bmp: require('sharp-bmp'),
|
||||
ico: require('sharp-ico'),
|
||||
}
|
||||
|
||||
static ICON_SIZES = ICON_SIZES;
|
||||
|
||||
/**
|
||||
* AppIconService listens to this event to register the
|
||||
@@ -61,6 +63,37 @@ class AppIconService extends BaseService {
|
||||
},
|
||||
}).attach(app);
|
||||
}
|
||||
|
||||
get_sizes () {
|
||||
return this.constructor.ICON_SIZES;
|
||||
}
|
||||
|
||||
async iconify_apps ({ apps, size }) {
|
||||
return await Promise.all(apps.map(async app => {
|
||||
const icon_result = await this.get_icon_stream({
|
||||
app_icon: app.icon,
|
||||
app_uid: app.uid ?? app.uuid,
|
||||
size: size,
|
||||
});
|
||||
|
||||
if ( icon_result.data_url ) {
|
||||
app.icon = icon_result.data_url;
|
||||
return app;
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = await stream_to_buffer(icon_result.stream);
|
||||
const resp_data_url = `data:${icon_result.mime};base64,${buffer.toString('base64')}`;
|
||||
|
||||
app.icon = resp_data_url;
|
||||
} catch (e) {
|
||||
this.errors.report('get-launch-apps:icon-stream', {
|
||||
source: e,
|
||||
});
|
||||
}
|
||||
return app;
|
||||
}));
|
||||
}
|
||||
|
||||
async get_icon_stream ({ app_icon, app_uid, size, tries = 0 }) {
|
||||
// If there is an icon provided, and it's an SVG, we'll just return it
|
||||
|
||||
@@ -15,6 +15,9 @@ class AppsModule extends AdvancedBase {
|
||||
|
||||
const { ProtectedAppService } = require('./ProtectedAppService');
|
||||
services.registerService('__protected-app', ProtectedAppService);
|
||||
|
||||
const RecommendedAppsService = require('./RecommendedAppsService');
|
||||
services.registerService('recommended-apps', RecommendedAppsService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
119
src/backend/src/modules/apps/RecommendedAppsService.js
Normal file
119
src/backend/src/modules/apps/RecommendedAppsService.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const { get_app } = require("../../helpers");
|
||||
const BaseService = require("../../services/BaseService");
|
||||
|
||||
const get_apps = async ({ specifiers }) => {
|
||||
return await Promise.all(specifiers.map(async (specifier) => {
|
||||
return await get_app(specifier);
|
||||
}));
|
||||
};
|
||||
|
||||
class RecommendedAppsService extends BaseService {
|
||||
static APP_NAMES = [
|
||||
'app-center',
|
||||
'dev-center',
|
||||
'editor',
|
||||
'code',
|
||||
'camera',
|
||||
'recorder',
|
||||
'shell-shockers-outpan',
|
||||
'krunker',
|
||||
'slash-frvr',
|
||||
'viewer',
|
||||
'solitaire-frvr',
|
||||
'terminal',
|
||||
'tiles-beat',
|
||||
'draw',
|
||||
'silex',
|
||||
'markus',
|
||||
'puterjs-playground',
|
||||
'player',
|
||||
'pdf',
|
||||
'photopea',
|
||||
'polotno',
|
||||
'basketball-frvr',
|
||||
'gold-digger-frvr',
|
||||
'plushie-connect',
|
||||
'hex-frvr',
|
||||
'spider-solitaire',
|
||||
'danger-cross',
|
||||
'doodle-jump-extra',
|
||||
'endless-lake',
|
||||
'sword-and-jewel',
|
||||
'reversi-2',
|
||||
'in-orbit',
|
||||
'bowling-king',
|
||||
'calc-hklocykcpts',
|
||||
'virtu-piano',
|
||||
'battleship-war',
|
||||
'turbo-racing',
|
||||
'guns-and-bottles',
|
||||
'tronix',
|
||||
'jewel-classic',
|
||||
];
|
||||
|
||||
_construct () {
|
||||
this.app_names = new Set(RecommendedAppsService.APP_NAMES);
|
||||
}
|
||||
|
||||
['__on_boot.consolidation'] () {
|
||||
const svc_appIcon = this.services.get('app-icon');
|
||||
const svc_event = this.services.get('event');
|
||||
svc_event.on('apps.invalidate', (_, { app }) => {
|
||||
const sizes = svc_appIcon.get_sizes();
|
||||
|
||||
this.log.noticeme('Invalidating recommended apps', { app, sizes });
|
||||
|
||||
// If it's a single-app invalidation, only invalidate if the
|
||||
// app is in the list of recommended apps
|
||||
if ( app ) {
|
||||
const name = app.name;
|
||||
if ( ! this.app_names.has(name) ) return;
|
||||
}
|
||||
|
||||
kv.del('global:recommended-apps');
|
||||
for ( const size of sizes ) {
|
||||
const key = `global:recommended-apps:icon-size:${size}`;
|
||||
kv.del(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async get_recommended_apps ({ icon_size }) {
|
||||
const recommended_cache_key = 'global:recommended-apps' + (
|
||||
icon_size ? `:icon-size:${icon_size}` : ''
|
||||
);
|
||||
|
||||
let recommended = kv.get(recommended_cache_key);
|
||||
if ( recommended ) return recommended;
|
||||
|
||||
// Prepare each app for returning to user by only returning the necessary fields
|
||||
// and adding them to the retobj array
|
||||
recommended = (await get_apps({
|
||||
specifiers: Array.from(this.app_names).map(name => ({ name }))
|
||||
})).filter(app => !! app).map(app => {
|
||||
return {
|
||||
uuid: app.uid,
|
||||
name: app.name,
|
||||
title: app.title,
|
||||
icon: app.icon,
|
||||
godmode: app.godmode,
|
||||
maximize_on_start: app.maximize_on_start,
|
||||
index_url: app.index_url,
|
||||
};
|
||||
});
|
||||
|
||||
const svc_appIcon = this.services.get('app-icon');
|
||||
|
||||
// Iconify apps
|
||||
if ( icon_size ) {
|
||||
recommended = await svc_appIcon.iconify_apps({
|
||||
apps: recommended,
|
||||
size: icon_size,
|
||||
});
|
||||
}
|
||||
|
||||
kv.set(recommended_cache_key, recommended);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecommendedAppsService;
|
||||
@@ -24,12 +24,6 @@ const { get_app } = require('../helpers.js');
|
||||
const { DB_READ } = require('../services/database/consts.js');
|
||||
const { stream_to_buffer } = require('../util/streamutil.js');
|
||||
|
||||
const get_apps = async ({ specifiers }) => {
|
||||
return await Promise.all(specifiers.map(async (specifier) => {
|
||||
return await get_app(specifier);
|
||||
}));
|
||||
};
|
||||
|
||||
const iconify_apps = async (context, { apps, size }) => {
|
||||
return await Promise.all(apps.map(async app => {
|
||||
const svc_appIcon = context.services.get('app-icon');
|
||||
@@ -77,80 +71,10 @@ module.exports = async (req, res) => {
|
||||
// -----------------------------------------------------------------------//
|
||||
// Recommended apps
|
||||
// -----------------------------------------------------------------------//
|
||||
const recommended_cache_key = 'global:recommended-apps' + (
|
||||
req.query.icon_size ? `:icon-size:${req.query.icon_size}` : ''
|
||||
);
|
||||
result.recommended = kv.get(recommended_cache_key);
|
||||
if ( ! result.recommended ) {
|
||||
let app_names = new Set([
|
||||
'app-center',
|
||||
'dev-center',
|
||||
'editor',
|
||||
'code',
|
||||
'camera',
|
||||
'recorder',
|
||||
'shell-shockers-outpan',
|
||||
'krunker',
|
||||
'slash-frvr',
|
||||
'viewer',
|
||||
'solitaire-frvr',
|
||||
'terminal',
|
||||
'tiles-beat',
|
||||
'draw',
|
||||
'silex',
|
||||
'markus',
|
||||
'puterjs-playground',
|
||||
'player',
|
||||
'pdf',
|
||||
'photopea',
|
||||
'polotno',
|
||||
'basketball-frvr',
|
||||
'gold-digger-frvr',
|
||||
'plushie-connect',
|
||||
'hex-frvr',
|
||||
'spider-solitaire',
|
||||
'danger-cross',
|
||||
'doodle-jump-extra',
|
||||
'endless-lake',
|
||||
'sword-and-jewel',
|
||||
'reversi-2',
|
||||
'in-orbit',
|
||||
'bowling-king',
|
||||
'calc-hklocykcpts',
|
||||
'virtu-piano',
|
||||
'battleship-war',
|
||||
'turbo-racing',
|
||||
'guns-and-bottles',
|
||||
'tronix',
|
||||
'jewel-classic',
|
||||
]);
|
||||
|
||||
// Prepare each app for returning to user by only returning the necessary fields
|
||||
// and adding them to the retobj array
|
||||
result.recommended = (await get_apps({
|
||||
specifiers: Array.from(app_names).map(name => ({ name }))
|
||||
})).filter(app => !! app).map(app => {
|
||||
return {
|
||||
uuid: app.uid,
|
||||
name: app.name,
|
||||
title: app.title,
|
||||
icon: app.icon,
|
||||
godmode: app.godmode,
|
||||
maximize_on_start: app.maximize_on_start,
|
||||
index_url: app.index_url,
|
||||
};
|
||||
});
|
||||
|
||||
// Iconify apps
|
||||
if ( req.query.icon_size ) {
|
||||
result.recommended = await iconify_apps({ services: req.services }, {
|
||||
apps: result.recommended,
|
||||
size: req.query.icon_size,
|
||||
});
|
||||
}
|
||||
|
||||
kv.set(recommended_cache_key, result.recommended);
|
||||
}
|
||||
const svc_recommendedApps = req.services.get('recommended-apps');
|
||||
result.recommended = await svc_recommendedApps.get_recommended_apps({
|
||||
icon_size: req.query.icon_size
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------//
|
||||
// Recent apps
|
||||
|
||||
Reference in New Issue
Block a user