mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-21 01:13:20 +03:00
Merge pull request #44 from Tzahi12345/auto-update-youtubedl-material
Ability to update YoutubeDL-Material
This commit is contained in:
321
backend/app.js
321
backend/app.js
@@ -1,6 +1,6 @@
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
const { uuid } = require('uuidv4');
|
const { uuid } = require('uuidv4');
|
||||||
var fs = require('fs');
|
var fs = require('fs-extra');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var youtubedl = require('youtube-dl');
|
var youtubedl = require('youtube-dl');
|
||||||
var compression = require('compression');
|
var compression = require('compression');
|
||||||
@@ -8,8 +8,10 @@ var https = require('https');
|
|||||||
var express = require("express");
|
var express = require("express");
|
||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
|
var unzipper = require('unzipper');
|
||||||
var mergeFiles = require('merge-files');
|
var mergeFiles = require('merge-files');
|
||||||
const low = require('lowdb')
|
const low = require('lowdb')
|
||||||
|
var ProgressBar = require('progress');
|
||||||
var md5 = require('md5');
|
var md5 = require('md5');
|
||||||
const NodeID3 = require('node-id3')
|
const NodeID3 = require('node-id3')
|
||||||
const downloader = require('youtube-dl/lib/downloader')
|
const downloader = require('youtube-dl/lib/downloader')
|
||||||
@@ -19,6 +21,8 @@ const shortid = require('shortid')
|
|||||||
const url_api = require('url');
|
const url_api = require('url');
|
||||||
var config_api = require('./config.js');
|
var config_api = require('./config.js');
|
||||||
var subscriptions_api = require('./subscriptions')
|
var subscriptions_api = require('./subscriptions')
|
||||||
|
const CONSTS = require('./consts')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
|
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
@@ -26,6 +30,8 @@ const FileSync = require('lowdb/adapters/FileSync')
|
|||||||
const adapter = new FileSync('./appdata/db.json');
|
const adapter = new FileSync('./appdata/db.json');
|
||||||
const db = low(adapter)
|
const db = low(adapter)
|
||||||
|
|
||||||
|
// var GithubContent = require('github-content');
|
||||||
|
|
||||||
// Set some defaults
|
// Set some defaults
|
||||||
db.defaults(
|
db.defaults(
|
||||||
{
|
{
|
||||||
@@ -56,12 +62,23 @@ var archivePath = path.join(__dirname, 'appdata', 'archives');
|
|||||||
// other needed values
|
// other needed values
|
||||||
var options = null; // encryption options
|
var options = null; // encryption options
|
||||||
var url_domain = null;
|
var url_domain = null;
|
||||||
|
var updaterStatus = null;
|
||||||
|
|
||||||
// check if debug mode
|
// check if debug mode
|
||||||
let debugMode = process.env.YTDL_MODE === 'debug';
|
let debugMode = process.env.YTDL_MODE === 'debug';
|
||||||
|
|
||||||
if (debugMode) console.log('YTDL-Material in debug mode!');
|
if (debugMode) console.log('YTDL-Material in debug mode!');
|
||||||
|
|
||||||
|
// check if just updated
|
||||||
|
const just_restarted = fs.existsSync('restart.json');
|
||||||
|
if (just_restarted) {
|
||||||
|
updaterStatus = {
|
||||||
|
updating: false,
|
||||||
|
details: 'Update complete! You are now on ' + CONSTS['CURRENT_VERSION']
|
||||||
|
}
|
||||||
|
fs.unlinkSync('restart.json');
|
||||||
|
}
|
||||||
|
|
||||||
// updates & starts youtubedl
|
// updates & starts youtubedl
|
||||||
startYoutubeDL();
|
startYoutubeDL();
|
||||||
|
|
||||||
@@ -122,15 +139,248 @@ async function startServer() {
|
|||||||
if (usingEncryption)
|
if (usingEncryption)
|
||||||
{
|
{
|
||||||
https.createServer(options, app).listen(backendPort, function() {
|
https.createServer(options, app).listen(backendPort, function() {
|
||||||
console.log('HTTPS: Started on PORT ' + backendPort);
|
console.log(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on port ${backendPort} - using SSL`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
app.listen(backendPort,function(){
|
app.listen(backendPort,function(){
|
||||||
console.log("HTTP: Started on PORT " + backendPort);
|
console.log(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restartServer() {
|
||||||
|
const restartProcess = () => {
|
||||||
|
spawn('node', ['app.js'], {
|
||||||
|
detached: true,
|
||||||
|
stdio: 'inherit'
|
||||||
|
}).unref()
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
console.log('Update complete! Restarting server...');
|
||||||
|
|
||||||
|
// the following line restarts the server through nodemon
|
||||||
|
fs.writeFileSync('restart.json', 'internal use only');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateServer(tag) {
|
||||||
|
// no tag provided means update to the latest version
|
||||||
|
if (!tag) {
|
||||||
|
const new_version_available = await isNewVersionAvailable();
|
||||||
|
if (!new_version_available) {
|
||||||
|
console.log('ERROR: Failed to update - no update is available.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
// backup current dir
|
||||||
|
updaterStatus = {
|
||||||
|
updating: true,
|
||||||
|
'details': 'Backing up key server files...'
|
||||||
|
}
|
||||||
|
let backup_succeeded = await backupServerLite();
|
||||||
|
if (!backup_succeeded) {
|
||||||
|
resolve(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updaterStatus = {
|
||||||
|
updating: true,
|
||||||
|
'details': 'Downloading requested release...'
|
||||||
|
}
|
||||||
|
// grab new package.json and public folder
|
||||||
|
// await downloadReleaseFiles(tag);
|
||||||
|
|
||||||
|
updaterStatus = {
|
||||||
|
updating: true,
|
||||||
|
'details': 'Installing new dependencies...'
|
||||||
|
}
|
||||||
|
// run npm install
|
||||||
|
await installDependencies();
|
||||||
|
|
||||||
|
updaterStatus = {
|
||||||
|
updating: true,
|
||||||
|
'details': 'Update complete! Restarting server...'
|
||||||
|
}
|
||||||
|
restartServer();
|
||||||
|
}, err => {
|
||||||
|
updaterStatus = {
|
||||||
|
updating: false,
|
||||||
|
error: true,
|
||||||
|
'details': 'Update failed. Check error logs for more info.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadReleaseFiles(tag) {
|
||||||
|
tag = tag ? tag : await getLatestVersion();
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
console.log('Downloading new files...')
|
||||||
|
|
||||||
|
// downloads the latest release zip file
|
||||||
|
await downloadReleaseZip(tag);
|
||||||
|
|
||||||
|
// deletes contents of public dir
|
||||||
|
fs.removeSync(path.join(__dirname, 'public'));
|
||||||
|
fs.mkdirSync(path.join(__dirname, 'public'));
|
||||||
|
|
||||||
|
let replace_ignore_list = ['youtubedl-material/appdata/default.json',
|
||||||
|
'youtubedl-material/appdata/db.json']
|
||||||
|
console.log(`Installing update ${tag}...`)
|
||||||
|
|
||||||
|
// downloads new package.json and adds new public dir files from the downloaded zip
|
||||||
|
fs.createReadStream(path.join(__dirname, `youtubedl-material-latest-release-${tag}.zip`)).pipe(unzipper.Parse())
|
||||||
|
.on('entry', function (entry) {
|
||||||
|
var fileName = entry.path;
|
||||||
|
var type = entry.type; // 'Directory' or 'File'
|
||||||
|
var size = entry.size;
|
||||||
|
var is_dir = fileName.substring(fileName.length-1, fileName.length) === '/'
|
||||||
|
if (!is_dir && fileName.includes('youtubedl-material/public/')) {
|
||||||
|
// get public folder files
|
||||||
|
var actualFileName = fileName.replace('youtubedl-material/public/', '');
|
||||||
|
if (actualFileName.length !== 0 && actualFileName.substring(actualFileName.length-1, actualFileName.length) !== '/') {
|
||||||
|
fs.ensureDirSync(path.join(__dirname, 'public', path.dirname(actualFileName)));
|
||||||
|
entry.pipe(fs.createWriteStream(path.join(__dirname, 'public', actualFileName)));
|
||||||
|
} else {
|
||||||
|
entry.autodrain();
|
||||||
|
}
|
||||||
|
} else if (!is_dir && !replace_ignore_list.includes(fileName)) {
|
||||||
|
// get package.json
|
||||||
|
var actualFileName = fileName.replace('youtubedl-material/', '');
|
||||||
|
if (debugMode) console.log('Downloading file ' + actualFileName);
|
||||||
|
entry.pipe(fs.createWriteStream(path.join(__dirname, actualFileName)));
|
||||||
|
} else {
|
||||||
|
entry.autodrain();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('close', function () {
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to download file using fetch
|
||||||
|
async function fetchFile(url, path, file_label) {
|
||||||
|
var len = null;
|
||||||
|
const res = await fetch(url);
|
||||||
|
|
||||||
|
len = parseInt(res.headers.get("Content-Length"), 10);
|
||||||
|
|
||||||
|
var bar = new ProgressBar(` Downloading ${file_label} [:bar] :percent :etas`, {
|
||||||
|
complete: '=',
|
||||||
|
incomplete: ' ',
|
||||||
|
width: 20,
|
||||||
|
total: len
|
||||||
|
});
|
||||||
|
const fileStream = fs.createWriteStream(path);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
res.body.pipe(fileStream);
|
||||||
|
res.body.on("error", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
res.body.on('data', function (chunk) {
|
||||||
|
bar.tick(chunk.length);
|
||||||
|
});
|
||||||
|
fileStream.on("finish", function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadReleaseZip(tag) {
|
||||||
|
console.log('downloading');
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
// get name of zip file, which depends on the version
|
||||||
|
const latest_release_link = `https://github.com/Tzahi12345/YoutubeDL-Material/releases/download/${tag}/`;
|
||||||
|
const tag_without_v = tag.substring(1, tag.length);
|
||||||
|
const zip_file_name = `youtubedl-material-${tag_without_v}.zip`
|
||||||
|
const latest_zip_link = latest_release_link + zip_file_name;
|
||||||
|
let output_path = path.join(__dirname, `youtubedl-material-release-${tag}.zip`);
|
||||||
|
|
||||||
|
// download zip from release
|
||||||
|
await fetchFile(latest_zip_link, output_path, 'update ' + tag);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installDependencies() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
var child_process = require('child_process');
|
||||||
|
child_process.execSync('npm install',{stdio:[0,1,2]});
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function backupServerLite() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let output_path = `backup-${Date.now()}.zip`;
|
||||||
|
console.log(`Backing up your non-video/audio files to ${output_path}. This may take up to a few seconds/minutes.`);
|
||||||
|
let output = fs.createWriteStream(path.join(__dirname, output_path));
|
||||||
|
var archive = archiver('zip', {
|
||||||
|
gzip: true,
|
||||||
|
zlib: { level: 9 } // Sets the compression level.
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.on('error', function(err) {
|
||||||
|
console.log(err);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// pipe archive data to the output file
|
||||||
|
archive.pipe(output);
|
||||||
|
|
||||||
|
// ignore certain directories (ones with video or audio files)
|
||||||
|
const files_to_ignore = [path.join(config_api.getConfigItem('ytdl_subscriptions_base_path'), '**'),
|
||||||
|
path.join(config_api.getConfigItem('ytdl_audio_folder_path'), '**'),
|
||||||
|
path.join(config_api.getConfigItem('ytdl_video_folder_path'), '**'),
|
||||||
|
'backup-*.zip'];
|
||||||
|
|
||||||
|
archive.glob('**/*', {
|
||||||
|
ignore: files_to_ignore
|
||||||
|
});
|
||||||
|
|
||||||
|
await archive.finalize();
|
||||||
|
|
||||||
|
// wait a tiny bit for the zip to reload in fs
|
||||||
|
setTimeout(function() {
|
||||||
|
resolve(true);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isNewVersionAvailable() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
// gets tag of the latest version of youtubedl-material, compare to current version
|
||||||
|
const latest_tag = await getLatestVersion();
|
||||||
|
const current_tag = CONSTS['CURRENT_VERSION'];
|
||||||
|
if (latest_tag > current_tag) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLatestVersion() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
fetch('https://api.github.com/repos/tzahi12345/youtubedl-material/releases/latest', {method: 'Get'})
|
||||||
|
.then(async res => res.json())
|
||||||
|
.then(async (json) => {
|
||||||
|
if (json['message']) {
|
||||||
|
// means there's an error in getting latest version
|
||||||
|
console.log(`ERROR: Received the following message from GitHub's API:`);
|
||||||
|
console.log(json['message']);
|
||||||
|
if (json['documentation_url']) console.log(`Associated URL: ${json['documentation_url']}`)
|
||||||
|
}
|
||||||
|
resolve(json['tag_name']);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setPortItemFromENV() {
|
async function setPortItemFromENV() {
|
||||||
@@ -143,7 +393,6 @@ async function setPortItemFromENV() {
|
|||||||
async function setAndLoadConfig() {
|
async function setAndLoadConfig() {
|
||||||
await setConfigFromEnv();
|
await setConfigFromEnv();
|
||||||
await loadConfig();
|
await loadConfig();
|
||||||
// console.log(backendUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setConfigFromEnv() {
|
async function setConfigFromEnv() {
|
||||||
@@ -162,9 +411,6 @@ async function setConfigFromEnv() {
|
|||||||
|
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// get config library
|
|
||||||
// config = require('config');
|
|
||||||
|
|
||||||
url = !debugMode ? config_api.getConfigItem('ytdl_url') : 'http://localhost:4200';
|
url = !debugMode ? config_api.getConfigItem('ytdl_url') : 'http://localhost:4200';
|
||||||
backendPort = config_api.getConfigItem('ytdl_port');
|
backendPort = config_api.getConfigItem('ytdl_port');
|
||||||
usingEncryption = config_api.getConfigItem('ytdl_use_encryption');
|
usingEncryption = config_api.getConfigItem('ytdl_use_encryption');
|
||||||
@@ -657,7 +903,7 @@ async function autoUpdateYoutubeDL() {
|
|||||||
let current_app_details_path = 'node_modules/youtube-dl/bin/details';
|
let current_app_details_path = 'node_modules/youtube-dl/bin/details';
|
||||||
let current_app_details_exists = fs.existsSync(current_app_details_path);
|
let current_app_details_exists = fs.existsSync(current_app_details_path);
|
||||||
if (!current_app_details_exists) {
|
if (!current_app_details_exists) {
|
||||||
console.log(`Failed to get youtube-dl binary details at location: ${current_app_details_path}. Cancelling update check.`);
|
console.log(`ERROR: Failed to get youtube-dl binary details at location '${current_app_details_path}'. Cancelling update check.`);
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -665,9 +911,17 @@ async function autoUpdateYoutubeDL() {
|
|||||||
let current_version = current_app_details['version'];
|
let current_version = current_app_details['version'];
|
||||||
let stored_binary_path = current_app_details['path'];
|
let stored_binary_path = current_app_details['path'];
|
||||||
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
||||||
console.log(`Failed to get youtube-dl binary path at location: ${current_app_details_path}. Cancelling update check.`);
|
// console.log(`INFO: Failed to get youtube-dl binary path at location: ${current_app_details_path}, attempting to guess actual path...`);
|
||||||
resolve(false);
|
const guessed_base_path = 'node_modules/youtube-dl/bin/';
|
||||||
return;
|
const guessed_file_path = guessed_base_path + 'youtube-dl' + (process.platform === 'win32' ? '.exe' : '');
|
||||||
|
if (fs.existsSync(guessed_file_path)) {
|
||||||
|
stored_binary_path = guessed_file_path;
|
||||||
|
// console.log('INFO: Guess successful! Update process continuing...')
|
||||||
|
} else {
|
||||||
|
console.log(`ERROR: Guess '${guessed_file_path}' is not correct. Cancelling update check. Verify that your youtube-dl binaries exist by running npm install.`);
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// got version, now let's check the latest version from the youtube-dl API
|
// got version, now let's check the latest version from the youtube-dl API
|
||||||
@@ -676,6 +930,10 @@ async function autoUpdateYoutubeDL() {
|
|||||||
.then(async res => res.json())
|
.then(async res => res.json())
|
||||||
.then(async (json) => {
|
.then(async (json) => {
|
||||||
// check if the versions are different
|
// check if the versions are different
|
||||||
|
if (!json || !json[0]) {
|
||||||
|
resolve(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const latest_update_version = json[0]['name'];
|
const latest_update_version = json[0]['name'];
|
||||||
if (current_version !== latest_update_version) {
|
if (current_version !== latest_update_version) {
|
||||||
let binary_path = 'node_modules/youtube-dl/bin';
|
let binary_path = 'node_modules/youtube-dl/bin';
|
||||||
@@ -729,6 +987,21 @@ async function checkExistsWithTimeout(filePath, timeout) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/32197381/8088021
|
||||||
|
const deleteFolderRecursive = function(folder_to_delete) {
|
||||||
|
if (fs.existsSync(folder_to_delete)) {
|
||||||
|
fs.readdirSync(folder_to_delete).forEach((file, index) => {
|
||||||
|
const curPath = path.join(folder_to_delete, file);
|
||||||
|
if (fs.lstatSync(curPath).isDirectory()) { // recurse
|
||||||
|
deleteFolderRecursive(curPath);
|
||||||
|
} else { // delete file
|
||||||
|
fs.unlinkSync(curPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.rmdirSync(folder_to_delete);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
res.header("Access-Control-Allow-Origin", getOrigin());
|
res.header("Access-Control-Allow-Origin", getOrigin());
|
||||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||||
@@ -1469,6 +1742,32 @@ app.post('/api/downloadArchive', async (req, res) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Updater API calls
|
||||||
|
|
||||||
|
app.get('/api/updaterStatus', async (req, res) => {
|
||||||
|
let status = updaterStatus;
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
res.send(updaterStatus);
|
||||||
|
} else {
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/updateServer', async (req, res) => {
|
||||||
|
let tag = req.body.tag;
|
||||||
|
|
||||||
|
updateServer(tag);
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pin API calls
|
||||||
|
|
||||||
app.post('/api/isPinSet', async (req, res) => {
|
app.post('/api/isPinSet', async (req, res) => {
|
||||||
let stored_pin = db.get('pin_md5').value();
|
let stored_pin = db.get('pin_md5').value();
|
||||||
let is_set = false;
|
let is_set = false;
|
||||||
|
|||||||
@@ -68,10 +68,16 @@ function setConfigFile(config) {
|
|||||||
function getConfigItem(key) {
|
function getConfigItem(key) {
|
||||||
let config_json = getConfigFile();
|
let config_json = getConfigFile();
|
||||||
if (!CONFIG_ITEMS[key]) {
|
if (!CONFIG_ITEMS[key]) {
|
||||||
console.log('cannot find config with key ' + key);
|
console.log(`ERROR: Config item with key '${key}' is not recognized.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let path = CONFIG_ITEMS[key]['path'];
|
let path = CONFIG_ITEMS[key]['path'];
|
||||||
|
const val = Object.byString(config_json, path);
|
||||||
|
if (val === undefined && Object.byString(DEFAULT_CONFIG, path)) {
|
||||||
|
console.log(`WARNING: Cannot find config with key '${key}'. Creating one with the default value...`);
|
||||||
|
setConfigItem(key, Object.byString(DEFAULT_CONFIG, path));
|
||||||
|
return Object.byString(DEFAULT_CONFIG, path);
|
||||||
|
}
|
||||||
return Object.byString(config_json, path);
|
return Object.byString(config_json, path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -124,4 +124,7 @@ let CONFIG_ITEMS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.CONFIG_ITEMS = CONFIG_ITEMS;
|
module.exports = {
|
||||||
|
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||||
|
CURRENT_VERSION: 'v3.5.1'
|
||||||
|
}
|
||||||
@@ -5,7 +5,17 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "node app.js"
|
"start": "nodemon -q app.js"
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ignore": [
|
||||||
|
"*.js",
|
||||||
|
"appdata/*",
|
||||||
|
"public/*"
|
||||||
|
],
|
||||||
|
"watch": [
|
||||||
|
"restart.json"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -24,12 +34,16 @@
|
|||||||
"config": "^3.2.3",
|
"config": "^3.2.3",
|
||||||
"exe": "^1.0.2",
|
"exe": "^1.0.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"fs-extra": "^9.0.0",
|
||||||
"lowdb": "^1.0.0",
|
"lowdb": "^1.0.0",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"node-id3": "^0.1.14",
|
|
||||||
"merge-files": "^0.1.2",
|
"merge-files": "^0.1.2",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
|
"node-id3": "^0.1.14",
|
||||||
|
"nodemon": "^2.0.2",
|
||||||
|
"progress": "^2.0.3",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
|
"unzipper": "^0.10.10",
|
||||||
"uuidv4": "^6.0.6",
|
"uuidv4": "^6.0.6",
|
||||||
"youtube-dl": "^3.0.2"
|
"youtube-dl": "^3.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ services:
|
|||||||
- ./subscriptions:/app/subscriptions
|
- ./subscriptions:/app/subscriptions
|
||||||
ports:
|
ports:
|
||||||
- "8998:17442"
|
- "8998:17442"
|
||||||
image: tzahi12345/youtubedl-material:experimental
|
image: tzahi12345/youtubedl-material:latest
|
||||||
37
package.json
37
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-dl-material",
|
"name": "youtube-dl-material",
|
||||||
"version": "3.5.0",
|
"version": "3.5.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -19,25 +19,21 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/core": "^9.0.6",
|
"@angular-devkit/core": "^9.0.6",
|
||||||
"@angular/animations": "^9.0.6",
|
"@angular/animations": "^9.1.0",
|
||||||
"@angular/cdk": "^9.1.2",
|
"@angular/cdk": "^9.2.0",
|
||||||
"@angular/common": "^9.0.6",
|
"@angular/common": "^9.1.0",
|
||||||
"@angular/compiler": "^9.0.6",
|
"@angular/compiler": "^9.1.0",
|
||||||
"@angular/core": "^9.0.6",
|
"@angular/core": "^9.0.7",
|
||||||
"@angular/forms": "^9.0.6",
|
"@angular/forms": "^9.1.0",
|
||||||
"@angular/http": "^7.2.15",
|
"@angular/localize": "^9.1.0",
|
||||||
"@angular/localize": "^9.0.6",
|
"@angular/material": "^9.2.0",
|
||||||
"@angular/material": "^9.1.2",
|
"@angular/platform-browser": "^9.1.0",
|
||||||
"@angular/platform-browser": "^9.0.6",
|
"@angular/platform-browser-dynamic": "^9.1.0",
|
||||||
"@angular/platform-browser-dynamic": "^9.0.6",
|
"@angular/router": "^9.1.0",
|
||||||
"@angular/router": "^9.0.6",
|
|
||||||
"@locl/core": "0.0.1-beta.2",
|
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"ng-lazyload-image": "^7.0.1",
|
"ng-lazyload-image": "^7.0.1",
|
||||||
"ng4-configure": "^0.1.7",
|
|
||||||
"ngx-content-loading": "^0.1.3",
|
|
||||||
"ngx-videogular": "^9.0.1",
|
"ngx-videogular": "^9.0.1",
|
||||||
"rxjs": "^6.5.3",
|
"rxjs": "^6.5.3",
|
||||||
"rxjs-compat": "^6.0.0-rc.0",
|
"rxjs-compat": "^6.0.0-rc.0",
|
||||||
@@ -47,11 +43,10 @@
|
|||||||
"zone.js": "~0.10.2"
|
"zone.js": "~0.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.900.6",
|
"@angular-devkit/build-angular": "^0.901.0",
|
||||||
"@angular/cli": "^9.0.6",
|
"@angular/cli": "^9.0.7",
|
||||||
"@angular/compiler-cli": "^9.0.6",
|
"@angular/compiler-cli": "^9.0.7",
|
||||||
"@angular/language-service": "^9.0.6",
|
"@angular/language-service": "^9.0.7",
|
||||||
"@locl/cli": "0.0.1-beta.6",
|
|
||||||
"@types/core-js": "^2.5.2",
|
"@types/core-js": "^2.5.2",
|
||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/jasmine": "2.5.45",
|
"@types/jasmine": "2.5.45",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import { NgModule, LOCALE_ID } from '@angular/core';
|
import { NgModule, LOCALE_ID } from '@angular/core';
|
||||||
import { registerLocaleData } from '@angular/common';
|
import { registerLocaleData, CommonModule } from '@angular/common';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
@@ -27,7 +27,6 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
|||||||
import {DragDropModule} from '@angular/cdk/drag-drop';
|
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { HttpModule } from '@angular/http';
|
|
||||||
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { FileCardComponent } from './file-card/file-card.component';
|
import { FileCardComponent } from './file-card/file-card.component';
|
||||||
@@ -38,7 +37,6 @@ import { PlayerComponent } from './player/player.component';
|
|||||||
import {VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule} from 'ngx-videogular';
|
import {VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule} from 'ngx-videogular';
|
||||||
import { InputDialogComponent } from './input-dialog/input-dialog.component';
|
import { InputDialogComponent } from './input-dialog/input-dialog.component';
|
||||||
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
|
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
|
||||||
import { NgxContentLoadingModule } from 'ngx-content-loading';
|
|
||||||
import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component';
|
import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component';
|
||||||
import { CreatePlaylistComponent } from './create-playlist/create-playlist.component';
|
import { CreatePlaylistComponent } from './create-playlist/create-playlist.component';
|
||||||
import { DownloadItemComponent } from './download-item/download-item.component';
|
import { DownloadItemComponent } from './download-item/download-item.component';
|
||||||
@@ -54,6 +52,8 @@ import es from '@angular/common/locales/es';
|
|||||||
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
||||||
import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component';
|
import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifier-dialog/arg-modifier-dialog.component';
|
import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifier-dialog/arg-modifier-dialog.component';
|
||||||
|
import { UpdaterComponent } from './updater/updater.component';
|
||||||
|
import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component';
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||||
@@ -79,9 +79,12 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
AboutDialogComponent,
|
AboutDialogComponent,
|
||||||
VideoInfoDialogComponent,
|
VideoInfoDialogComponent,
|
||||||
ArgModifierDialogComponent,
|
ArgModifierDialogComponent,
|
||||||
HighlightPipe
|
HighlightPipe,
|
||||||
|
UpdaterComponent,
|
||||||
|
UpdateProgressDialogComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
|
CommonModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
MatNativeDateModule,
|
MatNativeDateModule,
|
||||||
@@ -90,7 +93,6 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
HttpModule,
|
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
@@ -118,7 +120,6 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
VgOverlayPlayModule,
|
VgOverlayPlayModule,
|
||||||
VgBufferingModule,
|
VgBufferingModule,
|
||||||
LazyLoadImageModule.forRoot({ isVisible }),
|
LazyLoadImageModule.forRoot({ isVisible }),
|
||||||
NgxContentLoadingModule,
|
|
||||||
RouterModule,
|
RouterModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
],
|
],
|
||||||
|
|||||||
1
src/app/consts.ts
Normal file
1
src/app/consts.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const CURRENT_VERSION = 'v3.5';
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<h5 style="margin-top: 10px;">Installation details:</h5>
|
<h5 style="margin-top: 10px;">Installation details:</h5>
|
||||||
<p>
|
<p>
|
||||||
<ng-container i18n="Version label">Installed version:</ng-container> {{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner> <ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
|
<ng-container i18n="Version label">Installed version:</ng-container> {{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner> <ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
|
||||||
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon> <a *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag" [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>
|
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon> <a *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag" [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>. <ng-container i18n="Update through settings menu hint">You can update from the settings menu.</ng-container>
|
||||||
<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] === current_version_tag">You are up to date.</span>
|
<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] === current_version_tag">You are up to date.</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { CURRENT_VERSION } from 'app/consts';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about-dialog',
|
selector: 'app-about-dialog',
|
||||||
@@ -14,7 +15,7 @@ export class AboutDialogComponent implements OnInit {
|
|||||||
latestGithubRelease = null;
|
latestGithubRelease = null;
|
||||||
checking_for_updates = true;
|
checking_for_updates = true;
|
||||||
|
|
||||||
current_version_tag = 'v3.5.1';
|
current_version_tag = CURRENT_VERSION;
|
||||||
|
|
||||||
constructor(private postsService: PostsService) { }
|
constructor(private postsService: PostsService) { }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<h4 i18n="Update progress dialog title" mat-dialog-title>Updater</h4>
|
||||||
|
|
||||||
|
<mat-dialog-content>
|
||||||
|
<div *ngIf="updateStatus">
|
||||||
|
<div style="margin-bottom: 8px;">
|
||||||
|
<h6 *ngIf="updateStatus['updating']">Update in progress</h6>
|
||||||
|
<h6 *ngIf="!updateStatus['updating'] && updateStatus['error']">Update failed</h6>
|
||||||
|
<h6 *ngIf="!updateStatus['updating'] && !updateStatus['error']">Update succeeded!</h6>
|
||||||
|
</div>
|
||||||
|
<mat-progress-bar *ngIf="updateStatus['updating']" mode="indeterminate"></mat-progress-bar>
|
||||||
|
<mat-progress-bar *ngIf="!updateStatus['updating']" mode="determinate" value="100"></mat-progress-bar>
|
||||||
|
<p style="margin-top: 4px; font-size: 13px;" *ngIf="updateStatus['details']">{{updateStatus['details']}}</p>
|
||||||
|
</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close><ng-container i18n="Close update progress dialog">Close</ng-container></button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UpdateProgressDialogComponent } from './update-progress-dialog.component';
|
||||||
|
|
||||||
|
describe('UpdateProgressDialogComponent', () => {
|
||||||
|
let component: UpdateProgressDialogComponent;
|
||||||
|
let fixture: ComponentFixture<UpdateProgressDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ UpdateProgressDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UpdateProgressDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-update-progress-dialog',
|
||||||
|
templateUrl: './update-progress-dialog.component.html',
|
||||||
|
styleUrls: ['./update-progress-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class UpdateProgressDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
updateStatus = null;
|
||||||
|
updateInterval = 250;
|
||||||
|
errored = false;
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService, private snackBar: MatSnackBar) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getUpdateProgress();
|
||||||
|
setInterval(() => {
|
||||||
|
this.getUpdateProgress();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateProgress() {
|
||||||
|
this.postsService.getUpdaterStatus().subscribe(res => {
|
||||||
|
this.updateStatus = res;
|
||||||
|
if (!this.updateStatus) {
|
||||||
|
// update complete?
|
||||||
|
console.log('Update complete? or not started');
|
||||||
|
}
|
||||||
|
if (this.updateStatus && this.updateStatus['error']) {
|
||||||
|
this.openSnackBar('Update failed. Check logs for more details.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public openSnackBar(message: string, action: string = '') {
|
||||||
|
this.snackBar.open(message, action, {
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,9 +10,7 @@
|
|||||||
<div *ngIf="!image_errored && thumbnailURL" class="img-div">
|
<div *ngIf="!image_errored && thumbnailURL" class="img-div">
|
||||||
<img class="image" (error) ="onImgError($event)" [id]="type" [lazyLoad]="thumbnailURL" [customObservable]="scrollAndLoad" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
<img class="image" (error) ="onImgError($event)" [id]="type" [lazyLoad]="thumbnailURL" [customObservable]="scrollAndLoad" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
||||||
<span *ngIf="!image_loaded">
|
<span *ngIf="!image_loaded">
|
||||||
<ngx-content-loading [width]="500" [height]="360">
|
|
||||||
<svg:g ngx-rect width="500" height="360" y="0" x="0" rx="4" ry="4"></svg:g>
|
|
||||||
</ngx-content-loading>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ export class PostsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadAsset(name) {
|
||||||
|
return this.http.get(`./assets/${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
setConfig(config) {
|
setConfig(config) {
|
||||||
return this.http.post(this.path + 'setConfig', {new_config_file: config});
|
return this.http.post(this.path + 'setConfig', {new_config_file: config});
|
||||||
}
|
}
|
||||||
@@ -180,10 +184,24 @@ export class PostsService {
|
|||||||
return this.http.post(this.path + 'getAllSubscriptions', {});
|
return this.http.post(this.path + 'getAllSubscriptions', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updates the server to the latest version
|
||||||
|
updateServer(tag) {
|
||||||
|
return this.http.post(this.path + 'updateServer', {tag: tag});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdaterStatus() {
|
||||||
|
return this.http.get(this.path + 'updaterStatus');
|
||||||
|
}
|
||||||
|
|
||||||
// gets tag of the latest version of youtubedl-material
|
// gets tag of the latest version of youtubedl-material
|
||||||
getLatestGithubRelease() {
|
getLatestGithubRelease() {
|
||||||
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases/latest');
|
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases/latest');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvailableRelease() {
|
||||||
|
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -247,6 +247,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<!-- Version Control -->
|
||||||
|
<mat-expansion-panel class="settings-expansion-panel mat-elevation-z4">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
<ng-container i18n="Version settings title">Version</ng-container>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div *ngIf="new_config" class="container-fluid">
|
||||||
|
<app-updater></app-updater>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
|
||||||
<!-- Advanced -->
|
<!-- Advanced -->
|
||||||
<mat-expansion-panel class="settings-expansion-panel mat-elevation-z4">
|
<mat-expansion-panel class="settings-expansion-panel mat-elevation-z4">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
import {DomSanitizer} from '@angular/platform-browser';
|
import {DomSanitizer} from '@angular/platform-browser';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component';
|
import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component';
|
||||||
|
import { CURRENT_VERSION } from 'app/consts';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@@ -24,6 +25,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
|
|
||||||
_settingsSame = true;
|
_settingsSame = true;
|
||||||
|
|
||||||
|
latestGithubRelease = null;
|
||||||
|
CURRENT_VERSION = CURRENT_VERSION
|
||||||
|
|
||||||
get settingsAreTheSame() {
|
get settingsAreTheSame() {
|
||||||
this._settingsSame = this.settingsSame()
|
this._settingsSame = this.settingsSame()
|
||||||
return this._settingsSame;
|
return this._settingsSame;
|
||||||
@@ -40,6 +44,8 @@ export class SettingsComponent implements OnInit {
|
|||||||
this.getConfig();
|
this.getConfig();
|
||||||
|
|
||||||
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
|
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
|
||||||
|
|
||||||
|
this.getLatestGithubRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
@@ -129,6 +135,12 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLatestGithubRelease() {
|
||||||
|
this.postsService.getLatestGithubRelease().subscribe(res => {
|
||||||
|
this.latestGithubRelease = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// snackbar helper
|
// snackbar helper
|
||||||
public openSnackBar(message: string, action: string = '') {
|
public openSnackBar(message: string, action: string = '') {
|
||||||
this.snackBar.open(message, action, {
|
this.snackBar.open(message, action, {
|
||||||
|
|||||||
18
src/app/updater/updater.component.html
Normal file
18
src/app/updater/updater.component.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<div style="display: block">
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<ng-container i18n="Select a version">Select a version:</ng-container>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="availableVersions" style="display: inline-block; margin-left: 15px;">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select [(ngModel)]="selectedVersion">
|
||||||
|
<mat-option *ngFor="let version of availableVersionsFiltered" [value]="version['tag_name']">
|
||||||
|
{{version['tag_name'] + (version === latestStableRelease ? ' - Latest Stable' : '') + (version['tag_name'] === CURRENT_VERSION ? ' - Current Version' : '')}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="selectedVersion && selectedVersion !== CURRENT_VERSION" style="display: inline-block; margin-left: 15px;">
|
||||||
|
<button (click)="updateServer()" color="accent" mat-raised-button><mat-icon>update</mat-icon>
|
||||||
|
<ng-container *ngIf="selectedVersion > CURRENT_VERSION">Upgrade to</ng-container><ng-container *ngIf="selectedVersion < CURRENT_VERSION">Downgrade to</ng-container> {{selectedVersion}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
0
src/app/updater/updater.component.scss
Normal file
0
src/app/updater/updater.component.scss
Normal file
25
src/app/updater/updater.component.spec.ts
Normal file
25
src/app/updater/updater.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UpdaterComponent } from './updater.component';
|
||||||
|
|
||||||
|
describe('UpdaterComponent', () => {
|
||||||
|
let component: UpdaterComponent;
|
||||||
|
let fixture: ComponentFixture<UpdaterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ UpdaterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UpdaterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
62
src/app/updater/updater.component.ts
Normal file
62
src/app/updater/updater.component.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { CURRENT_VERSION } from 'app/consts';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { UpdateProgressDialogComponent } from 'app/dialogs/update-progress-dialog/update-progress-dialog.component';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-updater',
|
||||||
|
templateUrl: './updater.component.html',
|
||||||
|
styleUrls: ['./updater.component.scss']
|
||||||
|
})
|
||||||
|
export class UpdaterComponent implements OnInit {
|
||||||
|
|
||||||
|
availableVersions = null;
|
||||||
|
availableVersionsFiltered = [];
|
||||||
|
versionsShowLimit = 5;
|
||||||
|
latestStableRelease = null;
|
||||||
|
selectedVersion = null;
|
||||||
|
CURRENT_VERSION = CURRENT_VERSION;
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService, private dialog: MatDialog) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getAvailableVersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateServer() {
|
||||||
|
this.postsService.updateServer(this.selectedVersion).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.openUpdateProgressDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableVersions() {
|
||||||
|
this.availableVersionsFiltered = [];
|
||||||
|
this.postsService.getAvailableRelease().subscribe(res => {
|
||||||
|
this.availableVersions = res;
|
||||||
|
for (let i = 0; i < this.availableVersions.length; i++) {
|
||||||
|
const currentVersion = this.availableVersions[i];
|
||||||
|
// if a stable release has not been found and the version is not "rc" (meaning it's stable) then set it as the stable release
|
||||||
|
if (!this.latestStableRelease && !currentVersion.tag_name.includes('rc')) {
|
||||||
|
this.latestStableRelease = currentVersion;
|
||||||
|
this.selectedVersion = this.latestStableRelease.tag_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.latestStableRelease && i >= this.versionsShowLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.availableVersionsFiltered.push(currentVersion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openUpdateProgressDialog() {
|
||||||
|
this.dialog.open(UpdateProgressDialogComponent, {
|
||||||
|
minWidth: '300px',
|
||||||
|
minHeight: '200px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
|
|
||||||
import { loadTranslations } from '@angular/localize';
|
import { loadTranslations } from '@angular/localize';
|
||||||
import { getTranslations, ParsedTranslationBundle } from '@locl/core';
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
@@ -17,8 +16,7 @@ if (!locale) {
|
|||||||
localStorage.setItem('locale', 'en');
|
localStorage.setItem('locale', 'en');
|
||||||
}
|
}
|
||||||
if (locale && locale !== 'en') {
|
if (locale && locale !== 'en') {
|
||||||
getTranslations(`./assets/i18n/messages.${locale}.json`).then(
|
fetch(`./assets/i18n/messages.${locale}.json`).then(res => res.json()).then((data) => {
|
||||||
(data: ParsedTranslationBundle) => {
|
|
||||||
loadTranslations(data as any);
|
loadTranslations(data as any);
|
||||||
import('./app/app.module').then(module => {
|
import('./app/app.module').then(module => {
|
||||||
platformBrowserDynamic()
|
platformBrowserDynamic()
|
||||||
|
|||||||
Reference in New Issue
Block a user