Added some error handling for add entry

This commit is contained in:
Fergal Moran
2018-02-12 23:56:45 +00:00
parent 5a678b7a4d
commit f0116d99f2
10 changed files with 7512 additions and 27846 deletions

View File

@@ -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">&times;</span>
</button>
</div>

View File

@@ -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;
}
}
}

View File

@@ -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');
}
}

View File

@@ -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

View File

@@ -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 ();
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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)) {