Basic extension working

This commit is contained in:
Fergal Moran
2017-12-11 01:33:05 +00:00
parent 1b9e642eb0
commit 173f17bccd
38 changed files with 13365 additions and 54 deletions

3
extension/.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

3
extension/.bowerrc Normal file
View File

@@ -0,0 +1,3 @@
{
"directory": "app/bower_components"
}

24
extension/.editorconfig Normal file
View File

@@ -0,0 +1,24 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 4
[*.json]
indent_size = 4
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

1
extension/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

10
extension/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
node_modules
temp
.tmp
dist
.sass-cache
app/bower_components
test/bower_components
package
app/scripts

6
extension/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"workbench.colorTheme": "Atom One Light",
"files.exclude": {
"app/scripts": true
}
}

View File

@@ -0,0 +1,59 @@
<!doctype html>
<html lang="en">
<head>
<title>Pod::Noms Test</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
rel="stylesheet">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
body {
padding-top: 5rem;
}
.starter-template {
padding: 3rem 1.5rem;
text-align: center;
}
</style>
</head>
<body>
<main role="main"
class="container">
<div class="starter-template">
<h1>Pod::Noms test page</h1>
<div class="row">
<audio controls=""
preload="none">
<source type="audio/mpeg"
src="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
<a target="_blank"
href="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
Fish Go Deep Radio 2017-24-320</a>
</audio>
</div>
<div class="row">
<a target="_blank"
href="https://podnomscdn.blob.core.windows.net/audio/d34c8cd4-22cd-4a01-a140-6a9cf470051b.mp3">
Here is some audio
</a>
</div>
<div class="row">
<a target="_blank"
href="https://podnomscdn.blob.core.windows.net/audio/183ae2b3-2bec-4726-aa29-925f92b23ba3.mp3">
Here is some short audio
</a>
</div>
</div>
</main>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.bundle.min.js"></script>
</body>
</html>

6
extension/.yo-rc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"generator-mocha": {
"ui": "tdd",
"rjs": false
}
}

View File

@@ -0,0 +1,10 @@
{
"appName": {
"message": "podnoms",
"description": "The name of the application"
},
"appDescription": {
"message": "Robot Powered Podcasts",
"description": "The description of the application"
}
}

4
extension/app/env.js Normal file
View File

@@ -0,0 +1,4 @@
window.env = {
AUTH0_DOMAIN: 'podnoms.eu.auth0.com',
AUTH0_CLIENT_ID: 'Fx6Z3kZoheEpXhZO97ioAg1asbHDdCtr'
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
extension/app/images/icon-48.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
extension/app/images/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -0,0 +1,38 @@
{
"name": "__MSG_appName__",
"version": "0.0.1",
"manifest_version": 2,
"description": "__MSG_appDescription__",
"icons": {
"16": "images/icon-16.png",
"128": "images/icon-128.png"
},
"default_locale": "en",
"permissions": ["tabs", "identity", "notifications", "storage"],
"background": {
"scripts": [
"./env.js",
"scripts/chromereload.js",
"scripts/auth.js",
"node_modules/auth0-chrome/dist/auth0chrome.min.js",
"scripts/background.js"
],
"persistent": false
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["scripts/lib/jquery.js", "scripts/contentscript.js"],
"run_at": "document_end",
"all_frames": false
}
],
"browser_action": {
"default_icon": {
"19": "images/icon-19.png",
"38": "images/icon-38.png"
},
"default_title": "podnoms",
"default_popup": "popup.html"
}
}

View File

@@ -0,0 +1,24 @@
<!doctype html>
<html>
<head>
<!-- build:css styles/options-vendor.css -->
<!-- bower:css -->
<!-- endbower -->
<!-- endbuild -->
<!-- build:css styles/main.css -->
<link href="styles/main.css" rel="stylesheet">
<!-- endbuild -->
</head>
<body>
<h1>'Allo, 'Allo!</h1>
<!-- build:js scripts/options-vendor.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->
<!-- build:js scripts/options.js -->
<script src="scripts/options.js"></script>
<!-- endbuild -->
</body>
</html>

119
extension/app/popup.html Normal file
View File

