diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/Main.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/Main.cs index 94edb17..fb51606 100644 --- a/YTDLNetFrontEnd/YTDLNetFrontEnd/Main.cs +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/Main.cs @@ -9,12 +9,13 @@ using System.Text; using System.Windows.Forms; using System.IO; using System.Threading.Tasks; +using com.captainalm.YTDLNetFrontEnd.util; namespace com.captainalm.YTDLNetFrontEnd { public partial class Main : Form { - private Process theProcess; + private MonitorableProcess theProcess; public Main() { @@ -33,12 +34,14 @@ namespace com.captainalm.YTDLNetFrontEnd } Environment.CurrentDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos); textBoxSaveDir.Text = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos); + textBoxOutput.AppendText("Starting: " + ProductName + " : " + ProductVersion + Environment.NewLine); + textBoxOutput.AppendText("Copyright: (C) " + this.CompanyName + " : BSD-3-Clause License" + Environment.NewLine); } private void buttonGo_Click(object sender, EventArgs e) { if (!backgroundWorkerMain.IsBusy) backgroundWorkerMain.RunWorkerAsync(BWArg.Go); - + } private void buttonInstall_Click(object sender, EventArgs e) @@ -82,17 +85,28 @@ namespace com.captainalm.YTDLNetFrontEnd { case BWArg.Install: setEnableButtons(false); - - var loctxt = - YTDL.executeInstall((YTDL.getInstalled() != ApplicationType.Unavailable) ? YTDL.getInstalled() : - (((Control.ModifierKeys & Keys.Shift) == Keys.Shift) ? ApplicationType.YoutubeDL : ApplicationType.YT_DLP), - YTDL.getInstalled() != ApplicationType.Unavailable) + - Environment.NewLine; - this.Invoke(new Action(() => - { - textBoxOutput.AppendText(loctxt); - })); + using (var locpro = + YTDL.executeInstall((YTDL.getInstalled() != ApplicationType.Unavailable) ? YTDL.getInstalled() : + (((Control.ModifierKeys & Keys.Shift) == Keys.Shift) ? ApplicationType.YoutubeDL : ApplicationType.YT_DLP), + YTDL.getInstalled() != ApplicationType.Unavailable)) + { + locpro.addOuputReceiver(new OutputReceiverTextbox(textBoxOutput)); + locpro.addErrorReceiver(new OutputReaderPrefixed(textBoxOutput, "Error: ")); + try + { + locpro.start(); + locpro.waitForExit(); + } + catch (Exception ex) + { + this.Invoke(new Action(() => + { + textBoxOutput.AppendText("Could not start python, is python installed?" + Environment.NewLine + + ex.GetType().FullName + " : " + ex.Message + Environment.NewLine); + })); + } + } if (YTDL.getInstalled() != ApplicationType.Unavailable) { @@ -110,8 +124,9 @@ namespace com.captainalm.YTDLNetFrontEnd if (theProcess != null) { - if (!theProcess.HasExited) theProcess.WaitForExit(); - theProcess.Close(); + if (((Control.ModifierKeys & Keys.Shift) == Keys.Shift)) theProcess.kill(); + theProcess.waitForExit(); + theProcess.close(); } var theTarget = ""; @@ -120,18 +135,33 @@ namespace com.captainalm.YTDLNetFrontEnd { theTarget = textBoxEntry.Text; textBoxEntry.Text = ""; + textBoxOutput.AppendText("Downloading: " + theTarget + Environment.NewLine); })); theProcess = YTDL.executeApplication(theTarget); if (theProcess != null) { - theProcess.BeginOutputReadLine(); - theProcess.BeginErrorReadLine(); - theProcess.OutputDataReceived += theProcess_OutputDataReceived; - theProcess.ErrorDataReceived += theProcess_ErrorDataReceived; - theProcess.Exited += theProcess_Exited; - theProcess.EnableRaisingEvents = true; - theProcess.WaitForExit(); + theProcess.addOuputReceiver(new OutputReceiverTextbox(textBoxOutput)); + theProcess.addErrorReceiver(new OutputReaderPrefixed(textBoxOutput, "Error: ")); + try + { + theProcess.start(); + } + catch (Exception ex) + { + this.Invoke(new Action(() => + { + textBoxOutput.AppendText("Could not start python, is python and the downloader installed?" + Environment.NewLine + + ex.GetType().FullName + " : " + ex.Message + Environment.NewLine); + })); + } + } + else + { + this.Invoke(new Action(() => + { + textBoxOutput.AppendText("Could not start python, is python and the downloader installed?" + Environment.NewLine); + })); } setEnableButtons(true); @@ -141,43 +171,8 @@ namespace com.captainalm.YTDLNetFrontEnd } } - void theProcess_Exited(object sender, EventArgs e) + private enum BWArg { - try - { - theProcess.CancelOutputRead(); - } - catch (InvalidOperationException ex) - { - } - try - { - theProcess.CancelErrorRead(); - } - catch (InvalidOperationException ex) - { - } - } - - void theProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data == null || e.Data.Equals("")) return; - this.Invoke(new Action(() => - { - textBoxOutput.AppendText("Error: " + e.Data + Environment.NewLine); - })); - } - - void theProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data == null || e.Data.Equals("")) return; - this.Invoke(new Action(() => - { - textBoxOutput.AppendText(e.Data + Environment.NewLine); - })); - } - - private enum BWArg { Install, Go } diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/Properties/AssemblyInfo.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/Properties/AssemblyInfo.cs index e6b3f09..6a4e6ea 100644 --- a/YTDLNetFrontEnd/YTDLNetFrontEnd/Properties/AssemblyInfo.cs +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.0.0")] -[assembly: AssemblyFileVersion("0.2.0.0")] +[assembly: AssemblyVersion("0.3.0.0")] +[assembly: AssemblyFileVersion("0.3.0.0")] diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDL.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDL.cs index 779b384..3c4c0a7 100644 --- a/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDL.cs +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDL.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.IO; using System.Threading; +using com.captainalm.YTDLNetFrontEnd.util; namespace com.captainalm.YTDLNetFrontEnd { @@ -12,7 +13,7 @@ namespace com.captainalm.YTDLNetFrontEnd { private static string[] pathLocs = Environment.GetEnvironmentVariable("PATH").Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - public static Process executeApplication(string target) + public static MonitorableProcess executeApplication(string target) { var packageName = ""; switch (getInstalled()) @@ -29,10 +30,10 @@ namespace com.captainalm.YTDLNetFrontEnd var pyProSet = new ProcessStartInfo(findExecutableInPath("python"), "-m " + packageName + " --hls-prefer-native " + ((getInstalled() == ApplicationType.YT_DLP) ? "-N 16 " : "") + "\"" + target + "\"") { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; - return Process.Start(pyProSet); + return new MonitorableProcess(pyProSet); } - public static string executeInstall(ApplicationType package, bool update) + public static MonitorableProcess executeInstall(ApplicationType package, bool update) { var packageName = ""; switch (package) @@ -44,17 +45,11 @@ namespace com.captainalm.YTDLNetFrontEnd packageName = "yt-dlp"; break; default: - return "Invalid Package Name"; + return null; } var pipProSet = new ProcessStartInfo(findExecutableInPath("python"), "-m pip install " + packageName + ((update) ? " --upgrade" : "")) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; - using (var pipPro = Process.Start(pipProSet)) - { - var rpote = new ReadProcessOutputToEnd(pipPro); - pipPro.WaitForExit(); - if (!rpote.getError().Equals("")) return rpote.getError(); - return rpote.getOutput(); - } + return new MonitorableProcess(pipProSet); } private static ApplicationType installed; @@ -66,11 +61,13 @@ namespace com.captainalm.YTDLNetFrontEnd { var pipProSet = new ProcessStartInfo(findExecutableInPath("python"), "-m pip freeze") { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; - using (var pipPro = Process.Start(pipProSet)) + using (var pipPro = new MonitorableProcess(pipProSet)) { - var rpote = new ReadProcessOutputToEnd(pipPro); - pipPro.WaitForExit(); - var theList = rpote.getOutput(); + var outputReader = new OutputReceiverReader(); + pipPro.addOuputReceiver(outputReader); + pipPro.start(); + pipPro.waitForExit(); + var theList = outputReader.getData(); if (theList.Contains("yt-dlp")) installed = ApplicationType.YT_DLP; else if (theList.Contains("youtube-dl")) installed = ApplicationType.YoutubeDL; else installed = ApplicationType.Unavailable; @@ -103,68 +100,4 @@ namespace com.captainalm.YTDLNetFrontEnd YoutubeDL = 1, YT_DLP = 2 } - - class ReadProcessOutputToEnd : IDisposable - { - Process theProcess; - string tout = ""; - string terr = ""; - - - public ReadProcessOutputToEnd(Process processIn) - { - theProcess = processIn; - theProcess.BeginOutputReadLine(); - theProcess.BeginErrorReadLine(); - theProcess.OutputDataReceived += theProcess_OutputDataReceived; - theProcess.ErrorDataReceived += theProcess_ErrorDataReceived; - theProcess.Exited += theProcess_Exited; - theProcess.EnableRaisingEvents = true; - } - - void theProcess_Exited(object sender, EventArgs e) - { - try - { - theProcess.CancelOutputRead(); - } - catch (InvalidOperationException ex) - { - } - try - { - theProcess.CancelErrorRead(); - } - catch (InvalidOperationException ex) - { - } - } - - void theProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data == null || e.Data.Equals("")) return; - terr += e.Data + Environment.NewLine; - } - - void theProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data == null || e.Data.Equals("")) return; - tout += e.Data + Environment.NewLine; - } - - public string getOutput() - { - return tout; - } - - public string getError() - { - return terr; - } - - public void Dispose() - { - theProcess.Close(); - } - } } diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDLNetFrontEnd.csproj b/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDLNetFrontEnd.csproj index a059135..84edb74 100644 --- a/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDLNetFrontEnd.csproj +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/YTDLNetFrontEnd.csproj @@ -55,6 +55,9 @@ + + + Main.cs diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/util/IProcessOuputReceiver.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/IProcessOuputReceiver.cs new file mode 100644 index 0000000..f5d4d23 --- /dev/null +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/IProcessOuputReceiver.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace com.captainalm.YTDLNetFrontEnd.util +{ + public interface IProcessOuputReceiver + { + void receiveData(string dataIn); + } +} diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/util/MonitorableProcess.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/MonitorableProcess.cs new file mode 100644 index 0000000..3db19c3 --- /dev/null +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/MonitorableProcess.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace com.captainalm.YTDLNetFrontEnd.util +{ + public class MonitorableProcess : IDisposable + { + protected ProcessStartInfo startInfo; + protected Process representation; + protected List outputReceivers = new List(); + protected List errorReceivers = new List(); + + private object inputSLock = new object(); + private object outputSLock = new object(); + private object processSLock = new object(); + + public MonitorableProcess(ProcessStartInfo startInfo, bool redirectInput = false) + { + this.startInfo = startInfo; + this.startInfo.UseShellExecute = false; + this.startInfo.RedirectStandardInput = redirectInput; + } + + public void addOuputReceiver(IProcessOuputReceiver processOuput) + { + lock (outputSLock) + { + lock (processSLock) + { + if (representation == null) startInfo.RedirectStandardOutput = true; + } + outputReceivers.Add(processOuput); + } + } + + public void removeOutputReceiver(IProcessOuputReceiver processOuput) + { + lock (outputSLock) + { + outputReceivers.Remove(processOuput); + } + } + + public void addErrorReceiver(IProcessOuputReceiver processError) + { + lock (outputSLock) + { + lock (processSLock) + { + if (representation == null) startInfo.RedirectStandardError = true; + } + errorReceivers.Add(processError); + } + } + + public void removeErrorReceiver(IProcessOuputReceiver processError) + { + lock (outputSLock) + { + errorReceivers.Remove(processError); + } + } + + public void writeData(string inputData) + { + lock (processSLock) + { + if (!startInfo.RedirectStandardInput || representation == null || representation.HasExited) return; + lock (inputSLock) + { + representation.StandardInput.Write(inputData); + } + } + } + + public Process start() + { + lock (processSLock) + { + representation = Process.Start(startInfo); + if (startInfo.RedirectStandardOutput) + { + representation.BeginOutputReadLine(); + representation.OutputDataReceived += representation_OutputDataReceived; + } + if (startInfo.RedirectStandardError) + { + representation.BeginErrorReadLine(); + representation.ErrorDataReceived += representation_ErrorDataReceived; + } + representation.Exited += representation_Exited; + representation.EnableRaisingEvents = true; + if (startInfo.RedirectStandardInput) representation.StandardInput.AutoFlush = true; + } + return representation; + } + + void representation_Exited(object sender, EventArgs e) + { + try + { + representation.CancelOutputRead(); + } + catch (InvalidOperationException ex) + { + } + try + { + representation.CancelErrorRead(); + } + catch (InvalidOperationException ex) + { + } + } + + protected void representation_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + foreach (var c in outputReceivers) if (e.Data != null && !e.Data.Equals("")) lock (outputSLock) c.receiveData(e.Data); + } + + void representation_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + foreach (var c in errorReceivers) if (e.Data != null && !e.Data.Equals("")) lock (outputSLock) c.receiveData(e.Data); + } + + public void waitForExit() + { + if (representation == null) return; + if (!representation.HasExited) representation.WaitForExit(); + } + + void IDisposable.Dispose() + { + close(); + } + + public void close() + { + if (representation == null) return; + waitForExit(); + representation.Close(); + } + + public void kill() + { + if (representation != null && !representation.HasExited) representation.Kill(); + } + } +} diff --git a/YTDLNetFrontEnd/YTDLNetFrontEnd/util/OutputReceivers.cs b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/OutputReceivers.cs new file mode 100644 index 0000000..8e2c78f --- /dev/null +++ b/YTDLNetFrontEnd/YTDLNetFrontEnd/util/OutputReceivers.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows.Forms; +namespace com.captainalm.YTDLNetFrontEnd.util +{ + public class OutputReceiverReader : IProcessOuputReceiver + { + private string theOutput = ""; + + public void receiveData(string dataIn) + { + theOutput += dataIn; + } + + public string getData() + { + return theOutput.TrimEnd(Environment.NewLine.ToCharArray()); + } + } + + public class OutputReceiverTextbox : IProcessOuputReceiver + { + protected TextBox theOutputTextbox; + + public OutputReceiverTextbox(TextBox tboxIn) + { + theOutputTextbox = tboxIn; + } + + public virtual void receiveData(string dataIn) + { + if (theOutputTextbox.InvokeRequired) + { + theOutputTextbox.Invoke(new Action(() => + { + theOutputTextbox.AppendText(dataIn + Environment.NewLine); + })); + } + } + } + + public class OutputReaderPrefixed : OutputReceiverTextbox + { + protected string prefix; + + public OutputReaderPrefixed(TextBox tboxIn, string prefix) + : base(tboxIn) + { + this.prefix = prefix; + } + + public override void receiveData(string dataIn) + { + base.receiveData(prefix + dataIn); + } + } +} \ No newline at end of file