mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
feat: add log viewer (#132)
This commit is contained in:
10
main.js
10
main.js
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
63
modules/persistence/Logger.js
Normal file
63
modules/persistence/Logger.js
Normal 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;
|
||||
@@ -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");
|
||||
|
||||
@@ -40,7 +40,9 @@ contextBridge.exposeInMainWorld(
|
||||
"getDoneActions",
|
||||
"setDoneAction",
|
||||
"getSubtitles",
|
||||
"getSelectedSubtitles"
|
||||
"getSelectedSubtitles",
|
||||
"getLog",
|
||||
"saveLog"
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
return await ipcRenderer.invoke(channel, data);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">×</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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user