@@ -0,0 +1,119 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet"
href="../styles/bootstrap.css">
<style>
.hidden {
display: None;
}
.loading {
transform: translate(-50%, -50%);
position: absolute;
top: 50%;
left: 50%;
}
.loading-bar {
display: inline-block;
width: 2px;
height: 16px;
border-radius: 0px;
animation: loading 1s ease-in-out infinite;
}
.loading-bar:nth-child(1) {
background-color: #eb5424;
animation-delay: 0;
}
.loading-bar:nth-child(2) {
background-color: #44c7f4;
animation-delay: 0.09s;
}
.loading-bar:nth-child(3) {
background-color: #16214d;
animation-delay: .18s;
}
.loading-bar:nth-child(4) {
background-color: #eaca2c;
animation-delay: .27s;
}
@keyframes loading {
0% {
transform: scale(1);
}
20% {
transform: scale(1, 2.2);
}
40% {
transform: scale(1);
}
}
</style>
</head>
<body style="width: 320px;">
<nav class="navbar navbar-default"
role="navigation">
<div class="navbar-header">
<a class="navbar-brand"
href="#">Pod::Noms</a>
<button class="btn btn-success pull-right retry-button"><i class="fa fa-refresh"></i></button>
</div>
</nav>
<section class="container">
<div class="error alert alert-danger">
There was an error communicating with the PodNoms API
</div>
<div class="loggedin">
Choose podcast:
<select class="combobox input-large form-control">
</select>
<hr />
<div id="no-audio">
No audio found on page
</div>
<table class="table table-striped"
id="results-table">
<thead>
<td>Title</td>
<td>Size</td>
<td>Action</td>
</thead>
<tbody>
</tbody>
</table>
<hr />
</div>
<div class="loading">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
<div class="logout">
<div class="text-center">
<button class="btn btn-danger logout">Log Out</button>
</div>
</div>
<div class="login">
<div class="text-center">
<button class="btn btn-success login-button">Log In</button>
</div>
</div>
</section>
<script src="../../env.js"></script>
<script src="./scripts/lib/jquery.js"></script>
<script src="./scripts/lib/fontawesome.js"></script>
<script src="./scripts/popup.js"></script>
<script src="../../node_modules/jwt-decode/build/jwt-decode.js"></script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
console.log('auth.js');
chrome.runtime.onMessage.addListener(function(event, sender, sendResponse) {
if (event.type === 'authenticate') {
let options = {
scope: 'openid profile offline_access',
device: 'chrome-extension'
};
new Auth0Chrome(env.AUTH0_DOMAIN, env.AUTH0_CLIENT_ID)
.authenticate(options)
.then(function(authResult) {
localStorage.authResult = JSON.stringify(authResult);
chrome.notifications.create({
type: 'basic',
iconUrl: 'images/icon-128.png',
title: 'Login Successful',
message: 'You can use the app now'
});
})
.catch(function(err) {
chrome.notifications.create({
type: 'basic',
title: 'Login Failed',
message: err.message,
iconUrl: 'images/icon-128.png'
});
});
}
});

View File

@@ -0,0 +1,25 @@
'use strict';
console.log('Background');
chrome.runtime.onMessage.addListener(function(event, sender, sendResponse) {
if (event.type === 'updatebadge') {
chrome.tabs.get(sender.tab.id, function(tab) {
// chrome.pageAction.show(tab.id);
if (chrome.runtime.lastError) {
return;
}
if (tab.index >= 0) {
chrome.browserAction.setBadgeBackgroundColor({ tabId: tab.id, color: [255, 0, 0, 255] });
chrome.browserAction.setBadgeText({ tabId: tab.id, text: event.data });
} else {
var tabId = sender.tab.id,
text = event.data;
chrome.webNavigation.onCommitted.addListener(function update(details) {
if (details.tabId == tabId) {
chrome.browserAction.setBadgeText({ tabId: tab.id, text: event.data });
chrome.webNavigation.onCommitted.removeListener(update);
}
});
}
});
}
});

View File

@@ -0,0 +1,35 @@
'use strict';
// Reload client for Chrome Apps & Extensions.
// The reload client has a compatibility with livereload.
// WARNING: only supports reload command.
const LIVERELOAD_HOST = 'localhost:';
const LIVERELOAD_PORT = 35729;
const connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload');
var lastReload = false;
chrome.runtime.onInstalled.addListener(function(details) {
lastReload = Date.now();
});
connection.onerror = error => {
console.log('reload connection got error:', error);
};
connection.onmessage = e => {
if (e.data) {
const data = JSON.parse(e.data);
if (data && data.command === 'reload') {
var currentTime = Date.now();
if (lastReload && currentTime - lastReload > 60000) {
// don't reload more than once a minute
chrome.runtime.reload();
chrome.developerPrivate.reload(chrome.runtime.id,
{failQuietly: true});
}
}
}
};

