Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,6 @@ async startInstance(instanceId: string, wakeUp = true): Promise<void> {
- Run tests before submitting changes
- Check that builds complete successfully
- Follow the contribution guidelines in CONTRIBUTING.md
- **Add changelog entries to CHANGELOG.md for functional changes or enhancements** - Focus on the user-facing effect rather than technical implementation details

When working with this codebase, prioritize correctness, maintainability, and following established patterns over clever solutions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## __WORK IN PROGRESS__
* (@Apollon77) Allows only numbers for ts and tc fields in state when provided for setState
* (@GermanBluefox) Added typing for visIconSets in `io-package.json`(for vis-2 SVG icon sets)
* (@copilot) Fixed cleanup of storage meta folder files when deleting adapter instances

## 7.0.7 (2025-04-17) - Lucy
* (@foxriver76) fixed the edge-case problem on Windows (if adapter calls `readDir` on single file)
Expand Down
87 changes: 71 additions & 16 deletions packages/cli/src/lib/setup/setupInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,16 +1083,23 @@
* @param knownObjIDs
* @param adapter
* @param metaFilesToDelete
* @param instance optional instance number for filtering to instance-specific meta objects
*/
async _enumerateAdapterMeta(knownObjIDs: string[], adapter: string, metaFilesToDelete: string[]): Promise<void> {
async _enumerateAdapterMeta(
knownObjIDs: string[],
adapter: string,
metaFilesToDelete: string[],
instance?: number,
): Promise<void> {
try {
const adapterPrefix = `${adapter}${instance !== undefined ? `.${instance}` : ''}`;
const doc = await this.objects.getObjectViewAsync('system', 'meta', {
startkey: `${adapter}.`,
endkey: `${adapter}.\u9999`,
startkey: `${adapterPrefix}.`,
endkey: `${adapterPrefix}.\u9999`,
});

if (doc.rows.length) {
const adapterRegex = new RegExp(`^${adapter}\\.`);
const adapterRegex = new RegExp(`^${adapterPrefix.replace(/\./g, '\\.')}\\.`);

// add non-duplicates to the list
const newObjs = doc.rows
Expand All @@ -1105,7 +1112,7 @@
metaFilesToDelete.push(...newObjs);

if (newObjs.length) {
console.log(`host.${hostname} Counted ${newObjs.length} meta of ${adapter}`);
console.log(`host.${hostname} Counted ${newObjs.length} meta of ${adapterPrefix}`);
}
}
} catch (err) {
Expand Down Expand Up @@ -1369,6 +1376,59 @@
}
}

/**
* Enumerate meta objects for a specific adapter instance
*
* @param knownObjIDs The already known object ids
* @param adapter The adapter name
* @param instance The instance number
* @param metaFilesToDelete Array to collect meta files to delete
*/
/**
* Delete a list of files from the objects database
*
* @param filesToDelete Array of file objects with id and optional name properties
*/
private async _deleteFiles(
filesToDelete: Array<{
id: string;
name?: string;
}>,
): Promise<void> {
for (const file of filesToDelete) {
try {
await this.objects.unlinkAsync(file.id, file.name ?? '');
console.log(`host.${hostname} file ${file.id + (file.name ? `/${file.name}` : '')} deleted`);
} catch (err) {
err !== tools.ERRORS.ERROR_NOT_FOUND &&
err.message !== tools.ERRORS.ERROR_NOT_FOUND &&
console.error(`host.${hostname} Cannot delete ${file.id} files folder: ${err.message}`);
}
}
}

/**
* Delete files for a specific adapter instance
*
* @param adapter adapter name like hm-rpc
* @param instance instance number like 0
*/
private async _deleteInstanceFiles(adapter: string, instance: number): Promise<void> {
const knownObjectIDs: string[] = [];
const metaFilesToDelete: string[] = [];

// Enumerate meta files for this instance
await this._enumerateAdapterMeta(knownObjectIDs, adapter, metaFilesToDelete, instance);

// Create the files to delete list - only instance-specific files
const filesToDelete = [{ id: `${adapter}.${instance}` }, ...metaFilesToDelete.map(id => ({ id }))];

if (filesToDelete.length > 1) {
// More than just the instance folder
await this._deleteFiles(filesToDelete);
}
}

/**
* delete WWW pages, objects and meta files
*
Expand All @@ -1387,17 +1447,7 @@
...metaFilesToDelete.map(id => ({ id })),
];

for (const file of filesToDelete) {
const id = typeof file === 'object' ? file.id : file;
try {
await this.objects.unlinkAsync(id, file.name ?? '');
console.log(`host.${hostname} file ${id + (file.name ? `/${file.name}` : '')} deleted`);
} catch (err) {
err !== tools.ERRORS.ERROR_NOT_FOUND &&
err.message !== tools.ERRORS.ERROR_NOT_FOUND &&
console.error(`host.${hostname} Cannot delete ${id} files folder: ${err.message}`);
}
}
await this._deleteFiles(filesToDelete);

for (const objId of [adapter, `${adapter}.admin`]) {
try {
Expand Down Expand Up @@ -1617,6 +1667,11 @@
await this._enumerateAdapterStates(knownStateIDs, adapter, instance);
await this._enumerateAdapterDocs(knownObjectIDs, adapter, instance);

// Delete files for this specific instance (before deleting objects, since enumeration needs them)
if (instance !== undefined) {
await this._deleteInstanceFiles(adapter, instance);
}

await this._deleteAdapterObjects(knownObjectIDs);
await this._deleteAdapterStates(knownStateIDs);
if (this.params.custom) {
Expand Down
Loading