Skip to content

Commit

Permalink
feat: refresh playlist from file system or imported url
Browse files Browse the repository at this point in the history
  • Loading branch information
4gray committed May 13, 2021
1 parent 16584cd commit 57cf247
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 26 deletions.
173 changes: 155 additions & 18 deletions api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
EPG_GET_PROGRAM,
EPG_GET_PROGRAM_DONE,
PLAYLIST_SAVE_DETAILS,
PLAYLIST_PARSE,
PLAYLIST_PARSE_RESPONSE,
PLAYLIST_UPDATE,
} from './ipc-commands';

const fs = require('fs');
Expand All @@ -35,15 +38,17 @@ export class Api {
ipcMain.on('parse-playlist-by-url', async (event, args) => {
try {
await axios.get(args.url).then((result) => {
const array = result.data.split('\n');
const parsedPlaylist = this.parsePlaylist(array);
const parsedPlaylist = this.convertFileStringToPlaylist(
result.data
);
const playlistObject = this.createPlaylistObject(
args.title,
parsedPlaylist,
args.url
args.url,
'URL'
);
this.insertToDb(playlistObject);
event.sender.send('parse-response', {
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlistObject,
});
});
Expand All @@ -55,22 +60,26 @@ export class Api {
}
});

ipcMain.on('parse-playlist', (event, args) => {
ipcMain.on(PLAYLIST_PARSE, (event, args) => {
const parsedPlaylist = this.parsePlaylist(args.playlist);
const playlistObject = this.createPlaylistObject(
args.title,
parsedPlaylist
parsedPlaylist,
args.path,
'FILE'
);
this.insertToDb(playlistObject);
event.sender.send('parse-response', { payload: playlistObject });
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlistObject,
});
});

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('parse-response', {
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlist,
});
});
Expand All @@ -86,22 +95,23 @@ export class Api {

// open playlist from file system
ipcMain.on('open-file', (event, args) => {
fs.readFile(args.filePath, 'utf-8', (err, data) => {
fs.readFile(args.filePath, 'utf-8', (err, data: string) => {
if (err) {
console.log(
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
'An error ocurred reading the file :' + err.message
);
return;
}
const array = (data as string).split('\n');
const parsedPlaylist = this.parsePlaylist(array);

const parsedPlaylist = this.convertFileStringToPlaylist(data);
const playlistObject = this.createPlaylistObject(
args.fileName,
parsedPlaylist
parsedPlaylist,
args.filePath,
'FILE'
);
this.insertToDb(playlistObject);
event.sender.send('parse-response', {
event.sender.send(PLAYLIST_PARSE_RESPONSE, {
payload: playlistObject,
});
});
Expand Down Expand Up @@ -138,13 +148,30 @@ export class Api {
ipcMain.on(PLAYLIST_SAVE_DETAILS, async (event, args) => {
const updated = await db.update(
{ _id: args._id },
{ $set: { title: args.title, userAgent: args.userAgent } }
{
$set: {
title: args.title,
userAgent: args.userAgent,
autoRefresh: args.autoRefresh,
},
}
);
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Playlist details were not updated');
}
this.sendAllPlaylists(event);
});

ipcMain.on(
PLAYLIST_UPDATE,
(event, args: { id: string; filePath?: string; url?: string }) => {
if (args.filePath && args.id) {
this.fetchPlaylistByFilePath(args.id, args.filePath, event);
} else if (args.url && args.id) {
this.fetchPlaylistByUrl(args.id, args.url, event);
}
}
);
}

/**
Expand All @@ -162,6 +189,9 @@ export class Api {
importDate: 1,
userAgent: 1,
filename: 1,
filePath: 1,
autoRefresh: 1,
updateDate: 1,
}
);
event.sender.send('playlist-all-result', {
Expand Down Expand Up @@ -204,9 +234,15 @@ export class Api {
* Saves playlist to the localStorage
* @param name name of the playlist
* @param playlist playlist to save
* @param url url of the playlist
* @param urlOrPath absolute fs path or url of the playlist
* @param uploadType upload type - by file or via an url
*/
createPlaylistObject(name: string, playlist: any, url?: string): Playlist {
createPlaylistObject(
name: string,
playlist: any,
urlOrPath?: string,
uploadType?: 'URL' | 'FILE'
): Playlist {
return {
id: guid(),
_id: guid(),
Expand All @@ -223,10 +259,111 @@ export class Api {
importDate: new Date().toISOString(),
lastUsage: new Date().toISOString(),
favorites: [],
...(url ? { url } : {}),
autoRefresh: false,
...(uploadType === 'URL' ? { url: urlOrPath } : {}),
...(uploadType === 'FILE' ? { filePath: urlOrPath } : {}),
};
}

saveUpdatedPlaylist(
id: string,
playlist: any
): Promise<{
numAffected: number;
upsert: boolean;
}> {
return db.update(
{ _id: id },
{
$set: {
playlist,
count: playlist.items.length,
updateDate: Date.now(),
},
}
);
}

/**
* 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
* @param playlistString updated playlist as string
* @param event ipc event to send the response back to the renderer
*/
async handlePlaylistRefresh(
id: string,
playlistString: any,
event: Electron.IpcMainEvent
): Promise<void> {
const playlist = this.convertFileStringToPlaylist(playlistString);
const updated = await this.saveUpdatedPlaylist(id, playlist);
if (!updated.numAffected || updated.numAffected === 0) {
console.error('Error: Playlist details were not updated');
}

// send all playlists back to the renderer process
this.sendAllPlaylists(event);
}

/**
* Fetches the playlist from the given url and triggers the update operation
* @param id id of the playlist to update
* @param playlistString updated playlist as string
* @param event ipc event to send the response back to the renderer
*/
async fetchPlaylistByUrl(
id: string,
url: string,
event: Electron.IpcMainEvent
): Promise<void> {
try {
await axios
.get(url)
.then((result) =>
this.handlePlaylistRefresh(id, result.data, event)
);
} catch (err) {
event.sender.send('error', {
message: err.response.statusText,
status: err.response.status,
});
}
}

/**
* Fetches the playlist from the given path from the file system and triggers the update operation
* @param id id of the playlist to update
* @param playlistString updated playlist as string
* @param event ipc event to send the response back to the renderer
*/
fetchPlaylistByFilePath(
id: string,
path: string,
event: Electron.IpcMainEvent
): void {
const errorMessage = 'PLAYLIST UPDATE: something went wrong';
try {
fs.readFile(path, 'utf-8', async (err, data) => {
if (err) {
console.error(errorMessage, err);
return;
}

this.handlePlaylistRefresh(id, data, event);
});
} catch (err) {
console.error(errorMessage, err);
event.sender.send('error', {
message: 'Something went wrong',
status: 0,
});
}
}

convertFileStringToPlaylist(m3uString: string): any[] {
return this.parsePlaylist(m3uString.split('\n'));
}

/**
* Parses string based array to playlist object
* @param m3uArray m3u playlist as array with strings
Expand Down
4 changes: 4 additions & 0 deletions ipc-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const EPG_GET_PROGRAM_DONE = 'EPG:GET_PROGRAM_DONE';

// Playlist related commands
export const PLAYLIST_SAVE_DETAILS = 'PLAYLIST:SAVE_DETAILS';
export const PLAYLIST_PARSE = 'PLAYLIST:PARSE_PLAYLIST';
export const PLAYLIST_PARSE_RESPONSE = 'PLAYLIST:PARSE_PLAYLIST_RESPONSE';
export const PLAYLIST_UPDATE = 'PLAYLIST:UPDATE';
export const PLAYLIST_UPDATE_RESPONSE = 'PLAYLIST:UPDATE_RESPONSE';

// General
export const SHOW_WHATS_NEW = 'SHOW_WHATS_NEW';
1 change: 1 addition & 0 deletions src/app/home/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<app-recent-playlists
[playlists]="playlists"
(playlistClicked)="getPlaylist($event)"
(refreshClicked)="refreshPlaylist($event)"
(removeClicked)="removePlaylist($event)"
>
</app-recent-playlists>
Expand Down
32 changes: 28 additions & 4 deletions src/app/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, NgZone } from '@angular/core';
import { UploadFile } from 'ngx-uploader';
import { ChannelStore, createChannel } from '../state';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Playlist } from './playlist.interface';
import { ElectronService } from '../services/electron.service';
import {
PLAYLIST_PARSE,
PLAYLIST_PARSE_RESPONSE,
PLAYLIST_UPDATE,
} from './../../../ipc-commands';

/** Type to describe meta data of a playlist */
export type PlaylistMeta = Pick<
Playlist,
'count' | 'title' | 'filename' | '_id' | 'url' | 'importDate' | 'userAgent'
| 'count'
| 'title'
| 'filename'
| '_id'
| 'url'
| 'importDate'
| 'userAgent'
| 'filePath'
| 'updateDate'
>;

@Component({
Expand All @@ -26,7 +38,7 @@ export class HomeComponent {
/** IPC Renderer commands list with callbacks */
commandsList = [
{
id: 'parse-response',
id: PLAYLIST_PARSE_RESPONSE,
execute: (response: any) => this.setPlaylist(response.payload),
},
{
Expand Down Expand Up @@ -110,9 +122,10 @@ export class HomeComponent {
this.isLoading = true;
const result = (payload.uploadEvent.target as FileReader).result;
const array = (result as string).split('\n');
this.electronService.ipcRenderer.send('parse-playlist', {
this.electronService.ipcRenderer.send(PLAYLIST_PARSE, {
title: payload.file.name,
playlist: array,
path: payload.file.nativeFile.path,
});
}

Expand Down Expand Up @@ -166,6 +179,17 @@ export class HomeComponent {
});
}

/**
* Sends an IPC event with the playlist details to the main process to trigger the refresh operation
* @param item playlist to update
*/
refreshPlaylist(item: PlaylistMeta): void {
this.electronService.ipcRenderer.send(PLAYLIST_UPDATE, {
id: item._id,
...(item.url ? { url: item.url } : { filePath: item.filePath }),
});
}

/**
* Requests playlist by id
* @param playlistId playlist id
Expand Down
13 changes: 13 additions & 0 deletions src/app/home/playlist.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { ID } from '@datorama/akita';

/**
* An interface that describe the possible states of the playlist update/refresh process
*/
export enum PlaylistUpdateState {
UPDATED,
IN_PROGRESS,
NOT_UPDATED,
}

/**
* Describes playlist interface
*/
Expand All @@ -15,4 +24,8 @@ export interface Playlist {
count: number;
url?: string;
userAgent?: string;
filePath?: string;
autoRefresh: boolean;
updateDate?: string;
updateState?: PlaylistUpdateState;
}
13 changes: 13 additions & 0 deletions src/app/home/recent-playlists/recent-playlists.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,20 @@
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
{{ item.importDate | date }}
</ng-container>
<ng-container *ngIf="item.updateDate">
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
</div>
<button
*ngIf="item.url || item.filePath"
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
>
<mat-icon>sync</mat-icon>
</button>
<button
mat-icon-button
color="accent"
Expand Down
Loading

0 comments on commit 57cf247

Please sign in to comment.