View File

@@ -0,0 +1,17 @@
'use strict';
console.log('Content script', chrome.runtime.id);
function onRequest(request, sender, sendResponse) {
if (request.action === 'process-page') {
const array = $.map(links, (n, i) => {
return { href: n.href, text: n.text };
});
sendResponse(array);
}
}
chrome.runtime.onMessage.addListener(onRequest);
const links = $(document).find('a[href$="mp3"]');
if (links && links.length !== 0) {
chrome.runtime.sendMessage({ type: 'updatebadge', data: '' + links.length });
}

View File

@@ -0,0 +1,3 @@
'use strict';
console.log('\'Allo \'Allo! Option');

View File

@@ -0,0 +1,149 @@
function isLoggedIn(token) {
return jwt_decode(token).exp > Date.now() / 1000;
}
function logout() {
localStorage.clear();
window.close();
}
function humanFileSize(bytes, si) {
var thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1) + ' ' + units[u];
}
function postPodcast(event) {
const authResult = JSON.parse(localStorage.authResult || '{}');
const title = $(event.currentTarget).data('title');
const url = $(event.currentTarget).data('url');
const podcastId = $('.combobox').val();
const data = {
podcastId: podcastId,
title: title,
sourceUrl: url,
processed: false
};
fetch(`https://www.podnoms.com/api/entry/`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authResult.id_token}`
},
body: JSON.stringify(data)
}).then(r => {
if (r.status === 200) {
chrome.notifications.create({
type: 'basic',
iconUrl: 'images/icon-128.png',
title: 'Podcast Added Succesfully',
message: 'It should show up in your podcaster soon.'
});
} else {
chrome.notifications.create({
type: 'basic',
iconUrl: 'images/icon-128.png',
title: 'Error adding podcast',
message: 'Please try adding manually on the site.'
});
}
});
}
function renderLinks(links) {
// const links = JSON.parse(data);
if (links && links.length !== 0) {
$('#no-audio').hide();
$('#results-table').show();
$('table tbody')
.children()
.remove();
$(links).each((i, d) => {
var xhr = $.ajax({
type: 'HEAD',
url: d.href,
success: function(msg) {
const title = (d.text || d.href).trim();
var markup =
'<tr><td>' +
title +
'</td><td>' +
humanFileSize(xhr.getResponseHeader('Content-Length'), true) +
'</td><td><button class="btn btn-small add-podcast" data-url="' +
d.href +
'" data-title="' +
title +
'"><i class="fa fa-plus"></i></button></td></tr>';
$('table tbody').append(markup);
$('.add-podcast').click(postPodcast);
}
});
});
}
}
function startRenderLinks() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: 'process-page' }, renderLinks);
});
}
function renderProfileView(authResult) {
$('.login').hide();
$('.loading').show();
$('.logout-button').show();
$('.logout-button').click(logout);
$('.retry-button').click(startRenderLinks);
fetch(`https://www.podnoms.com/api/podcast/`, {
headers: {
Authorization: `Bearer ${authResult.id_token}`
}
})
.then(resp => resp.json())
.then(podcasts => {
podcasts.forEach(podcast => {
$('.combobox').append(new Option(podcast['title'], podcast['id']));
});
console.log(podcasts);
startRenderLinks();
$('.loading').hide();
$('.loggedin').show();
})
.catch(err => {
$('.error').show();
console.error(err);
});
}
function renderDefaultView() {
$('.login-button').click(() => {
$('.login').hide();
$('.loading').show();
chrome.runtime.sendMessage({
type: 'authenticate'
});
});
}
function main() {
$('.loading').hide();
$('.loggedin').hide();
$('.error').hide();
$('.logout').hide();
const authResult = JSON.parse(localStorage.authResult || '{}');
const token = authResult.id_token;
if (token && isLoggedIn(token)) {
renderProfileView(authResult);
} else {
renderDefaultView();
}
}
document.addEventListener('DOMContentLoaded', main);

7
extension/app/styles/bootstrap.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
body {
padding: 20px;
}

7
extension/bower.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "podnoms",
"private": true,
"version": "0.0.0",
"dependencies": {},
"devDependencies": {}
}

