mirror of
https://github.com/fergalmoran/podnoms.git
synced 2026-01-01 22:27:53 +00:00
Added some error handling for add entry
This commit is contained in:
@@ -1,7 +1,33 @@
|
||||
<div class="input-group add-url-form">
|
||||
<input type="text" id="input2-group2" name="entry-url" [(ngModel)]="newEntrySourceUrl" class="form-control" placeholder="New entry URL">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-primary" (click)="addEntry(podcast)">
|
||||
<i class="fa fa-plus-circle"></i> Submit</button>
|
||||
</span>
|
||||
<form class="push">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text"
|
||||
name="entry-url"
|
||||
#input
|
||||
[disabled]="isPosting"
|
||||
[(ngModel)]="newEntrySourceUrl"
|
||||
class="form-control"
|
||||
placeholder="Paste (or type) URL and press enter or the button">
|
||||
<div class="input-group-append">
|
||||
<button type="submit"
|
||||
class="btn btn-secondary"
|
||||
[disabled]="isPosting"
|
||||
(click)="addEntry(podcast)">
|
||||
<i class="fa fa-plus"
|
||||
*ngIf="!isPosting"></i>
|
||||
<i class="fa fa-cog fa-spin"
|
||||
*ngIf="isPosting"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="alert alert-danger alert-dismissible fade show animated fadeInDown"
|
||||
role="alert"
|
||||
*ngIf="errorText">
|
||||
<strong>Error!</strong> {{errorText}}
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,20 +1,66 @@
|
||||
import { PodcastModel, PodcastEntryModel } from 'app/models/podcasts.models';
|
||||
import { Component, Input, EventEmitter, Output } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output,
|
||||
AfterViewInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PodcastService } from 'app/services/podcast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-podcast-add-url-form',
|
||||
templateUrl: './podcast-add-url-form.component.html',
|
||||
styleUrls: ['./podcast-add-url-form.component.css']
|
||||
})
|
||||
export class PodcastAddUrlFormComponent {
|
||||
export class PodcastAddUrlFormComponent implements AfterViewInit {
|
||||
@Input() podcast: PodcastModel;
|
||||
@Output() onUrlAddComplete: EventEmitter<any> = new EventEmitter();
|
||||
newEntrySourceUrl: string;
|
||||
|
||||
constructor() {}
|
||||
|
||||
errorText: string;
|
||||
isPosting: boolean = false;
|
||||
@ViewChild('input') vc: any;
|
||||
constructor(private _service: PodcastService) {}
|
||||
ngAfterViewInit() {
|
||||
this.vc.nativeElement.focus();
|
||||
}
|
||||
isValidURL(str) {
|
||||
var a = document.createElement('a');
|
||||
a.href = str;
|
||||
return a.host && a.host != window.location.host;
|
||||
}
|
||||
addEntry(podcast: PodcastModel) {
|
||||
const entry = new PodcastEntryModel(this.podcast.id, this.newEntrySourceUrl);
|
||||
const urlToCheck = this.newEntrySourceUrl;
|
||||
this.newEntrySourceUrl = 'Checking (please wait).....';
|
||||
this.errorText = '';
|
||||
if (this.isValidURL(urlToCheck)) {
|
||||
this.isPosting = true;
|
||||
this._service.checkEntry(urlToCheck).subscribe(
|
||||
r => {
|
||||
if (r) {
|
||||
const entry = new PodcastEntryModel(
|
||||
this.podcast.id,
|
||||
urlToCheck
|
||||
);
|
||||
this.onUrlAddComplete.emit(entry);
|
||||
this.isPosting = false;
|
||||
this.newEntrySourceUrl = urlToCheck;
|
||||
} else {
|
||||
this.errorText = 'This is not a supported URL';
|
||||
this.isPosting = false;
|
||||
this.newEntrySourceUrl = urlToCheck;
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.errorText = 'This is not a supported URL';
|
||||
this.isPosting = false;
|
||||
this.newEntrySourceUrl = urlToCheck;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.errorText = 'This does not look like a valid URL';
|
||||
this.newEntrySourceUrl = urlToCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,9 @@ import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Injectable()
|
||||
export class EntriesService {
|
||||
constructor(private http: Http) {}
|
||||
|
||||
constructor(private http: Http) { }
|
||||
|
||||
get() : Observable<any> {
|
||||
get(): Observable<any> {
|
||||
return this.http.get('https://api.com');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { AuthHttp } from 'angular2-jwt';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class PodcastService {
|
||||
static _replacer(key, value) {
|
||||
@@ -14,20 +13,23 @@ export class PodcastService {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
constructor(private _http: AuthHttp) { }
|
||||
constructor(private _http: AuthHttp) {}
|
||||
//#region Podcasts
|
||||
get(): Observable<PodcastModel[]> {
|
||||
return this._http.get(environment.API_HOST + '/podcast/')
|
||||
return this._http
|
||||
.get(environment.API_HOST + '/podcast/')
|
||||
.map(res => res.json());
|
||||
}
|
||||
getPodcast(slug: string): Observable<PodcastModel> {
|
||||
return this._http.get(environment.API_HOST + '/podcast/' + slug)
|
||||
return this._http
|
||||
.get(environment.API_HOST + '/podcast/' + slug)
|
||||
.map(res => res.json());
|
||||
}
|
||||
addPodcast(podcast: PodcastModel): Observable<PodcastModel> {
|
||||
console.log('PodcastService', 'addPodcast', podcast);
|
||||
const data = JSON.stringify(podcast, PodcastService._replacer);
|
||||
return this._http.post(environment.API_HOST + '/podcast', data)
|
||||
return this._http
|
||||
.post(environment.API_HOST + '/podcast', data)
|
||||
.map(res => res.json());
|
||||
}
|
||||
updatePodcast(podcast: PodcastModel) {
|
||||
@@ -39,19 +41,31 @@ export class PodcastService {
|
||||
//#endregion
|
||||
//#region Entries
|
||||
getEntries(slug: string): any {
|
||||
return this._http.get(environment.API_HOST + '/entry/all/' + slug)
|
||||
return this._http
|
||||
.get(environment.API_HOST + '/entry/all/' + slug)
|
||||
.map(res => res.json());
|
||||
}
|
||||
addEntry(entry: PodcastEntryModel) {
|
||||
return this._http.post(environment.API_HOST + '/entry', JSON.stringify(entry))
|
||||
return this._http
|
||||
.post(environment.API_HOST + '/entry', JSON.stringify(entry))
|
||||
.map(res => res.json());
|
||||
}
|
||||
updateEntry(entry: PodcastEntryModel) {
|
||||
return this._http.post(environment.API_HOST + '/entry', JSON.stringify(entry))
|
||||
return this._http
|
||||
.post(environment.API_HOST + '/entry', JSON.stringify(entry))
|
||||
.map(res => res.json());
|
||||
}
|
||||
deleteEntry(id: number) {
|
||||
return this._http.delete(environment.API_HOST + '/entry/' + id);
|
||||
}
|
||||
checkEntry(url: string): Observable<boolean> {
|
||||
return this._http
|
||||
.post(environment.API_HOST + '/entry/isvalid/', `"${url}"`)
|
||||
.map(r => (r.status == 200 ? true : false))
|
||||
.catch((error: any) => {
|
||||
return Observable.throw(new Error(error.status));
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -13,6 +9,10 @@ using PodNoms.Api.Models.ViewModels;
|
||||
using PodNoms.Api.Persistence;
|
||||
using PodNoms.Api.Services.Processor;
|
||||
using PodNoms.Api.Services.Storage;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PodNoms.Api.Controllers {
|
||||
[Route ("[controller]")]
|
||||
@@ -65,6 +65,9 @@ namespace PodNoms.Api.Controllers {
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post ([FromBody] PodcastEntryViewModel item) {
|
||||
|
||||
// first check url is valid
|
||||
|
||||
var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry> (item);
|
||||
if (entry.ProcessingStatus == ProcessingStatus.Accepted) {
|
||||
var podcast = await _podcastRepository.GetAsync (item.PodcastId);
|
||||
@@ -82,6 +85,7 @@ namespace PodNoms.Api.Controllers {
|
||||
}
|
||||
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel> (entry);
|
||||
return Ok (result);
|
||||
|
||||
}
|
||||
|
||||
[HttpDelete ("{id}")]
|
||||
@@ -90,5 +94,14 @@ namespace PodNoms.Api.Controllers {
|
||||
await _uow.CompleteAsync ();
|
||||
return Ok ();
|
||||
}
|
||||
|
||||
[HttpPost ("isvalid")]
|
||||
public async Task<IActionResult> IsValid ([FromBody] string url) {
|
||||
if (!string.IsNullOrEmpty (url)) {
|
||||
var isValid = await _processor.CheckUrlValid (url);
|
||||
if (isValid) return Ok ();
|
||||
}
|
||||
return BadRequest ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using PodNoms.Api.Models.ViewModels;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using PodNoms.Api.Models.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PodNoms.Api.Services.Downloader
|
||||
{
|
||||
public class AudioDownloader
|
||||
{
|
||||
namespace PodNoms.Api.Services.Downloader {
|
||||
public class AudioDownloader {
|
||||
private readonly string _url;
|
||||
private readonly string _downloader;
|
||||
public dynamic Properties { get; private set; }
|
||||
@@ -20,19 +19,15 @@ namespace PodNoms.Api.Services.Downloader
|
||||
|
||||
public event EventHandler<ProcessProgressEvent> DownloadProgress;
|
||||
public event EventHandler<String> PostProcessing;
|
||||
public AudioDownloader(string url, string downloader)
|
||||
{
|
||||
public AudioDownloader (string url, string downloader) {
|
||||
this._url = url;
|
||||
this._downloader = downloader;
|
||||
}
|
||||
public static string GetVersion(string downloader)
|
||||
{
|
||||
try
|
||||
{
|
||||
var proc = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
|
||||
public static string GetVersion (string downloader) {
|
||||
try {
|
||||
var proc = new Process {
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = downloader,
|
||||
Arguments = $"--version",
|
||||
UseShellExecute = false,
|
||||
@@ -40,27 +35,36 @@ namespace PodNoms.Api.Services.Downloader
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
var br = new StringBuilder();
|
||||
proc.Start();
|
||||
while (!proc.StandardOutput.EndOfStream)
|
||||
{
|
||||
br.Append(proc.StandardOutput.ReadLine());
|
||||
var br = new StringBuilder ();
|
||||
proc.Start ();
|
||||
while (!proc.StandardOutput.EndOfStream) {
|
||||
br.Append (proc.StandardOutput.ReadLine ());
|
||||
}
|
||||
return br.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return br.ToString ();
|
||||
} catch (Exception ex) {
|
||||
return $"{{\"Error\": \"{ex.Message}\"}}";
|
||||
}
|
||||
}
|
||||
public string DownloadAudio(string uid)
|
||||
{
|
||||
var outputFile = Path.Combine(Path.GetTempPath(), $"{uid}.mp3");
|
||||
var templateFile = Path.Combine(Path.GetTempPath(), $"{uid}.%(ext)s");
|
||||
var proc = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
public async Task<bool> CheckUrlValid () {
|
||||
var proc = new Process {
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = this._downloader,
|
||||
Arguments = $"\"{this._url}\" -j",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
proc.Start ();
|
||||
await (Task.Run (() => proc.WaitForExit ()));
|
||||
return (proc.ExitCode == 0);
|
||||
}
|
||||
|
||||
public string DownloadAudio (string uid) {
|
||||
var outputFile = Path.Combine (Path.GetTempPath (), $"{uid}.mp3");
|
||||
var templateFile = Path.Combine (Path.GetTempPath (), $"{uid}.%(ext)s");
|
||||
var proc = new Process {
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = this._downloader,
|
||||
Arguments = $"-o \"{templateFile}\" --audio-format mp3 -x \"{this._url}\"",
|
||||
UseShellExecute = false,
|
||||
@@ -69,41 +73,31 @@ namespace PodNoms.Api.Services.Downloader
|
||||
}
|
||||
};
|
||||
|
||||
StringBuilder br = new StringBuilder();
|
||||
proc.Start();
|
||||
while (!proc.StandardOutput.EndOfStream)
|
||||
{
|
||||
string output = proc.StandardOutput.ReadLine();
|
||||
if (output.Contains("%"))
|
||||
{
|
||||
var progress = _parseProgress(output);
|
||||
if (DownloadProgress != null)
|
||||
{
|
||||
DownloadProgress(this, progress);
|
||||
StringBuilder br = new StringBuilder ();
|
||||
proc.Start ();
|
||||
while (!proc.StandardOutput.EndOfStream) {
|
||||
string output = proc.StandardOutput.ReadLine ();
|
||||
if (output.Contains ("%")) {
|
||||
var progress = _parseProgress (output);
|
||||
if (DownloadProgress != null) {
|
||||
DownloadProgress (this, progress);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PostProcessing != null)
|
||||
{
|
||||
PostProcessing(this, output);
|
||||
} else {
|
||||
if (PostProcessing != null) {
|
||||
PostProcessing (this, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(outputFile))
|
||||
{
|
||||
if (File.Exists (outputFile)) {
|
||||
return outputFile;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public void DownloadInfo()
|
||||
{
|
||||
var proc = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
public void DownloadInfo () {
|
||||
var proc = new Process {
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = "youtube-dl",
|
||||
Arguments = $"-j {this._url}",
|
||||
UseShellExecute = false,
|
||||
@@ -112,38 +106,34 @@ namespace PodNoms.Api.Services.Downloader
|
||||
}
|
||||
};
|
||||
|
||||
StringBuilder br = new StringBuilder();
|
||||
proc.Start();
|
||||
while (!proc.StandardOutput.EndOfStream)
|
||||
{
|
||||
br.Append(proc.StandardOutput.ReadLine());
|
||||
StringBuilder br = new StringBuilder ();
|
||||
proc.Start ();
|
||||
while (!proc.StandardOutput.EndOfStream) {
|
||||
br.Append (proc.StandardOutput.ReadLine ());
|
||||
}
|
||||
Properties = JsonConvert.DeserializeObject<ExpandoObject>(br.ToString());
|
||||
Properties = JsonConvert.DeserializeObject<ExpandoObject> (br.ToString ());
|
||||
}
|
||||
|
||||
private ProcessProgressEvent _parseProgress(string output)
|
||||
{
|
||||
private ProcessProgressEvent _parseProgress (string output) {
|
||||
|
||||
var result = new ProcessProgressEvent();
|
||||
var result = new ProcessProgressEvent ();
|
||||
|
||||
int progressIndex = output.LastIndexOf(' ', output.IndexOf('%')) + 1;
|
||||
string progressString = output.Substring(progressIndex, output.IndexOf('%') - progressIndex);
|
||||
result.Percentage = (int)Math.Round(double.Parse(progressString));
|
||||
int progressIndex = output.LastIndexOf (' ', output.IndexOf ('%')) + 1;
|
||||
string progressString = output.Substring (progressIndex, output.IndexOf ('%') - progressIndex);
|
||||
result.Percentage = (int) Math.Round (double.Parse (progressString));
|
||||
|
||||
int sizeIndex = output.LastIndexOf(' ', output.IndexOf(DOWNLOADSIZESTRING)) + 1;
|
||||
string sizeString = output.Substring(sizeIndex, output.IndexOf(DOWNLOADSIZESTRING) - sizeIndex + 2);
|
||||
int sizeIndex = output.LastIndexOf (' ', output.IndexOf (DOWNLOADSIZESTRING)) + 1;
|
||||
string sizeString = output.Substring (sizeIndex, output.IndexOf (DOWNLOADSIZESTRING) - sizeIndex + 2);
|
||||
result.TotalSize = sizeString;
|
||||
|
||||
if (output.Contains(DOWNLOADRATESTRING))
|
||||
{
|
||||
int rateIndex = output.LastIndexOf(' ', output.LastIndexOf(DOWNLOADRATESTRING)) + 1;
|
||||
string rateString = output.Substring(rateIndex, output.LastIndexOf(DOWNLOADRATESTRING) - rateIndex + 4);
|
||||
if (output.Contains (DOWNLOADRATESTRING)) {
|
||||
int rateIndex = output.LastIndexOf (' ', output.LastIndexOf (DOWNLOADRATESTRING)) + 1;
|
||||
string rateString = output.Substring (rateIndex, output.LastIndexOf (DOWNLOADRATESTRING) - rateIndex + 4);
|
||||
result.CurrentSpeed = rateString;
|
||||
}
|
||||
|
||||
if (output.Contains(ETASTRING))
|
||||
{
|
||||
result.ETA = output.Substring(output.LastIndexOf(' ') + 1);
|
||||
if (output.Contains (ETASTRING)) {
|
||||
result.ETA = output.Substring (output.LastIndexOf (' ') + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PodNoms.Api.Services.Processor
|
||||
{
|
||||
public interface IUrlProcessService
|
||||
{
|
||||
Task<bool> GetInformation(int entryId);
|
||||
Task<bool> DownloadAudio(int entryId);
|
||||
namespace PodNoms.Api.Services.Processor {
|
||||
public interface IUrlProcessService {
|
||||
Task<bool> CheckUrlValid (string url);
|
||||
Task<bool> GetInformation (int entryId);
|
||||
Task<bool> DownloadAudio (int entryId);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -16,6 +12,10 @@ using PodNoms.Api.Services.Downloader;
|
||||
using PodNoms.Api.Services.Realtime;
|
||||
using PodNoms.Api.Services.Storage;
|
||||
using PusherServer;
|
||||
using System.ComponentModel;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PodNoms.Api.Services.Processor {
|
||||
internal class UrlProcessService : ProcessService, IUrlProcessService {
|
||||
@@ -24,7 +24,6 @@ namespace PodNoms.Api.Services.Processor {
|
||||
|
||||
public ApplicationsSettings _applicationsSettings { get; }
|
||||
|
||||
|
||||
public UrlProcessService (IEntryRepository repository, IUnitOfWork unitOfWork,
|
||||
IFileUploader fileUploader, IOptions<ApplicationsSettings> applicationsSettings,
|
||||
ILoggerFactory logger, IMapper mapper, IRealTimeUpdater pusher) : base (logger, mapper, pusher) {
|
||||
@@ -39,6 +38,12 @@ namespace PodNoms.Api.Services.Processor {
|
||||
uid,
|
||||
e);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckUrlValid (string url) {
|
||||
return await new AudioDownloader (url, _applicationsSettings.Downloader)
|
||||
.CheckUrlValid ();
|
||||
}
|
||||
|
||||
public async Task<bool> GetInformation (int entryId) {
|
||||
var entry = await _repository.GetAsync (entryId);
|
||||
if (entry == null || string.IsNullOrEmpty (entry.SourceUrl)) {
|
||||
|
||||
Reference in New Issue
Block a user