Merge pull request #44 from 4gray/feature/auto-refresh-playlists-on-startup

Feature/auto refresh playlists on startup
This commit is contained in:
4gray authored and GitHub committed 2021-05-18 19:14:09 +02:00
commit 010c589b83
20 files changed
+440 -290

No files matched your search

+3 -2
View File
@@ -11,7 +11,6 @@
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"prettier/@typescript-eslint",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
@@ -38,7 +37,8 @@
"SwitchCase": 1,
"CallExpression": { "arguments": "first" },
"FunctionExpression": { "parameters": "first" },
"FunctionDeclaration": { "parameters": "first" }
"FunctionDeclaration": { "parameters": "first" },
"ignoredNodes": ["TSTypeParameterInstantiation"]
}
],
"@typescript-eslint/no-empty-function": 0,
@@ -49,6 +49,7 @@
"@typescript-eslint/no-unsafe-assignment": 0,
"@typescript-eslint/no-unsafe-return": 0,
"@typescript-eslint/no-floating-promises": 0,
"@typescript-eslint/unbound-method": ["off"],
"@angular-eslint/use-injectable-provided-in": "error",
"@angular-eslint/no-attribute-decorator": "error"
}
+1
View File
@@ -6,3 +6,4 @@
npx stylelint "**/*.scss"
npx --no-install commitlint --edit
npm test -- --coverage
npx eslint src/**/*.ts
-17
View File
@@ -45,23 +45,6 @@
}
]
},
"web": {
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
"extractCss": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
+178 -84
View File
@@ -1,9 +1,13 @@
import { ParsedPlaylist } from './src/typings.d';
import { app, BrowserWindow, ipcMain } from 'electron';
import { parse } from 'iptv-playlist-parser';
import axios from 'axios';
import { guid } from '@datorama/akita';
import { Playlist } from './src/app/home/playlist.interface';
import Nedb from 'nedb-promises-ts';
import {
Playlist,
PlaylistUpdateState,
} from './src/app/home/playlist.interface';
import Nedb, { Cursor } from 'nedb-promises-ts';
import {
EPG_ERROR,
EPG_FETCH,
@@ -36,9 +40,9 @@ export class Api {
workerWindow: BrowserWindow;
constructor() {
ipcMain.on('parse-playlist-by-url', async (event, args) => {
ipcMain.on('parse-playlist-by-url', (event, args) => {
try {
await axios.get(args.url).then((result) => {
axios.get(args.url).then((result) => {
const parsedPlaylist = this.convertFileStringToPlaylist(
result.data
);
@@ -77,55 +81,63 @@ export class Api {
ipcMain.on('playlists-all', (event) => this.sendAllPlaylists(event));
ipcMain.on('playlist-by-id', async (event, args) => {
const playlist = await db.findOne({ _id: args.id });
this.setUserAgent(playlist.userAgent);
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlist,
ipcMain.on('playlist-by-id', (event, args) => {
db.findOne({ _id: args.id }).then((playlist) => {
this.setUserAgent(playlist.userAgent);
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlist,
});
});
});
ipcMain.on('playlist-remove-by-id', async (event, args) => {
const removed = await db.remove({ _id: args.id });
if (removed) {
event.sender.send('playlist-remove-by-id-result', {
message: 'playlist was removed',
});
}
ipcMain.on('playlist-remove-by-id', (event, args) => {
db.remove({ _id: args.id }).then((removed) => {
if (removed) {
event.sender.send('playlist-remove-by-id-result', {
message: 'playlist was removed',
});
}
});
});
// open playlist from file system
ipcMain.on('open-file', (event, args) => {
fs.readFile(args.filePath, 'utf-8', (err, data: string) => {
if (err) {
console.log(
'An error ocurred reading the file :' + err.message
);
return;
}
fs.readFile(
args.filePath,
'utf-8',
(err: NodeJS.ErrnoException, data: string) => {
if (err) {
console.log(
'An error ocurred reading the file :' + err.message
);
return;
}
const parsedPlaylist = this.convertFileStringToPlaylist(data);
const playlistObject = this.createPlaylistObject(
args.fileName,
parsedPlaylist,
args.filePath,
'FILE'
);
this.insertToDb(playlistObject);
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlistObject,
});
});
const parsedPlaylist =
this.convertFileStringToPlaylist(data);
const playlistObject = this.createPlaylistObject(
args.fileName,
parsedPlaylist,
args.filePath,
'FILE'
);
this.insertToDb(playlistObject);
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlistObject,
});
}
);
});
ipcMain.on('update-favorites', async (event, args) => {
const updated = await db.update(
ipcMain.on('update-favorites', (event, args) => {
db.update(
{ id: args.id },
{ $set: { favorites: args.favorites } }
);
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Favorites were not updated');
}
).then((updated) => {
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Favorites were not updated');
}
});
});
// listeners for EPG events
@@ -146,22 +158,29 @@ export class Api {
this.mainWindow.webContents.send(EPG_ERROR, arg)
);
ipcMain.on(PLAYLIST_SAVE_DETAILS, async (event, args) => {
const updated = await db.update(
{ _id: args._id },
{
$set: {
title: args.title,
userAgent: args.userAgent,
autoRefresh: args.autoRefresh,
},
}
);
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Playlist details were not updated');
ipcMain.on(
PLAYLIST_SAVE_DETAILS,
(
event,
args: Pick<
Playlist,
'_id' | 'title' | 'userAgent' | 'autoRefresh'
>
) => {
this.updatePlaylistById(args._id, {
title: args.title,
userAgent: args.userAgent,
autoRefresh: args.autoRefresh,
}).then((updated) => {
if (!updated.numAffected || updated.numAffected === 0) {
console.error(
'Error: Playlist details were not updated'
);
}
this.sendAllPlaylists(event);
});
}
this.sendAllPlaylists(event);
});
);
ipcMain.on(
PLAYLIST_UPDATE,
@@ -173,16 +192,52 @@ export class Api {
}
}
);
this.refreshPlaylists();
}
/**
* Starts the update process for all the playlists with the enabled auto-refresh flag
*/
refreshPlaylists(): void {
this.getAllPlaylistsMeta().then((playlists) => {
playlists.forEach((playlist) => {
if (playlist.autoRefresh && playlist.autoRefresh === true) {
if (playlist.url) {
this.fetchPlaylistByUrl(playlist._id, playlist.url);
} else if (playlist.filePath) {
this.fetchPlaylistByFilePath(
playlist._id,
playlist.filePath
);
} else {
console.log('skip...');
}
}
});
});
}
/**
* Sends list with all playlists which are stored in the database
* @param event main event
*/
async sendAllPlaylists(event: Electron.IpcMainEvent): Promise<void> {
const playlists = await db.find(
{ type: { $exists: false } },
{
sendAllPlaylists(event: Electron.IpcMainEvent): void {
this.getAllPlaylistsMeta().then((playlists) => {
event.sender.send('playlist-all-result', {
payload: playlists,
});
});
}
/**
* Returns all existing playlists with meta information from the database
* @returns
*/
getAllPlaylistsMeta(): Cursor<Playlist> {
return db
.find({ type: { $exists: false } })
.projection({
count: 1,
title: 1,
_id: 1,
@@ -193,11 +248,9 @@ export class Api {
filePath: 1,
autoRefresh: 1,
updateDate: 1,
}
);
event.sender.send('playlist-all-result', {
payload: playlists,
});
updateState: 1,
})
.sort({ importDate: -1 });
}
/**
@@ -280,11 +333,33 @@ export class Api {
playlist,
count: playlist.items.length,
updateDate: Date.now(),
updateState: PlaylistUpdateState.UPDATED,
},
}
);
}
/**
* Updates the provided playlist in the database
* @param id id of the playlist
* @param data playlist data to update
* @returns
*/
updatePlaylistById(
id: string,
data: Partial<Playlist>
): Promise<{
numAffected: number;
upsert: boolean;
}> {
return db.update(
{ _id: id },
{
$set: data,
}
);
}
/**
* Converts the fetched playlist string to the playlist object, updates it in the database and sends the updated playlists array back to the renderer
* @param id id of the playlist to update
@@ -294,20 +369,28 @@ export class Api {
async handlePlaylistRefresh(
id: string,
playlistString: any,
event: Electron.IpcMainEvent
event?: Electron.IpcMainEvent
): Promise<void> {
const playlist = this.convertFileStringToPlaylist(playlistString);
const updated = await this.saveUpdatedPlaylist(id, playlist);
const playlist: ParsedPlaylist =
this.convertFileStringToPlaylist(playlistString);
const updated = await this.updatePlaylistById(id, {
playlist,
count: playlist.items.length,
updateDate: Date.now(),
updateState: PlaylistUpdateState.UPDATED,
});
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Playlist details were not updated');
}
event.sender.send(PLAYLIST_UPDATE_RESPONSE, {
message: `Success! The playlist was successfully updated (${playlist.items.length} channels)`,
});
if (event) {
event.sender.send(PLAYLIST_UPDATE_RESPONSE, {
message: `Success! The playlist was successfully updated (${playlist.items.length} channels)`,
});
// send all playlists back to the renderer process
this.sendAllPlaylists(event);
// send all playlists back to the renderer process
this.sendAllPlaylists(event);
}
}
/**
@@ -319,7 +402,7 @@ export class Api {
async fetchPlaylistByUrl(
id: string,
url: string,
event: Electron.IpcMainEvent
event?: Electron.IpcMainEvent
): Promise<void> {
try {
await axios
@@ -328,8 +411,11 @@ export class Api {
this.handlePlaylistRefresh(id, result.data, event)
);
} catch (err) {
this.updatePlaylistById(id, {
updateState: PlaylistUpdateState.NOT_UPDATED,
});
event.sender.send(ERROR, {
message: `${err.response.statusText}. Please check the entered playlist URL again.`,
message: `File not found. Please check the entered playlist URL again.`,
status: err.response.status,
});
}
@@ -344,19 +430,19 @@ export class Api {
fetchPlaylistByFilePath(
id: string,
path: string,
event: Electron.IpcMainEvent
event?: Electron.IpcMainEvent
): void {
try {
fs.readFile(path, 'utf-8', async (err, data) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (err) {
this.handleFileNotFoundError(err, event);
this.handleFileNotFoundError(err, id, event);
return;
}
this.handlePlaylistRefresh(id, data, event);
});
} catch (err) {
this.handleFileNotFoundError(err, event);
this.handleFileNotFoundError(err, id, event);
}
}
@@ -372,13 +458,21 @@ export class Api {
syscall: string;
path: string;
},
event: Electron.IpcMainEvent
id: string,
event?: Electron.IpcMainEvent
) {
console.error(error);
event.sender.send(ERROR, {
message: `Sorry, playlist was not found (${error.path})`,
status: 'ENOENT',
this.updatePlaylistById(id, {
updateState: PlaylistUpdateState.NOT_UPDATED,
});
if (event) {
// send all playlists back to the renderer process
this.sendAllPlaylists(event);
event.sender.send(ERROR, {
message: `Sorry, playlist was not found (${error.path})`,
status: 'ENOENT',
});
}
}
convertFileStringToPlaylist(m3uString: string): any {
@@ -398,7 +492,7 @@ export class Api {
* Inserts new playlist to the database
* @param playlist playlist to add
*/
insertToDb(playlist: any): void {
insertToDb(playlist: Playlist): void {
db.insert(playlist).then((response) => {
console.log('playlist was saved...', response._id);
});
-2
View File
@@ -14,8 +14,6 @@
"!e2e/",
"!hooks/",
"!angular.json",
"!_config.yml",
"!karma.conf.js",
"!tsconfig.json",
"!tslint.json"
],
+154 -154
View File
@@ -1,157 +1,157 @@
{
"name": "iptvnator",
"version": "0.6.0",
"description": "IPTV player application.",
"homepage": "https://github.com/4gray/iptvnator",
"author": {
"name": "4gray",
"email": "dev@artslab.info"
},
"keywords": [
"angular",
"electron",
"typescript",
"m3u",
"m3u8",
"player",
"iptv",
"video",
"tv"
],
"main": "main.js",
"private": true,
"scripts": {
"postinstall": "electron-builder install-app-deps && husky install",
"ng": "ng",
"start": "npm-run-all -p electron:serve ng:serve",
"build": "npm run electron:serve-tsc && ng build --base-href ./",
"build:dev": "npm run build -- -c dev",
"build:prod": "npm run build -- -c production",
"ng:serve": "ng serve",
"ng:serve:web": "ng serve -c web -o",
"electron:serve-tsc": "tsc -p tsconfig.serve.json",
"electron:serve": "wait-on tcp:4200 && npm run electron:serve-tsc && npx electron . --serve",
"electron:local": "npm run build:prod && npx electron .",
"electron:build": "npm run build:prod && electron-builder build",
"electron:build:mac": "npm run build:prod && electron-builder build -m",
"electron:build:linux": "npm run build:prod && electron-builder build -l --x64 --ia32 --arm64 --armv7l",
"electron:build:windows": "npm run build:prod && electron-builder build -w",
"test": "jest",
"test:watch": "jest --watch",
"e2e": "npm run build:prod && cross-env TS_NODE_PROJECT='e2e/tsconfig.e2e.json' mocha e2e/**/*.ts --config ./.mocharc.json --exit",
"e2e:watch": "npm run e2e -- --watch",
"version": "conventional-changelog -i CHANGELOG.md -p angular -s -r 0 && git add CHANGELOG.md",
"lint": "ng lint"
},
"devDependencies": {
"@angular-builders/custom-webpack": "9.2.0",
"@angular-devkit/build-angular": "0.1101.4",
"@angular-eslint/builder": "0.0.1-alpha.32",
"@angular-eslint/eslint-plugin": "0.0.1-alpha.32",
"@angular-eslint/eslint-plugin-template": "0.0.1-alpha.32",
"@angular-eslint/template-parser": "0.0.1-alpha.32",
"@angular/cli": "11.1.4",
"@angular/common": "11.1.2",
"@angular/compiler": "11.1.2",
"@angular/compiler-cli": "11.1.2",
"@angular/core": "11.1.2",
"@angular/forms": "11.1.2",
"@angular/language-service": "11.1.2",
"@angular/platform-browser": "11.1.2",
"@angular/platform-browser-dynamic": "11.1.2",
"@angular/router": "11.1.2",
"@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "11.0.0",
"@ngx-translate/core": "13.0.0",
"@ngx-translate/http-loader": "6.0.0",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/git": "9.0.0",
"@semantic-release/npm": "7.1.1",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "2.0.8",
"@types/jest": "26.0.14",
"@types/mocha": "7.0.2",
"@types/node": "12.12.6",
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/eslint-plugin-tslint": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"chai": "4.2.0",
"conventional-changelog-cli": "2.0.34",
"core-js": "3.6.5",
"cross-env": "7.0.2",
"electron": "10.2.0",
"electron-builder": "22.9.1",
"eslint": "7.13.0",
"eslint-plugin-import": "2.22.1",
"husky": "5.0.9",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"jest": "26.4.2",
"jest-preset-angular": "8.3.1",
"karma": "5.2.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-electron": "6.3.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"mocha": "7.2.0",
"ng-mocks": "11.5.0",
"npm-run-all": "4.1.5",
"prettier": "2.3.0",
"rxjs": "6.6.3",
"semantic-release": "17.2.3",
"spectron": "12.0.0",
"stylelint": "13.13.1",
"stylelint-config-standard": "22.0.0",
"stylelint-scss": "3.19.0",
"ts-node": "8.10.2",
"tslib": "2.1.0",
"typescript": "4.1.3",
"wait-on": "5.2.1",
"webdriver-manager": "12.1.7",
"zone.js": "~0.11.3"
},
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"@angular/animations": "11.1.2",
"@angular/cdk": "11.1.2",
"@angular/flex-layout": "10.0.0-beta.32",
"@angular/material": "11.1.2",
"@datorama/akita": "5.2.6",
"@ngx-pwa/local-storage": "11.1.0",
"@types/hls.js": "0.13.3",
"@types/video.js": "7.3.11",
"@videojs/http-streaming": "2.3.1",
"about-window": "1.13.4",
"axios": "0.21.1",
"custom-electron-titlebar": "3.2.5",
"epg-parser": "0.1.1",
"hls.js": "0.14.16",
"iptv-playlist-parser": "0.6.0",
"lodash": "4.17.21",
"material-icons": "0.3.1",
"moment": "2.29.1",
"nedb": "1.8.0",
"nedb-promises-ts": "0.0.1-4",
"ngx-filter-pipe": "2.1.2",
"ngx-uploader": "11.0.0",
"ngx-whats-new": "0.0.1",
"semver": "7.3.4",
"uuid": "8.3.0",
"video-js": "0.7.0"
},
"jest": {
"preset": "jest-preset-angular",
"setupFilesAfterEnv": [
"<rootDir>/src/setupJest.ts"
"name": "iptvnator",
"version": "0.6.0",
"description": "IPTV player application.",
"homepage": "https://github.com/4gray/iptvnator",
"author": {
"name": "4gray",
"email": "dev@artslab.info"
},
"keywords": [
"angular",
"electron",
"typescript",
"m3u",
"m3u8",
"player",
"iptv",
"video",
"tv"
],
"testMatch": [
"**/+(*.)+(spec|test).+(ts)?(x)"
],
"coverageReporters": [
"html",
"lcov"
]
}
"main": "main.js",
"private": true,
"scripts": {
"postinstall": "electron-builder install-app-deps && husky install",
"ng": "ng",
"start": "npm-run-all -p electron:serve ng:serve",
"build": "npm run electron:serve-tsc && ng build --base-href ./",
"build:dev": "npm run build -- -c dev",
"build:prod": "npm run build -- -c production",
"ng:serve": "ng serve",
"ng:serve:web": "ng serve -c web -o",
"electron:serve-tsc": "tsc -p tsconfig.serve.json",
"electron:serve": "wait-on tcp:4200 && npm run electron:serve-tsc && npx electron . --serve",
"electron:local": "npm run build:prod && npx electron .",
"electron:build": "npm run build:prod && electron-builder build",
"electron:build:mac": "npm run build:prod && electron-builder build -m",
"electron:build:linux": "npm run build:prod && electron-builder build -l --x64 --ia32 --arm64 --armv7l",
"electron:build:windows": "npm run build:prod && electron-builder build -w",
"test": "jest",
"test:watch": "jest --watch",
"e2e": "npm run build:prod && cross-env TS_NODE_PROJECT='e2e/tsconfig.e2e.json' mocha e2e/**/*.ts --config ./.mocharc.json --exit",
"e2e:watch": "npm run e2e -- --watch",
"version": "conventional-changelog -i CHANGELOG.md -p angular -s -r 0 && git add CHANGELOG.md",
"lint": "ng lint"
},
"devDependencies": {
"@angular-builders/custom-webpack": "9.2.0",
"@angular-devkit/build-angular": "0.1101.4",
"@angular-eslint/builder": "0.0.1-alpha.32",
"@angular-eslint/eslint-plugin": "0.0.1-alpha.32",
"@angular-eslint/eslint-plugin-template": "0.0.1-alpha.32",
"@angular-eslint/template-parser": "0.0.1-alpha.32",
"@angular/cli": "11.1.4",
"@angular/common": "11.1.2",
"@angular/compiler": "11.1.2",
"@angular/compiler-cli": "11.1.2",
"@angular/core": "11.1.2",
"@angular/forms": "11.1.2",
"@angular/language-service": "11.1.2",
"@angular/platform-browser": "11.1.2",
"@angular/platform-browser-dynamic": "11.1.2",
"@angular/router": "11.1.2",
"@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "11.0.0",
"@ngx-translate/core": "13.0.0",
"@ngx-translate/http-loader": "6.0.0",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/git": "9.0.0",
"@semantic-release/npm": "7.1.1",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "2.0.8",
"@types/jest": "26.0.14",
"@types/mocha": "7.0.2",
"@types/node": "12.12.6",
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/eslint-plugin-tslint": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"chai": "4.2.0",
"conventional-changelog-cli": "2.0.34",
"core-js": "3.6.5",
"cross-env": "7.0.2",
"electron": "10.2.0",
"electron-builder": "22.9.1",
"eslint": "7.13.0",
"eslint-plugin-import": "2.22.1",
"husky": "5.0.9",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"jest": "26.4.2",
"jest-preset-angular": "8.3.1",
"karma": "5.2.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-electron": "6.3.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"mocha": "7.2.0",
"ng-mocks": "11.5.0",
"npm-run-all": "4.1.5",
"prettier": "2.3.0",
"rxjs": "6.6.3",
"semantic-release": "17.2.3",
"spectron": "12.0.0",
"stylelint": "13.13.1",
"stylelint-config-standard": "22.0.0",
"stylelint-scss": "3.19.0",
"ts-node": "8.10.2",
"tslib": "2.1.0",
"typescript": "4.1.3",
"wait-on": "5.2.1",
"webdriver-manager": "12.1.7",
"zone.js": "~0.11.3"
},
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"@angular/animations": "11.1.2",
"@angular/cdk": "11.1.2",
"@angular/flex-layout": "10.0.0-beta.32",
"@angular/material": "11.1.2",
"@datorama/akita": "5.2.6",
"@ngx-pwa/local-storage": "11.1.0",
"@types/hls.js": "0.13.3",
"@types/video.js": "7.3.11",
"@videojs/http-streaming": "2.3.1",
"about-window": "1.13.4",
"axios": "0.21.1",
"custom-electron-titlebar": "3.2.5",
"epg-parser": "0.1.1",
"hls.js": "0.14.16",
"iptv-playlist-parser": "0.6.0",
"lodash": "4.17.21",
"material-icons": "0.3.1",
"moment": "2.29.1",
"nedb": "1.8.0",
"nedb-promises-ts": "0.0.1-4",
"ngx-filter-pipe": "2.1.2",
"ngx-uploader": "11.0.0",
"ngx-whats-new": "0.0.1",
"semver": "7.3.4",
"uuid": "8.3.0",
"video-js": "0.7.0"
},
"jest": {
"preset": "jest-preset-angular",
"setupFilesAfterEnv": [
"<rootDir>/src/setupJest.ts"
],
"testMatch": [
"**/+(*.)+(spec|test).+(ts)?(x)"
],
"coverageReporters": [
"html",
"lcov"
]
}
}
+7 -7
View File
@@ -41,18 +41,18 @@ export class HomeComponent {
commandsList = [
{
id: PLAYLIST_PARSE_RESPONSE,
execute: (response: any) => this.setPlaylist(response.payload),
execute: (response: { payload: Playlist }) =>
this.setPlaylist(response.payload),
},
{
id: 'playlist-all-result',
execute: (response: { payload: Partial<Playlist[]> }) =>
(this.playlists = response.payload.sort((a, b) =>
b.importDate.localeCompare(a.importDate)
)),
execute: (response: { payload: Partial<PlaylistMeta[]> }): void => {
this.playlists = response.payload;
},
},
{
id: 'playlist-remove-by-id-result',
execute: () => {
execute: (): void => {
this.snackBar.open('Done! Playlist was removed.', null, {
duration: 2000,
});
@@ -61,7 +61,7 @@ export class HomeComponent {
},
{
id: ERROR,
execute: (response: { message: string; status: number }) => {
execute: (response: { message: string; status: number }): void => {
this.isLoading = false;
this.showNotification(
`Error: ${response.status} ${response.message}.`
+1 -1
View File
@@ -26,6 +26,6 @@ export interface Playlist {
userAgent?: string;
filePath?: string;
autoRefresh: boolean;
updateDate?: string;
updateDate?: number;
updateState?: PlaylistUpdateState;
}
@@ -42,6 +42,27 @@
}}</mat-label>
<input formControlName="userAgent" matInput />
</mat-form-field>
<mat-form-field class="full-width" *ngIf="playlist.filePath">
<mat-label>
{{ 'HOME.PLAYLISTS.INFO_DIALOG.FILE_PATH' | translate }}
</mat-label>
<input formControlName="filePath" matInput />
<mat-hint *ngIf="playlist.updateState === 2">
{{ 'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate }}
</mat-hint>
</mat-form-field>
<mat-checkbox
*ngIf="playlist.url || playlist.filePath"
class="full-width"
formControlName="autoRefresh"
>
{{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}
</mat-checkbox>
<p>
{{
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION' | translate
}}
</p>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button
@@ -10,6 +10,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DatePipe } from '@angular/common';
import { PlaylistInfoComponent } from './playlist-info.component';
import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms';
import { Playlist } from '../../playlist.interface';
describe('PlaylistInfoComponent', () => {
let component: PlaylistInfoComponent;
@@ -51,7 +52,7 @@ describe('PlaylistInfoComponent', () => {
});
it('should send an event to the main process after save', () => {
const playlistToSave = { _id: 'a12345', title: 'Playlist' };
const playlistToSave = { _id: 'a12345', title: 'Playlist' } as Playlist;
spyOn(electronService.ipcRenderer, 'send');
component.saveChanges(playlistToSave);
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(1);
@@ -61,6 +61,11 @@ export class PlaylistInfoComponent {
disabled: true,
}),
url: new FormControl({ value: this.playlist.url, disabled: true }),
filePath: new FormControl({
value: this.playlist.filePath,
disabled: true,
}),
autoRefresh: new FormControl(this.playlist.autoRefresh),
});
}
@@ -68,7 +73,9 @@ export class PlaylistInfoComponent {
* Saves updated playlist information
* @param data updated form data
*/
saveChanges(data: Pick<Playlist, '_id' | 'title' | 'userAgent'>): void {
saveChanges(
data: Pick<Playlist, '_id' | 'title' | 'userAgent' | 'autoRefresh'>
): void {
this.electronService.ipcRenderer.send(PLAYLIST_SAVE_DETAILS, data);
}
}
@@ -36,6 +36,15 @@
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
<ng-container *ngIf="item.updateState === 2">
<mat-icon
[matTooltip]="
'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate
"
[inline]="true"
>warning</mat-icon
>
</ng-container>
</div>
<button
*ngIf="item.url || item.filePath"
+5 -1
View File
@@ -57,7 +57,11 @@
"TITLE": "Titel",
"USER_AGENT": "User agent",
"SAVE": "Speichern",
"ORIGINAL_FILENAME": "Original-Dateinamen"
"ORIGINAL_FILENAME": "Original-Dateinamen",
"FILE_PATH": "Dateipfad",
"UPDATE_FAILED": "Update fehlgeschlagen",
"AUTO_UPDATE": "Auto-update",
"AUTO_UPDATE_DESCRIPTION": "Wenn die Auto-Refresh-Funktion aktiviert ist, wird die Wiedergabeliste bei jedem Start der Anwendung automatisch aktualisiert."
},
"UPDATED": "Aktualisiert am",
"REFRESH": "Aktualisieren"
+5 -1
View File
@@ -25,7 +25,11 @@
"CLOSE": "Close",
"SAVE": "Save",
"USER_AGENT": "User agent",
"ORIGINAL_FILENAME": "Original filename"
"ORIGINAL_FILENAME": "Original filename",
"FILE_PATH": "File path",
"UPDATE_FAILED": "Last playlist update failed",
"AUTO_UPDATE": "Auto-update",
"AUTO_UPDATE_DESCRIPTION": "If the auto-refresh function is activated, the playlist will be updated automatically each time the app is started."
},
"UPDATED": "Updated",
"REFRESH": "Refresh playlist"
+5 -1
View File
@@ -25,7 +25,11 @@
"CLOSE": "Закрыть",
"SAVE": "Сохранить",
"USER_AGENT": "User agent",
"ORIGINAL_FILENAME": "Имя файла при загрузке"
"ORIGINAL_FILENAME": "Имя файла при загрузке",
"FILE_PATH": "Путь к файлу",
"UPDATE_FAILED": "Не удалось обновить плейлист",
"AUTO_UPDATE": "Автообновление",
"AUTO_UPDATE_DESCRIPTION": "Если активирована функция автообновления, список воспроизведения будет обновляться автоматически при каждом запуске приложения."
},
"UPDATED": "Обновлен",
"REFRESH": "Обновить плейлист"
+2 -2
View File
@@ -4,6 +4,6 @@
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const AppConfig = {
production: false,
environment: 'DEV'
production: false,
environment: 'DEV',
};
+2 -2
View File
@@ -1,4 +1,4 @@
export const AppConfig = {
production: true,
environment: 'PROD'
production: true,
environment: 'PROD',
};
-9
View File
@@ -1,9 +0,0 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `index.ts`, but if you do
// `ng build --env=prod` then `index.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const AppConfig = {
production: false,
environment: 'DEV'
};
+5 -5
View File
@@ -5,11 +5,11 @@ import { AppModule } from './app/app.module';
import { AppConfig } from './environments/environment';
if (AppConfig.production) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule, {
preserveWhitespaces: false
})
.catch(err => console.error(err));
.bootstrapModule(AppModule, {
preserveWhitespaces: false,
})
.catch((err) => console.error(err));
+32
View File
@@ -7,3 +7,35 @@ interface Window {
process: any;
require: any;
}
export interface ParsedPlaylist {
header: {
attrs: {
'x-tvg-url': string;
};
raw: string;
};
items: ParsedPlaylistItem[];
}
export interface ParsedPlaylistItem {
name: string;
tvg: {
id: string;
name: string;
language: string;
country: string;
url: string;
logo: string;
rec: string;
};
group: {
title: string;
};
http: {
referrer: string;
'user-agent': string;
};
url: string;
raw: string;
}