140
extension/gulpfile.babel.js Normal file
View File

@@ -0,0 +1,140 @@
// generated on 2017-12-10 using generator-chrome-extension 0.7.0
import gulp from 'gulp';
import gulpLoadPlugins from 'gulp-load-plugins';
import del from 'del';
import runSequence from 'run-sequence';
import {stream as wiredep} from 'wiredep';
const $ = gulpLoadPlugins();
gulp.task('extras', () => {
return gulp.src([
'app/*.*',
'app/_locales/**',
'!app/scripts.babel',
'!app/*.json',
'!app/*.html',
], {
base: 'app',
dot: true
}).pipe(gulp.dest('dist'));
});
function lint(files, options) {
return () => {
return gulp.src(files)
.pipe($.eslint(options))
.pipe($.eslint.format());
};
}
gulp.task('lint', lint('app/scripts.babel/**/*.js', {
env: {
es6: true
}
}));
gulp.task('images', () => {
return gulp.src('app/images/**/*')
.pipe($.if($.if.isFile, $.cache($.imagemin({
progressive: true,
interlaced: true,
// don't remove IDs from SVGs, they are often used
// as hooks for embedding and styling
svgoPlugins: [{cleanupIDs: false}]
}))
.on('error', function (err) {
console.log(err);
this.end();
})))
.pipe(gulp.dest('dist/images'));
});
gulp.task('html', () => {
return gulp.src('app/*.html')
.pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
.pipe($.sourcemaps.init())
.pipe($.if('*.js', $.uglify()))
.pipe($.if('*.css', $.cleanCss({compatibility: '*'})))
.pipe($.sourcemaps.write())
.pipe($.if('*.html', $.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
removeComments: true
})))
.pipe(gulp.dest('dist'));
});
gulp.task('chromeManifest', () => {
return gulp.src('app/manifest.json')
.pipe($.chromeManifest({
buildnumber: true,
background: {
target: 'scripts/background.js',
exclude: [
'scripts/chromereload.js'
]
}
}))
.pipe($.if('*.css', $.cleanCss({compatibility: '*'})))
.pipe($.if('*.js', $.sourcemaps.init()))
.pipe($.if('*.js', $.uglify()))
.pipe($.if('*.js', $.sourcemaps.write('.')))
.pipe(gulp.dest('dist'));
});
gulp.task('babel', () => {
return gulp.src('app/scripts.babel/**/*.js')
.pipe($.babel({
presets: ['es2015']
}))
.pipe(gulp.dest('app/scripts'));
});
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
gulp.task('watch', ['lint', 'babel'], () => {
$.livereload.listen();
gulp.watch([
'app/*.html',
'app/scripts/**/*.js',
'app/images/**/*',
'app/styles/**/*',
'app/_locales/**/*.json'
]).on('change', $.livereload.reload);
gulp.watch('app/scripts.babel/**/*.js', ['lint', 'babel']);
gulp.watch('bower.json', ['wiredep']);
});
gulp.task('size', () => {
return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});
gulp.task('wiredep', () => {
gulp.src('app/*.html')
.pipe(wiredep({
ignorePath: /^(\.\.\/)*\.\./
}))
.pipe(gulp.dest('app'));
});
gulp.task('package', function () {
var manifest = require('./dist/manifest.json');
return gulp.src('dist/**')
.pipe($.zip('podnoms-' + manifest.version + '.zip'))
.pipe(gulp.dest('package'));
});
gulp.task('build', (cb) => {
runSequence(
'lint', 'babel', 'chromeManifest',
['html', 'images', 'extras'],
'size', cb);
});
gulp.task('default', ['clean'], cb => {
runSequence('build', cb);
});

8062
extension/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

51
extension/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "podnoms",
"private": true,
"engines": {
"node": ">=0.8.0"
},
"devDependencies": {
"babel-core": "^6.7.2",
"babel-preset-es2015": "^6.6.0",
"del": "^2.2.0",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-cache": "^0.4.3",
"gulp-chrome-manifest": "0.0.13",
"gulp-clean-css": "^2.0.3",
"gulp-eslint": "^2.0.0",
"gulp-htmlmin": "^2.0.0",
"gulp-if": "^2.0.0",
"gulp-imagemin": "^2.4.0",
"gulp-livereload": "^3.8.1",
"gulp-load-plugins": "^1.2.0",
"gulp-size": "^2.1.0",
"gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.5.3",
"gulp-useref": "^3.0.8",
"gulp-zip": "^3.2.0",
"main-bower-files": "^2.11.1",
"run-sequence": "^1.1.5",
"wiredep": "^4.0.0"
},
"eslintConfig": {
"env": {
"node": true,
"browser": true
},
"globals": {
"chrome": true
},
"rules": {
"eol-last": 0,
"quotes": [
2,
"single"
]
}
},
"dependencies": {
"auth0-chrome": "^0.1.2",
"jwt-decode": "^2.2.0"
}
}

