// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // Scan a Source // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrake.ApplicationServices.Services { using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; using HandBrake.ApplicationServices.EventArgs; using HandBrake.ApplicationServices.Exceptions; using HandBrake.ApplicationServices.Parsing; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Utilities; using Parser = HandBrake.ApplicationServices.Parsing.Parser; /// /// Scan a Source /// public class ScanService : IScan { #region Private Variables /// /// The User Setting Service /// private readonly IUserSettingService userSettingService; /// /// The Log File Header /// private readonly StringBuilder header; /// /// The CLI data parser /// private Parser readData; /// /// The Log Buffer /// private StringBuilder logBuffer; /// /// The Process belonging to the CLI /// private Process hbProc; /// /// The stop scan. /// private bool cancelScan; #endregion /// /// Initializes a new instance of the class. /// /// /// The user Setting Service. /// public ScanService(IUserSettingService userSettingService) { this.userSettingService = userSettingService; this.logBuffer = new StringBuilder(); header = GeneralUtilities.CreateCliLogHeader(); } #region Events /// /// Scan has Started /// public event EventHandler ScanStared; /// /// Scan has completed /// public event ScanCompletedStatus ScanCompleted; /// /// Encode process has progressed /// public event ScanProgessStatus ScanStatusChanged; #endregion #region Public Properties /// /// Gets a value indicating whether IsScanning. /// public bool IsScanning { get; private set; } /// /// Gets the Souce Data. /// public Source SouceData { get; private set; } /// /// Gets ActivityLog. /// public string ActivityLog { get { string noLog = "No log data available... Log data will show here after you scan a source. \n\nOpen the log file directory to get previous log files."; return string.IsNullOrEmpty(this.logBuffer.ToString()) ? this.header + noLog : this.header + this.logBuffer.ToString(); } } #endregion #region Public Methods /// /// Scan a Source Path. /// Title 0: scan all /// /// /// Path to the file to scan /// /// /// int title number. 0 for scan all /// /// /// The preview Count. /// /// /// The post Scan Action. /// public void Scan(string sourcePath, int title, int previewCount, Action postScanAction) { Thread t = new Thread(unused => this.ScanSource(sourcePath, title, previewCount, postScanAction)); t.Start(); } /// /// Kill the scan /// public void Stop() { try { // Try to clean things up as best as possible. if (this.readData != null) { this.readData.OnScanProgress -= this.OnScanProgress; } cancelScan = true; if (this.hbProc != null && !this.hbProc.HasExited) { this.hbProc.Kill(); } } catch { // We don't really need to notify the user of any errors here. } } /// /// Take a Scan Log file, and process it as if it were from the CLI. /// /// /// The path to the log file. /// public void DebugScanLog(string path) { try { StreamReader parseLog = new StreamReader(path); this.readData = new Parser(parseLog.BaseStream); this.SouceData = Source.Parse(this.readData, this.userSettingService.GetUserSetting(ASUserSettingConstants.DisableLibDvdNav)); this.SouceData.ScanPath = path; if (this.ScanCompleted != null) { this.ScanCompleted(this, new ScanCompletedEventArgs(false, null, string.Empty)); } } catch (Exception e) { throw new GeneralApplicationException("Debug Run Failed", string.Empty, e); } } /// /// Shutdown the service. /// public void Shutdown() { // Nothing to do for this implementation. } #endregion #region Private Methods /// /// Start a scan for a given source path and title /// /// /// Path to the source file /// /// /// the title number to look at /// /// /// The preview Count. /// /// /// The post Scan Action. Disables the Scan Completed Event /// private void ScanSource(object sourcePath, int title, int previewCount, Action postScanAction) { try { this.IsScanning = true; this.cancelScan = false; if (this.ScanStared != null) { this.ScanStared(this, new EventArgs()); } this.logBuffer = new StringBuilder(); string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe"); string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string dvdInfoPath = Path.Combine( logDir, string.Format("last_scan_log{0}.txt", GeneralUtilities.ProcessId)); if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } // Make we don't pick up a stale last_encode_log.txt (and that we have rights to the file) if (File.Exists(dvdInfoPath)) { File.Delete(dvdInfoPath); } string extraArguments = string.Empty; if (previewCount != 10) { extraArguments += " --previews " + previewCount; } if (this.userSettingService.GetUserSetting(ASUserSettingConstants.DisableLibDvdNav)) { extraArguments += " --no-dvdnav"; } extraArguments += string.Format(" --min-duration={0}", this.userSettingService.GetUserSetting(ASUserSettingConstants.MinScanDuration)); if (title > 0) { extraArguments += " --scan "; } // Quick fix for "F:\\" style paths. We need \\\\ (Escaped \ twice) string source = sourcePath.ToString().EndsWith("\\") ? string.Format("\"{0}\\\\\"", sourcePath.ToString().TrimEnd('\\')) : "\"" + sourcePath + "\""; string query = string.Format(@" -i {0} -t{1} {2} -v ", source, title, extraArguments); this.hbProc = new Process { StartInfo = { FileName = handbrakeCLIPath, Arguments = string.Format(@" -i {0} -t{1} {2} -v ", source, title, extraArguments), RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; // Start the Scan this.hbProc.Start(); this.readData = new Parser(this.hbProc.StandardError.BaseStream); this.readData.OnScanProgress += this.OnScanProgress; this.SouceData = Source.Parse(this.readData, this.userSettingService.GetUserSetting(ASUserSettingConstants.DisableLibDvdNav)); this.SouceData.ScanPath = (string)sourcePath; // Write the Buffer out to file. using (StreamWriter scanLog = new StreamWriter(dvdInfoPath)) { // Only write the log file to disk if it's less than 50MB. if (this.readData.Buffer.Length < 50000000) { scanLog.WriteLine(header); scanLog.WriteLine(query); scanLog.Write(this.readData.Buffer); this.logBuffer.AppendLine(query); this.logBuffer.AppendLine(this.readData.Buffer.ToString()); } else { throw new GeneralApplicationException( "The Log file has not been written to disk as it has grown above the 50MB limit", " This indicates there was a problem during the scan process.", null); } } this.IsScanning = false; if (postScanAction != null) { postScanAction(true); } else { if (this.ScanCompleted != null) { if (logBuffer.ToString().Contains("scan: unrecognized file type")) this.ScanCompleted(this, new ScanCompletedEventArgs(false, null, "Unrecognized file type.")); else this.ScanCompleted(this, new ScanCompletedEventArgs(this.cancelScan, null, string.Empty)); } } } catch (GeneralApplicationException) { throw; } catch (Exception exc) { this.Stop(); if (postScanAction != null) { postScanAction(false); } else { if (this.ScanCompleted != null) { this.ScanCompleted(this, new ScanCompletedEventArgs(false, exc, "An Error has occured in ScanService.ScanSource()")); } } } } /// /// Fire an event when the scan process progresses /// /// the sender /// the current title being scanned /// the total number of titles /// The Percentage private void OnScanProgress(object sender, int currentTitle, int titleCount, decimal percentage) { ScanProgressEventArgs eventArgs = new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = titleCount, Percentage = percentage }; if (this.ScanStatusChanged != null) { this.ScanStatusChanged(this, eventArgs); } } #endregion } }