feat: add log viewer (#132)

This commit is contained in:
Jelle Glebbeek
2021-08-03 13:25:59 +02:00
parent 76048d70fd
commit fee3ab3a98
11 changed files with 199 additions and 8 deletions

10
main.js
View File

@@ -74,6 +74,16 @@ function startCriticalHandlers(env) {
taskList.restore()
});
//Send the log for a specific download to renderer
ipcMain.handle("getLog", (event, identifier) => {
return env.logger.get(identifier);
});
//Save the log when renderer asks main
ipcMain.handle("saveLog", (event, identifier) => {
return env.logger.save(identifier);
})
//Catch all console.log calls, print them to stdout and send them to the renderer devtools.
console.log = (arg) => {
process.stdout.write(arg + "\n");

View File

@@ -2,6 +2,7 @@ const Bottleneck = require("bottleneck");
const Filepaths = require("./Filepaths");
const Settings = require("./persistence/Settings");
const DetectPython = require("./DetectPython");
const Logger = require("./persistence/Logger");
const fs = require("fs").promises;
class Environment {
@@ -15,6 +16,7 @@ class Environment {
this.mainAudioQuality = "best";
this.mainDownloadSubs = false;
this.doneAction = "Do nothing";
this.logger = new Logger(this);
this.paths = new Filepaths(app, this);
this.downloadLimiter = new Bottleneck({
trackDoneStatus: true,

View File

@@ -321,6 +321,7 @@ class QueryManager {
this.managedVideos = this.managedVideos.filter(item => item.identifier !== video.identifier);
this.playlistMetadata = this.playlistMetadata.filter(item => item.video_url !== video.url && item.playlist_url !== video.url);
this.window.webContents.send("videoAction", { action: "remove", identifier: video.identifier })
this.environment.logger.clear(video.identifier);
}
onError(identifier) {

View File

@@ -106,6 +106,16 @@ class DownloadQuery extends Query {
const perLine = liveData.split("\n");
for(const line of perLine) {
this.video.setFilename(line);
if(line.lastIndexOf("[download]") !== line.indexOf("[download]")) {
const splitLines = line.split("[");
for(const splitLine of splitLines) {
if(splitLine.trim() !== "") {
this.environment.logger.log(this.video.identifier, "[" + splitLine.trim());
}
}
} else {
this.environment.logger.log(this.video.identifier, line);
}
}
if (!liveData.includes("[download]")) return;
if (!initialReset) {

View File

@@ -13,7 +13,7 @@ class InfoQueryList {
}
async start() {
let result = await new Promise(((resolve) => {
return await new Promise(((resolve) => {
let totalMetadata = [];
let playlistUrls = Utils.extractPlaylistUrls(this.query);
for (const videoData of playlistUrls[1]) {
@@ -43,7 +43,6 @@ class InfoQueryList {
});
}
}));
return result;
}
createVideo(data, url) {

View File

@@ -0,0 +1,63 @@
const {dialog} = require("electron");
const path = require("path");
const fs = require("fs");
class Logger {
constructor(environment) {
this.environment = environment;
this.logs = {};
}
log(identifier, line) {
if(line == null || line === "") return;
let trimmedLine;
if(line === "done") {
trimmedLine = "Download finished";
} else if(line === "killed") {
trimmedLine = "Download stopped";
} else {
trimmedLine = line.replace(/[\n\r]/g, "");
}
if(identifier in this.logs) {
this.logs[identifier].push(trimmedLine);
} else {
this.logs[identifier] = [trimmedLine];
}
}
get(identifier) {
return this.logs[identifier];
}
clear(identifier) {
delete this.logs[identifier];
}
async save(identifier) {
const logLines = this.logs[identifier];
let log = "";
for(const line of logLines) {
log += line + "\n";
}
const date = new Date().toLocaleString()
.replace(", ", "-")
.replaceAll("/", "-")
.replaceAll(":", "-")
.replace(/:.. /," ");
let result = await dialog.showSaveDialog(this.environment.win, {
defaultPath: path.join(this.environment.settings.downloadPath, "ytdl-log-" + date),
buttonLabel: "Save metadata",
filters: [
{ name: "txt", extensions: ["txt"] },
{ name: "All Files", extensions: ["*"] },
],
properties: ["createDirectory"]
});
if(!result.canceled) {
fs.promises.writeFile(result.filePath, log).then(() => console.log("Download log saved."));
}
}
}
module.exports = Logger;

View File

@@ -104,6 +104,7 @@ class Query {
resolve("done");
});
this.process.stderr.on("data", (data) => {
cb(data.toString());
if(this.environment.errorHandler.checkError(data.toString(), this.identifier)) {
cb("killed");
resolve("killed");

View File

@@ -40,7 +40,9 @@ contextBridge.exposeInMainWorld(
"getDoneActions",
"setDoneAction",
"getSubtitles",
"getSelectedSubtitles"
"getSelectedSubtitles",
"getLog",
"saveLog"
];
if (validChannels.includes(channel)) {
return await ipcRenderer.invoke(channel, data);

View File

@@ -500,6 +500,27 @@ option.audio {
}
}
/* LOG MODAL */
#logModal {
padding: 0 5rem;
}
#logModal .modal-dialog {
max-width: 1200px;
margin: 1.75rem auto !important;
}
#logModal .log {
overflow-y: auto;
max-height: 550px;
border-color: var(--tertiary-bg-color);
background-color: var(--secondary-bg-color);
}
#logModal .log p {
margin-bottom: 0.5rem;
}
/* INFO MODAL */
#info-description {
resize: none;

View File

@@ -222,6 +222,27 @@
</div>
</div>
<div class="modal fade" id="logModal" tabindex="-1" aria-labelledby="logModalLabel" aria-hidden="true">
<div class="modal-xl modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="logModalLabel">Download Log</h5>
<button type="button" class="close dismiss" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="log p-3 rounded w-100">Loading log...</div>
</div>
</div>
<div class="modal-footer">
<button type="button" title="Save as file" class="btn save btn-dark">Save as file</button>
<button type="button" class="btn btn-dark dismiss" >Close</button>
</div>
<span class="identifier d-none"></span>
</div>
</div>
</div>
<div class="modal fade" id="authModal" tabindex="-1" aria-labelledby="authModalLabel" aria-hidden="true">
<div class="modal-xl modal-dialog modal-dialog-centered">
<div class="modal-content">

View File

@@ -3,6 +3,7 @@ let linkCopied = false;
let progressCooldown = [];
let sizeCooldown = [];
let sizeCache = [];
let logUpdateTask;
(function () { init(); })();
@@ -147,6 +148,11 @@ async function init() {
$('#authModal').modal("hide");
});
$('#logModal .dismiss').on('click', () => {
$('#logModal').modal("hide");
clearInterval(logUpdateTask);
});
$('#subsModal .dismiss').on('click', () => {
$('#subsModal').modal("hide");
});
@@ -248,7 +254,11 @@ async function init() {
})
$('#infoModal .json').on('click', () => {
window.main.invoke('videoAction', {action: "downloadInfo", identifier: $('#infoModal .identifier').html()})
window.main.invoke('videoAction', {action: "downloadInfo", identifier: $('#infoModal .identifier').html()});
});
$('#logModal .save').on('click', () => {
window.main.invoke('saveLog', $('#logModal .identifier').html());
});
$('#clearBtn').on('click', () => {
@@ -308,10 +318,11 @@ async function init() {
}
$(card).find('.progress').addClass("d-flex");
$(card).find('.metadata.left').html('<strong>Speed: </strong>' + "0.00MiB/s").show();
$(card).find('.metadata.right').html('<strong>ETA: </strong>' + "Unknown");
$(card).find('.metadata.right').html('<strong>ETA: </strong>' + "Unknown").show();
$(card).find('.options').addClass("d-flex");
$(card).find('select').addClass("d-none");
$(card).find('.download-btn, .download-btn i, .subtitle-btn, .subtitle-btn i').addClass("disabled");
changeDownloadIconToLog(card);
if($(card).hasClass("unified")) {
$(card).find('.metadata.left, .metadata.right').empty();
$(card).find('.info').addClass("d-flex").removeClass("d-none");
@@ -621,10 +632,11 @@ async function addVideo(args) {
updateGlobalDownloadQuality();
$(template).find('.progress').addClass("d-flex");
$(template).find('.metadata.left').html('<strong>Speed: </strong>' + "0.00MiB/s").show();
$(template).find('.metadata.right').html('<strong>ETA: </strong>' + "Unknown");
$(template).find('.metadata.right').html('<strong>ETA: </strong>' + "Unknown").show();
$(template).find('.options').addClass("d-flex");
$(template).find('select').addClass("d-none");
$(template).find('.download-btn i, .download-btn, .subtitle-btn, .subtitle-btn i').addClass("disabled");
changeDownloadIconToLog(template);
});
$(template).find('.subtitle-btn').on('click', () => {
@@ -760,6 +772,7 @@ async function setUnifiedPlaylist(args) {
$(card).find('.metadata.info').html('Downloading playlist...');
$(card).find('select').addClass("d-none");
$(card).find('.download-btn i, .download-btn, .subtitle-btn, .subtitle-btn i').addClass("disabled");
changeDownloadIconToLog(card);
});
setCodecs(card, args.audioCodecs, args.formats);
@@ -870,7 +883,7 @@ function updateProgress(args) {
}
if(!progressCooldown.includes(args.identifier)) {
progressCooldown.push(args.identifier);
$(card).find('.metadata.right').html('<strong>ETA: </strong>' + args.progress.eta);
$(card).find('.metadata.right').html('<strong>ETA: </strong>' + args.progress.eta).show();
$(card).find('.metadata.left').html('<strong>Speed: </strong>' + args.progress.speed).show();
setTimeout(() => {
progressCooldown = progressCooldown.filter(item => item !== args.identifier);
@@ -1155,12 +1168,60 @@ function changeSubsToRetry(url, card) {
.find('i').removeClass("disabled");
}
function changeDownloadIconToLog(card) {
if(card == null) return;
$(card).find('.download-btn i')
.unbind()
.removeClass("bi-download")
.removeClass("disabled")
.addClass("bi-journal-text")
.on('click', () => {
const id = $(card).prop('id');
$('#logModal').modal("show").find('.identifier').html(id);
$('#logModal .log').html("Loading log...");
openLog(id);
logUpdateTask = setInterval(() => openLog(id), 1000);
});
$(card).find('.download-btn')
.unbind()
.removeClass("disabled");
}
function openLog(identifier) {
const logBox = $('#logModal .log');
window.main.invoke("getLog", identifier).then(log => {
if(log == null) {
$(logBox).val("No log was found for this video.")
} else {
let fullLog = "";
for(const line of log) {
if(line.startsWith("WARNING")) {
const pre = line.slice(0, line.indexOf("W")) + "<strong>" + line.slice(line.indexOf("W"));
const suffixed = pre.slice(0, pre.indexOf(":") + 1) + "</strong>" + pre.slice(pre.indexOf(":") + 1);
fullLog += "<p class='text-warning'>" + suffixed + "</p>";
} else if(line.startsWith("ERROR")) {
const pre = line.slice(0, line.indexOf("E")) + "<strong>" + line.slice(line.indexOf("E"));
const suffixed = pre.slice(0, pre.indexOf(":") + 1) + "</strong>" + pre.slice(pre.indexOf(":") + 1);
fullLog += "<p class='text-danger'>" + suffixed + "</p>";
} else if(line.startsWith("[")) {
const pre = line.slice(0, line.indexOf("[")) + "<strong>" + line.slice(line.indexOf("["));
const suffixed = pre.slice(0, pre.indexOf("]") + 1) + "</strong>" + pre.slice(pre.indexOf("]") + 1);
fullLog += "<p>" + suffixed + "</p>";
} else {
fullLog += "<p><strong>" + line + "</strong></p>";
}
}
$(logBox).html(fullLog);
}
})
}
function setError(code, description, unexpected, identifier, url) {
let card = getCard(identifier);
$(card).append(`<input type="hidden" class="url" value="${url}">`);
$(card).find('.progress-bar').removeClass("progress-bar-striped").removeClass("progress-bar-animated").css("width", "100%").css('background-color', 'var(--error-color)');
$(card).find('.buttons').children().each(function() {
if($(this).hasClass("remove-btn") || $(this).hasClass("info-btn")) {
if($(this).hasClass("remove-btn") || $(this).hasClass("info-btn") || $(this).find("i").hasClass("bi-journal-text")) {
$(this).removeClass("disabled").find('i').removeClass("disabled");
} else if($(this).hasClass("subtitle-btn")) {
changeSubsToRetry(url, card);