29
extension/test/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha Spec Runner</title>
<link rel="stylesheet" href="../bower_components/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="../bower_components/mocha/mocha.js"></script>
<script>mocha.setup('tdd');</script>
<script src="../bower_components/chai/chai.js"></script>
<script>
var assert = chai.assert;
var expect = chai.expect;
var should = chai.should();
</script>
<!-- bower:js -->
<!-- endbower -->
<!-- include source files here... -->
<!-- include spec files here... -->
<script src="spec/test.js"></script>
<script>
if (navigator.userAgent.indexOf('PhantomJS') === -1) {
mocha.run();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
(function () {
'use strict';
describe('Give it some context', function () {
describe('maybe a bit more context here', function () {
it('should run here few assertions', function () {
});
});
});
})();

4442
extension/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,11 +14,9 @@ using PodNoms.Api.Persistence;
using PodNoms.Api.Services.Processor; using PodNoms.Api.Services.Processor;
using PodNoms.Api.Services.Storage; using PodNoms.Api.Services.Storage;
namespace PodNoms.Api.Controllers namespace PodNoms.Api.Controllers {
{ [Route ("[controller]")]
[Route("[controller]")] public class EntryController : Controller {
public class EntryController : Controller
{
private readonly IPodcastRepository _podcastRepository; private readonly IPodcastRepository _podcastRepository;
private readonly IEntryRepository _repository; private readonly IEntryRepository _repository;
private readonly IUnitOfWork _uow; private readonly IUnitOfWork _uow;
@@ -26,75 +24,69 @@ namespace PodNoms.Api.Controllers
private readonly IUrlProcessService _processor; private readonly IUrlProcessService _processor;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly AudioFileStorageSettings _audioFileStorageSettings; private readonly AudioFileStorageSettings _audioFileStorageSettings;
private readonly StorageSettings _storageSettings;
public EntryController(IEntryRepository repository, public EntryController (IEntryRepository repository,
IPodcastRepository podcastRepository, IPodcastRepository podcastRepository,
IUnitOfWork unitOfWork, IMapper mapper, IUnitOfWork unitOfWork, IMapper mapper, IOptions<StorageSettings> storageSettings,
IOptions<AudioFileStorageSettings> audioFileStorageSettings, IOptions<AudioFileStorageSettings> audioFileStorageSettings,
IUrlProcessService processor, ILoggerFactory logger) IUrlProcessService processor, ILoggerFactory logger) {
{ this._logger = logger.CreateLogger<EntryController> ();
this._logger = logger.CreateLogger<EntryController>();
this._podcastRepository = podcastRepository; this._podcastRepository = podcastRepository;
this._repository = repository; this._repository = repository;
this._storageSettings = storageSettings.Value;
this._uow = unitOfWork; this._uow = unitOfWork;
this._audioFileStorageSettings = audioFileStorageSettings.Value; this._audioFileStorageSettings = audioFileStorageSettings.Value;
this._mapper = mapper; this._mapper = mapper;
this._processor = processor; this._processor = processor;
} }
private void _processEntry(PodcastEntry entry) private void _processEntry (PodcastEntry entry) {
{ try {
try var infoJobId = BackgroundJob.Enqueue<IUrlProcessService> (
{ service => service.GetInformation (entry.Id));
var infoJobId = BackgroundJob.Enqueue<IUrlProcessService>( var extract = BackgroundJob.ContinueWith<IUrlProcessService> (
service => service.GetInformation(entry.Id)); infoJobId, service => service.DownloadAudio (entry.Id));
var extract = BackgroundJob.ContinueWith<IUrlProcessService>( var upload = BackgroundJob.ContinueWith<IAudioUploadProcessService> (
infoJobId, service => service.DownloadAudio(entry.Id)); extract, service => service.UploadAudio (entry.Id, entry.AudioUrl));
var upload = BackgroundJob.ContinueWith<IAudioUploadProcessService>( } catch (InvalidOperationException ex) {
extract, service => service.UploadAudio(entry.Id, entry.AudioUrl)); _logger.LogError ($"Failed submitting job to processor\n{ex.Message}");
entry.ProcessingStatus = ProcessingStatus.Failed;
}
} }
catch (InvalidOperationException ex) [HttpGet ("all/{podcastSlug}")]
{ public async Task<IActionResult> GetAllForSlug (string podcastSlug) {
_logger.LogError($"Failed submitting job to processor\n{ex.Message}"); var entries = await _repository.GetAllAsync (podcastSlug);
entry.ProcessingStatus = ProcessingStatus.Failed; var results = _mapper.Map<List<PodcastEntry>, List<PodcastEntryViewModel>> (entries.ToList ());
}
}
[HttpGet("all/{podcastSlug}")]
public async Task<IActionResult> GetAllForSlug(string podcastSlug)
{
var entries = await _repository.GetAllAsync(podcastSlug);
var results = _mapper.Map<List<PodcastEntry>, List<PodcastEntryViewModel>>(entries.ToList());
return Ok(results); return Ok (results);
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> Post([FromBody] PodcastEntryViewModel item) public async Task<IActionResult> Post ([FromBody] PodcastEntryViewModel item) {
{ var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry> (item);
var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry>(item); if (entry.ProcessingStatus == ProcessingStatus.Accepted) {
if (entry.ProcessingStatus == ProcessingStatus.Accepted) var podcast = await _podcastRepository.GetAsync (item.PodcastId);
{ entry.ImageUrl = $"{_storageSettings.CdnUrl}static/images/default-entry.png";
var podcast = await _podcastRepository.GetAsync(item.PodcastId);
entry.Podcast = podcast; entry.Podcast = podcast;
entry.Processed = false; entry.Processed = false;
} }
await _repository.AddOrUpdateAsync(entry); await _repository.AddOrUpdateAsync (entry);
await _uow.CompleteAsync(); await _uow.CompleteAsync ();
if (entry.ProcessingStatus.Equals(ProcessingStatus.Accepted) && entry.Id != 0) if (entry.ProcessingStatus.Equals (ProcessingStatus.Accepted) && entry.Id != 0) {
{ _processEntry (entry);
_processEntry(entry);
} }
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel>(entry); var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel> (entry);
return Ok(result); return Ok (result);
} }
[HttpDelete("{id}")] [HttpDelete ("{id}")]
public async Task<IActionResult> Delete(int id) public async Task<IActionResult> Delete (int id) {
{ await this._repository.DeleteAsync (id);
await this._repository.DeleteAsync(id); await _uow.CompleteAsync ();
await _uow.CompleteAsync(); return Ok ();
return Ok();
} }
} }
} }

View File

@@ -24,6 +24,7 @@ namespace PodNoms.Api.Services.Processor {
public ApplicationsSettings _applicationsSettings { get; } public ApplicationsSettings _applicationsSettings { get; }
public UrlProcessService (IEntryRepository repository, IUnitOfWork unitOfWork, public UrlProcessService (IEntryRepository repository, IUnitOfWork unitOfWork,
IFileUploader fileUploader, IOptions<ApplicationsSettings> applicationsSettings, IFileUploader fileUploader, IOptions<ApplicationsSettings> applicationsSettings,
ILoggerFactory logger, IMapper mapper, IRealTimeUpdater pusher) : base (logger, mapper, pusher) { ILoggerFactory logger, IMapper mapper, IRealTimeUpdater pusher) : base (logger, mapper, pusher) {
@@ -44,7 +45,9 @@ namespace PodNoms.Api.Services.Processor {
_logger.LogError ("Unable to process item"); _logger.LogError ("Unable to process item");
return false; return false;
} }
if (entry.SourceUrl.EndsWith (".mp3") || entry.SourceUrl.EndsWith (".wav") || entry.SourceUrl.EndsWith (".aif")) {
return true;
}
var downloader = new AudioDownloader (entry.SourceUrl, _applicationsSettings.Downloader); var downloader = new AudioDownloader (entry.SourceUrl, _applicationsSettings.Downloader);
downloader.DownloadInfo (); downloader.DownloadInfo ();
if (downloader.Properties != null) { if (downloader.Properties != null) {