mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
This commit is contained in:
1 parent
cbc918b096
commit
bd848aaad6
53 files changed
+1980
-87
No files matched your search
@@ -1,4 +1,6 @@
|
||||
import { createServer } from 'node:http';
|
||||
import {
|
||||
xtreamMockServer,
|
||||
addStalkerPortal,
|
||||
addXtreamPortal,
|
||||
closeElectronApp,
|
||||
@@ -506,6 +508,185 @@ https://streams.example.test/url-omega.m3u8
|
||||
}
|
||||
});
|
||||
|
||||
test('skips a source whose startup auto-refresh is still fetching', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
let requests = 0;
|
||||
const server = createServer(() => {
|
||||
requests++;
|
||||
});
|
||||
await new Promise<void>((resolve) =>
|
||||
server.listen(0, '127.0.0.1', resolve)
|
||||
);
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string')
|
||||
throw new Error('Missing server address');
|
||||
const url = `http://127.0.0.1:${address.port}/slow.m3u`;
|
||||
let app = await launchElectronApp(dataDir);
|
||||
try {
|
||||
await app.mainWindow.evaluate(async (url) => {
|
||||
await window.electron.dbUpsertAppPlaylist({
|
||||
_id: 'startup-busy',
|
||||
title: 'Startup busy source',
|
||||
url,
|
||||
count: 0,
|
||||
importDate: '2026-01-01',
|
||||
lastUsage: '2026-01-01',
|
||||
autoRefresh: true,
|
||||
});
|
||||
}, url);
|
||||
app = await restartElectronApp(app, dataDir);
|
||||
await expect.poll(() => requests).toBeGreaterThan(0);
|
||||
await openSources(app.mainWindow);
|
||||
await app.mainWindow
|
||||
.getByRole('button', {
|
||||
name: 'Clean up inactive sources…',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
const dialog = app.mainWindow.getByRole('dialog');
|
||||
const row = dialog.locator('[data-source-id="startup-busy"]');
|
||||
await expect(row).toHaveAttribute('data-status', 'skipped');
|
||||
await expect(row.getByRole('checkbox')).toBeDisabled();
|
||||
expect(
|
||||
await app.mainWindow.evaluate(async () =>
|
||||
(await window.electron.dbGetAppPlaylists()).some(
|
||||
(p) => p._id === 'startup-busy'
|
||||
)
|
||||
)
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
test('cleans selected inactive sources across filters and preserves unselected SQLite data after restart', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
const urlServer = await createMutableTextServer(
|
||||
'<html>Temporarily unavailable</html>',
|
||||
{ resourcePath: '/uncertain.m3u' }
|
||||
);
|
||||
let app = await launchElectronApp(dataDir);
|
||||
try {
|
||||
await app.mainWindow.evaluate(
|
||||
async ({ serverUrl, url }) => {
|
||||
const common = {
|
||||
count: 0,
|
||||
importDate: '2026-01-01',
|
||||
lastUsage: '2026-01-01',
|
||||
autoRefresh: false,
|
||||
};
|
||||
for (const id of ['expired-delete', 'expired-keep']) {
|
||||
await window.electron.dbUpsertAppPlaylist({
|
||||
...common,
|
||||
_id: id,
|
||||
title: id,
|
||||
serverUrl,
|
||||
username: 'expired',
|
||||
password: 'expired',
|
||||
});
|
||||
}
|
||||
await window.electron.dbUpsertAppPlaylist({
|
||||
...common,
|
||||
_id: 'local-keep',
|
||||
title: 'local-keep',
|
||||
favorites: ['preserved-channel'],
|
||||
});
|
||||
await window.electron.dbUpsertAppPlaylist({
|
||||
...common,
|
||||
_id: 'uncertain-keep',
|
||||
title: 'uncertain-keep',
|
||||
url,
|
||||
});
|
||||
},
|
||||
{ serverUrl: xtreamMockServer, url: urlServer.resourceUrl }
|
||||
);
|
||||
app = await restartElectronApp(app, dataDir);
|
||||
await openSources(app.mainWindow);
|
||||
await selectSourceTypeFilter(app.mainWindow, 'M3U');
|
||||
await app.mainWindow
|
||||
.getByRole('button', {
|
||||
name: 'Clean up inactive sources…',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
const dialog = app.mainWindow.getByRole('dialog');
|
||||
await expect(
|
||||
dialog.getByRole('checkbox', {
|
||||
name: 'expired-delete',
|
||||
exact: true,
|
||||
})
|
||||
).toBeChecked({ timeout: 20000 });
|
||||
await expect(
|
||||
dialog.getByRole('checkbox', {
|
||||
name: 'expired-keep',
|
||||
exact: true,
|
||||
})
|
||||
).toBeChecked();
|
||||
await expect(
|
||||
dialog.getByRole('checkbox', {
|
||||
name: 'uncertain-keep',
|
||||
exact: true,
|
||||
})
|
||||
).not.toBeChecked();
|
||||
await expect(
|
||||
dialog.getByRole('checkbox', {
|
||||
name: 'local-keep',
|
||||
exact: true,
|
||||
})
|
||||
).toHaveCount(0);
|
||||
await app.mainWindow.screenshot({
|
||||
path: '/tmp/iptvnator-source-cleanup-light.png',
|
||||
animations: 'disabled',
|
||||
});
|
||||
await app.mainWindow.evaluate(() =>
|
||||
document.body.classList.add('dark-theme')
|
||||
);
|
||||
await app.mainWindow.screenshot({
|
||||
path: '/tmp/iptvnator-source-cleanup-dark.png',
|
||||
animations: 'disabled',
|
||||
});
|
||||
await dialog
|
||||
.getByRole('checkbox', { name: 'expired-keep', exact: true })
|
||||
.uncheck();
|
||||
await dialog
|
||||
.getByRole('button', {
|
||||
name: 'Delete selected · 1',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
await expect(
|
||||
dialog.locator('[data-source-id="expired-delete"]')
|
||||
).toHaveAttribute('data-status', 'deleted', { timeout: 20000 });
|
||||
await dialog
|
||||
.getByRole('button', { name: 'Close', exact: true })
|
||||
.click();
|
||||
app = await restartElectronApp(app, dataDir);
|
||||
const persisted = await app.mainWindow.evaluate(async () => {
|
||||
const playlists = await window.electron.dbGetAppPlaylists();
|
||||
return {
|
||||
ids: playlists.map((p) => p._id),
|
||||
favorites: playlists.find((p) => p._id === 'local-keep')
|
||||
?.favorites,
|
||||
};
|
||||
});
|
||||
expect(persisted.ids.sort()).toEqual([
|
||||
'expired-keep',
|
||||
'local-keep',
|
||||
'uncertain-keep',
|
||||
]);
|
||||
expect(persisted.favorites).toEqual(['preserved-channel']);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
await urlServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('deletes M3U, Stalker, and Xtream sources from the sources view', async ({
|
||||
dataDir,
|
||||
request,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SourceActivityService } from '@iptvnator/services';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
@@ -108,6 +109,18 @@ describe('ElectronService', () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('protects all startup-refresh sources until the request settles, including failure', async () => {
|
||||
let reject!: (error: Error) => void;
|
||||
electronBridge.autoUpdatePlaylists.mockReturnValue(new Promise((_, r) => { reject = r; }));
|
||||
const pending = service.sendIpcEvent(AUTO_UPDATE_PLAYLISTS, [{ _id: 'startup' }]);
|
||||
const activity = TestBed.inject(SourceActivityService);
|
||||
expect(activity.isBusy('startup')).toBe(true);
|
||||
expect(activity.isBusy('other')).toBe(false);
|
||||
reject(new Error('refresh failed'));
|
||||
await expect(pending).rejects.toThrow('refresh failed');
|
||||
expect(activity.isBusy('startup')).toBe(false);
|
||||
});
|
||||
|
||||
it.each([401, 403])('preserves a health HTTP %s rejection without global error handling', async (status) => {
|
||||
const error = new Error(`Error invoking remote method: Error: HTTP Error ${status}`);
|
||||
electronBridge.xtreamRequest.mockRejectedValue(error);
|
||||
|
||||
@@ -4,7 +4,11 @@ import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PlaylistActions } from '@iptvnator/m3u-state';
|
||||
import { DialogService } from '@iptvnator/ui/components';
|
||||
import { DataService, SettingsStore } from '@iptvnator/services';
|
||||
import {
|
||||
DataService,
|
||||
SettingsStore,
|
||||
SourceActivityService,
|
||||
} from '@iptvnator/services';
|
||||
import {
|
||||
AUTO_UPDATE_PLAYLISTS,
|
||||
AutoUpdatePlaylistsResult,
|
||||
@@ -57,6 +61,7 @@ interface ErrorStatus {
|
||||
export class ElectronService extends DataService {
|
||||
private eventListeners: { [key: string]: () => void } = {};
|
||||
private messageListeners = new Map<string, EventListener>();
|
||||
private readonly sourceActivity = inject(SourceActivityService);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private readonly dialogService = inject(DialogService);
|
||||
private readonly store = inject(Store);
|
||||
@@ -237,17 +242,22 @@ export class ElectronService extends DataService {
|
||||
|
||||
if (type === AUTO_UPDATE_PLAYLISTS) {
|
||||
const data = payload as Playlist[];
|
||||
const result = await window.electron.autoUpdatePlaylists(
|
||||
data,
|
||||
this.settingsStore.getTrustOptions()
|
||||
);
|
||||
this.store.dispatch(
|
||||
PlaylistActions.updateManyPlaylists({
|
||||
playlists: result.playlists,
|
||||
})
|
||||
);
|
||||
this.reportAutoUpdatePlaylistsResult(result);
|
||||
return result as T;
|
||||
const release = this.sourceActivity.begin(data.map((p) => p._id));
|
||||
try {
|
||||
const result = await window.electron.autoUpdatePlaylists(
|
||||
data,
|
||||
this.settingsStore.getTrustOptions()
|
||||
);
|
||||
this.store.dispatch(
|
||||
PlaylistActions.updateManyPlaylists({
|
||||
playlists: result.playlists,
|
||||
})
|
||||
);
|
||||
this.reportAutoUpdatePlaylistsResult(result);
|
||||
return result as T;
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug('Unknown IPC event type:', type);
|
||||
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Проверка отменена",
|
||||
"checking": "Проверяется…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Очистить неактивные…",
|
||||
"SCOPE": "Все сетевые источники библиотеки, независимо от фильтров страницы.",
|
||||
"CONSEQUENCES": "Удаляются только выбранные источники, вместе с их избранным, историей и прогрессом воспроизведения. Скачанные файлы сохраняются.",
|
||||
"SUMMARY": "Проверено {{checked}}/{{total}} · Доступно {{available}} · Удалено {{deleted}} · Пропущено {{skipped}} · Ошибок {{failed}}",
|
||||
"SELECT_ALL": "Выбрать всех кандидатов",
|
||||
"SELECT_NONE": "Снять выбор",
|
||||
"UNTITLED": "Источник без названия",
|
||||
"DELETE": "Удалить выбранные · {{count}}",
|
||||
"CLOSE": "Закрыть",
|
||||
"STOP": "Остановить после текущего",
|
||||
"CLEANUP_WARNING": "Источник удалён; часть дополнительной очистки не выполнена",
|
||||
"GROUP": {
|
||||
"confirmed": "Подтверждённо неактивные аккаунты",
|
||||
"uncertain": "Требуют решения",
|
||||
"skipped": "Пропущенные источники",
|
||||
"results": "Результаты удаления"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Проверяется…",
|
||||
"skipped": "Воспроизводится, занят, изменён или уже отсутствует",
|
||||
"deleting": "Удаляется…",
|
||||
"deleted": "Удалён",
|
||||
"failed": "Не удалось удалить — выберите для повтора"
|
||||
},
|
||||
"DELETE_PROGRESS": "Обработано {{done}} из {{total}} выбранных источников"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
@@ -1807,5 +1807,32 @@
|
||||
"cancelled": "Check cancelled",
|
||||
"checking": "Checking…"
|
||||
}
|
||||
},
|
||||
"SOURCE_CLEANUP": {
|
||||
"TITLE": "Clean up inactive sources…",
|
||||
"SCOPE": "All network sources in your library, regardless of page filters.",
|
||||
"CONSEQUENCES": "Delete only sources you select. Their saved favourites, history and playback progress will also be removed. Downloaded files are kept.",
|
||||
"SUMMARY": "Checked {{checked}}/{{total}} · Available {{available}} · Deleted {{deleted}} · Skipped {{skipped}} · Failed {{failed}}",
|
||||
"SELECT_ALL": "Select all candidates",
|
||||
"SELECT_NONE": "Deselect all",
|
||||
"UNTITLED": "Untitled source",
|
||||
"DELETE": "Delete selected · {{count}}",
|
||||
"CLOSE": "Close",
|
||||
"STOP": "Stop after current source",
|
||||
"CLEANUP_WARNING": "Source deleted; some follow-up cleanup failed",
|
||||
"GROUP": {
|
||||
"confirmed": "Confirmed inactive accounts",
|
||||
"uncertain": "Review before deleting",
|
||||
"skipped": "Skipped sources",
|
||||
"results": "Deletion results"
|
||||
},
|
||||
"STATUS": {
|
||||
"checking": "Checking…",
|
||||
"skipped": "Playing, busy, changed or no longer present",
|
||||
"deleting": "Deleting…",
|
||||
"deleted": "Deleted",
|
||||
"failed": "Deletion failed — select to retry"
|
||||
},
|
||||
"DELETE_PROGRESS": "Processed {{done}} of {{total}} selected sources"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user