public Transform(XElement node) : base(node) { Replace = node.Element(XmlTag.Replace).Value; Subs = new Dictionary <string, ITransformSubstitution>(); var subs = node.Element(XmlTag.Subs); foreach (var s in subs.Elements(XmlTag.Sub)) { var tag = s.Element(XmlTag.Tag).As <string>(); // The capture tag that has an associated substitution var guid = s.Element(XmlTag.Id).As <Guid>(); // The unique id of the text transform var name = s.Element(XmlTag.Name).As <string>(); // The friendly name of the transform try { // Create a new instance of the appropriate transform and populate it with instance specific data var sub = Substitutors.FirstOrDefault(x => string.Compare(x.Guid.ToString(), guid.ToString(), StringComparison.OrdinalIgnoreCase) == 0); if (sub == null) { throw new Exception($"Text transform '{name}' (unique id: {guid}) was not found\r\nThis text transform will not behaviour correctly"); } sub = (ITransformSubstitution)Activator.CreateInstance(sub.GetType()); sub.FromXml(s.Element(XmlTag.SubData)); Subs.Add(tag, sub); } catch (Exception ex) { Log.Write(ELogLevel.Warn, ex, $"Text transform '{name}' ({guid}) failed to load"); Misc.ShowMessage(null, $"Text transform '{name}' ({guid}) failed to load\r\n{ex.Message}", Application.ProductName, MessageBoxIcon.Information); Subs.Add(tag, new SubNoChange()); } } PropertyChanged += HandlePatternChanged; UpdateSubs(); }
/// <summary>Searches the file from 'start' looking for a match to 'pat'</summary> /// <returns>Returns true if a match is found, false otherwise. If true /// is returned 'found' contains the file byte offset of the first match</returns> private bool Find(Pattern pat, long start, bool backward, out long found) { long at = -1; DialogResult res = DialogResult.Cancel; try { var body = backward ? (start == FileByteRange.End ? "Searching backward from the end of the file..." : "Searching backward from the current selection position...") : (start == FileByteRange.Beg ? "Searching forward from the start of the file..." : "Searching forward from the current selection position..."); // Although this search runs in a background thread, it's wrapped in a modal // dialog box, so it should be ok to use class members directly var search = new ProgressForm("Searching...", body, null, ProgressBarStyle.Marquee, (s, a, cb) => { var d = new BLIData(this, Src, fileend_: m_fileend); int last_progress = 0; d.progress = (scanned, length) => { int progress = (int)(100 * Math_.Frac(0, scanned, length != 0?length:1)); if (progress != last_progress) { cb(new ProgressForm.UserState { FractionComplete = progress * 0.01f }); last_progress = progress; } return(!s.CancelPending); }; // Searching.... DoFind(pat, start, backward, d, rng => { at = rng.Beg; return(false); }); // We can call BuildLineIndex in this thread context because we know // we're in a modal dialog. if (at != -1 && !s.CancelPending) { this.BeginInvoke(() => SelectRowByAddr(at)); } }) { StartPosition = FormStartPosition.CenterParent }; using (search) res = search.ShowDialog(this, 500); } catch (OperationCanceledException) {} catch (Exception ex) { Misc.ShowMessage(this, "Find terminated by an error.", "Find error", MessageBoxIcon.Error, ex); } found = at; return(res == DialogResult.OK); }
/// <summary>Export the current code lookup list to a file</summary> private void ExportCodeLookupList() { // Prompt for the file to save var dg = new SaveFileDialog { Title = "Export code lookup list", Filter = Constants.XmlOrCsvFileFilter }; if (dg.ShowDialog(this) != DialogResult.OK) { return; } try { string extn = Path.GetExtension(dg.FileName); extn = extn != null?extn.Trim(new[] { '.' }) : ""; // If a csv file is being saved, write out csv if (string.CompareOrdinal(extn, "csv") == 0) { // Create a CSV object var csv = new CSVData { AutoSize = true }; csv.Reserve(Values.Count, 2); foreach (var c in Values) { CSVData.Row row = new CSVData.Row(); row.Add(c.Key); row.Add(c.Value); csv.Add(row); } csv.Save(dg.FileName); } // Otherwise write out xml else { XDocument doc = new XDocument(new XElement(XmlTag.Root)); if (doc.Root == null) { throw new Exception("Failed to create root xml node"); } var codes = new XElement(XmlTag.CodeValues); foreach (var v in Values) { codes.Add(new XElement(XmlTag.CodeValue, new XElement(XmlTag.Code, v.Key), new XElement(XmlTag.Value, v.Value) )); } doc.Root.Add(codes); doc.Save(dg.FileName, SaveOptions.None); } } catch (Exception ex) { Misc.ShowMessage(this, "Export failed.", "Export Failed", MessageBoxIcon.Error, ex); } }
/// <summary>Launch the custom data source</summary> private void LogCustomDataSource(ICustomLogDataSource src, LogDataSourceRunData launch) { BufferedCustomDataSource buffered_src = null; try { // Close any currently open file // Strictly, we don't have to close because OpenLogFile closes before opening // however if the user reopens the same process the existing process will hold // a lock to the capture file preventing the new process being created. Src = null; // Set options so that data always shows PrepareForStreamedData(launch.OutputFilepath); // Launch the process with standard output/error redirected to the temporary file buffered_src = new BufferedCustomDataSource(src, launch); // Give some UI feedback when the data source ends buffered_src.ConnectionDropped += (s, a) => { this.BeginInvoke(() => SetStaticStatusMessage(string.Format("{0} stopped", src.ShortName), Color.Black, Color.LightSalmon)); }; // Attach the optional selection changed handler if (launch.HandleSelectionChanged != null) { SelectionChanged += (s, a) => launch.HandleSelectionChanged(a.Rows); } // Open the capture file created by buffered_src OpenSingleLogFile(buffered_src.Filepath, !buffered_src.TmpFile); buffered_src.Start(); SetStaticStatusMessage("Connected", Color.Black, Color.LightGreen); // Pass over the ref if (m_buffered_custom_source != null) { m_buffered_custom_source.Dispose(); } m_buffered_custom_source = buffered_src; buffered_src = null; } catch (Exception ex) { Log.Write(ELogLevel.Error, ex, $"Custom data source failed: {src.ShortName} -> {launch.OutputFilepath}"); Misc.ShowMessage(this, $"Failed to launch {src.ShortName}.", "Data Source Failed", MessageBoxIcon.Error, ex); } finally { if (buffered_src != null) { buffered_src.Dispose(); } } }
/// <summary>Searches the entire file and bookmarks all locations that match the find pattern</summary> private void FindBookmarkAll() { if (!PreFind()) { return; } try { Log.Write(ELogLevel.Info, "FindBookmarkAll"); var pat = FindUI.Pattern; const string body = "Bookmarking all found instances..."; // Although this search runs in a background thread, it's wrapped in a modal // dialog box, so it should be ok to use class members directly var search = new ProgressForm("Searching...", body, null, ProgressBarStyle.Marquee, (s, a, cb) => { var d = new BLIData(this, Src, fileend_: m_fileend); int last_progress = 0; d.progress = (scanned, length) => { int progress = (int)(100 * Math_.Frac(0, scanned, length != 0?length:1)); if (progress != last_progress) { cb(new ProgressForm.UserState { FractionComplete = progress * 0.01f }); last_progress = progress; } return(!s.CancelPending); }; // Searching.... DoFind(pat, 0, false, d, rng => { this.BeginInvoke(() => SetBookmark(rng, Bit.EState.Set)); return(true); }); }) { StartPosition = FormStartPosition.CenterParent }; using (search) search.ShowDialog(this, 500); } catch (OperationCanceledException) {} catch (Exception ex) { Misc.ShowMessage(this, "Find terminated by an error.", "Find error", MessageBoxIcon.Error, ex); } }
/// <summary>Open a TCP network connection and log anything read</summary> private void LogTcpNetConnection(NetConn conn) { BufferedTcpNetConn buffered_tcp_netconn = null; try { // Close any currently open file // Strictly, we don't have to close because OpenLogFile closes before opening // however if the user reopens the same connection the existing connection will // hold a lock on the capture file preventing the new connection being created. Src = null; // Set options so that data always shows PrepareForStreamedData(conn.OutputFilepath); // Launch the process with standard output/error redirected to the temporary file buffered_tcp_netconn = new BufferedTcpNetConn(conn); // Give some UI feedback if the connection drops buffered_tcp_netconn.ConnectionDropped += (s, a) => { this.BeginInvoke(() => SetStaticStatusMessage("Connection dropped", Color.Black, Color.LightSalmon)); }; // Open the capture file created by buffered_process OpenSingleLogFile(buffered_tcp_netconn.Filepath, !buffered_tcp_netconn.TmpFile); buffered_tcp_netconn.Start(this); SetStaticStatusMessage("Connected", Color.Black, Color.LightGreen); // Pass over the ref if (m_buffered_tcp_netconn != null) { m_buffered_tcp_netconn.Dispose(); } m_buffered_tcp_netconn = buffered_tcp_netconn; buffered_tcp_netconn = null; } catch (OperationCanceledException) {} catch (Exception ex) { Log.Write(ELogLevel.Error, ex, $"Failed to connect {conn.Hostname}:{conn.Port} -> {conn.OutputFilepath}"); Misc.ShowMessage(this, $"Failed to connect to {conn.Hostname}:{conn.Port}.", Application.ProductName, MessageBoxIcon.Error, ex); } finally { if (buffered_tcp_netconn != null) { buffered_tcp_netconn.Dispose(); } } }
/// <summary>Launch a process, piping its output into a temporary file</summary> private void LaunchProcess(LaunchApp conn) { Debug.Assert(conn != null); BufferedProcess buffered_process = null; try { // Close any currently open file // Strictly, we don't have to close because OpenLogFile closes before opening // however if the user reopens the same process the existing process will hold // a lock to the capture file preventing the new process being created. Src = null; // Set options so that data always shows PrepareForStreamedData(conn.OutputFilepath); // Launch the process with standard output/error redirected to the temporary file buffered_process = new BufferedProcess(conn); // Give some UI feedback when the process ends buffered_process.ConnectionDropped += (s, a) => { this.BeginInvoke(() => SetStaticStatusMessage(string.Format("{0} exited", Path.GetFileName(conn.Executable)), Color.Black, Color.LightSalmon)); }; // Open the capture file created by buffered_process OpenSingleLogFile(buffered_process.Filepath, !buffered_process.TmpFile); buffered_process.Start(); SetStaticStatusMessage("Connected", Color.Black, Color.LightGreen); // Pass over the ref if (m_buffered_process != null) { m_buffered_process.Dispose(); } m_buffered_process = buffered_process; buffered_process = null; } catch (Exception ex) { Log.Write(ELogLevel.Error, ex, $"Failed to launch child process {conn.Executable} {conn.Arguments} -> {conn.OutputFilepath}"); Misc.ShowMessage(this, $"Failed to launch child process {conn.Executable}.", Application.ProductName, MessageBoxIcon.Error, ex); } finally { if (buffered_process != null) { buffered_process.Dispose(); } } }
/// <summary>Called when scanning ends with an error. Shows an error dialog and turns off file watching</summary> private void BuildLineIndexTerminatedWithError(Exception err) { // Disable watched files, so we don't get an endless blizzard of error messages if (Settings.WatchEnabled) { EnableWatch(false); Misc.ShowHint(m_btn_watch, "File watching disabled due to error."); } Log.Write(ELogLevel.Error, err, $"Failed to build index list for {Src.Name}"); if (err is NoLinesException) { Misc.ShowMessage(this, err.Message, "Scanning file terminated", MessageBoxIcon.Information); } else { Misc.ShowMessage(this, "Scanning the log file ended with an error.", "Scanning file terminated", MessageBoxIcon.Error, err); } }
/// <summary> /// Export the file 'filepath' using current filters to the stream 'outp'. /// Note: this method throws if an exception occurs in the background thread.</summary> /// <param name="d">A copy of the data needed to do the export</param> /// <param name="ranges">Byte ranges within 'filepath' to be exported</param> /// <param name="row_delimiter">The delimiter that defines rows (robitised)</param> /// <param name="col_delimiter">The delimiter that defines columns (robitised)</param> /// <param name="outp">The stream to write the exported file to</param> private bool DoExportWithProgress(BLIData d, IEnumerable <RangeI> ranges, string row_delimiter, string col_delimiter, StreamWriter outp) { DialogResult res = DialogResult.Cancel; try { // Although this search runs in a background thread, it's wrapped in a modal // dialog box, so it should be ok to use class members directly var export = new ProgressForm("Exporting...", null, null, ProgressBarStyle.Continuous, (s, a, cb) => { // Report progress and test for cancel int last_progress = -1; d.progress = (scanned, length) => { int progress = (int)(100 * Math_.Frac(0, scanned, length != 0?length:1)); if (progress != last_progress) { cb(new ProgressForm.UserState { FractionComplete = progress * 0.01f }); last_progress = progress; } return(!s.CancelPending); }; // Do the export DoExport(d, ranges, row_delimiter, col_delimiter, outp); }) { StartPosition = FormStartPosition.CenterParent }; using (export) res = export.ShowDialog(this); } catch (OperationCanceledException) { } catch (Exception ex) { Misc.ShowMessage(this, "Exporting terminated due to an error.", "Export error", MessageBoxIcon.Error, ex); } return(res == DialogResult.OK); }
/// <summary>Import the code lookup list from a file</summary> private void ImportCodeLookupList() { // If there's an existing list, ask before clearing it if (Values.Count != 0) { var res = MsgBox.Show(this, "Replace the current list with code/value pairs loaded from file?", "Confirm Replace Codes", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (res != DialogResult.OK) { return; } } // Prompt for the file to load var dg = new OpenFileDialog { Title = "Import code lookup list", Filter = Constants.XmlOrCsvFileFilter }; if (dg.ShowDialog(this) != DialogResult.OK) { return; } try { string extn = Path.GetExtension(dg.FileName); extn = extn != null?extn.Trim(new[] { '.' }) : ""; // Load into a temp list var values = new List <Pair>(); bool partial_import = false; // Load from CSV if (string.Compare(extn, "csv", true) == 0) { var csv = CSVData.Load(dg.FileName, ignore_comment_rows: false); foreach (var row in csv.Rows) { partial_import |= row.Count != 0 && row.Count != 2; if (row.Count < 2) { continue; } values.Add(new Pair(row[0], row[1])); } } // Load from xml else { XDocument doc = XDocument.Load(dg.FileName); if (doc.Root == null) { throw new Exception("XML file invalid, no root node found"); } var codes = doc.Root.Element(XmlTag.CodeValues); if (codes == null) { throw new Exception("XML file invalid, no 'codevalues' element found"); } foreach (var code in codes.Elements(XmlTag.CodeValue)) { var cnode = code.Element(XmlTag.Code); var vnode = code.Element(XmlTag.Value); if (cnode != null && vnode != null) { values.Add(new Pair(cnode.Value, vnode.Value)); } else { partial_import = true; } } } // If errors where found during the import, ask the user if they still want to continue if (partial_import) { var res = MsgBox.Show(this, "Some imported data was ignored because it was invalid.\r\nContinue with import?", "Partial Import", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (res != DialogResult.OK) { return; } } // If successful, replace the existing list Values.Clear(); Values.AddRange(values); m_src.ResetBindings(false); } catch (Exception ex) { Misc.ShowMessage(this, "Import failed.", "Import Failed", MessageBoxIcon.Error, ex); } }
/// <summary>Search for the full path of adb.exe</summary> private void AutoDetectAdbPath() { // If the full path is saved in the settings, use that (if it's valid) if (Path_.FileExists(m_settings.AdbFullPath)) { SetAdbPath(m_settings.AdbFullPath); return; } // Quick check the most likely spot var likely_path = Path.Combine(Environment.GetEnvironmentVariable("ANDROID_HOME") ?? string.Empty, @"platform-tools\adb.exe"); if (Path_.FileExists(likely_path)) { SetAdbPath(likely_path); return; } // Not found? longer search... const string msg = "Searching for the Android Debugging Bridge application...\r\n" + "\r\n" + "Trying Path: {0}...\r\n" + "\r\n" + "Click cancel to locate it manually."; var adb_path = string.Empty; var search_paths = new[] { Environment.GetEnvironmentVariable("ANDROID_HOME"), Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\Android\android-sdk\platform-tools", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Android\android-sdk\platform-tools", @"D:\Program Files (x86)\Android\android-sdk\platform-tools", @"D:\Program Files\Android\android-sdk\platform-tools", @"E:\Program Files (x86)\Android\android-sdk\platform-tools", @"E:\Program Files\Android\android-sdk\platform-tools", Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"C:\" }; var res = DialogResult.Cancel; try { // Search for 'adb.exe' var find_adb = new ProgressForm("Locating 'adb.exe'...", string.Format(msg, string.Empty), Icon, ProgressBarStyle.Marquee, (s, a, cb) => { foreach (var path in search_paths) { if (s.CancelPending || adb_path.HasValue()) { break; } if (path == null || !Directory.Exists(path)) { continue; } cb(new ProgressForm.UserState { Description = string.Format(msg, path), ForceLayout = false }); Func <string, bool> progress = dir => { cb(ProgressForm.UserState.Empty); return(!s.CancelPending); }; foreach (var fi in Path_.EnumFileSystem(path, SearchOption.AllDirectories, regex_filter:@"adb\.exe", progress:progress)) { // Found one! adb_path = fi.FullName; return; } } }) { StartPosition = FormStartPosition.CenterParent, ClientSize = new Size(640, 280), }; using (find_adb) res = find_adb.ShowDialog(this); } catch (OperationCanceledException) {} catch (Exception ex) { Misc.ShowMessage(this, "An error occurred while searching for 'adb.exe'", "Locating 'adb.exe' failed", MessageBoxIcon.Error, ex); } if (res != DialogResult.OK) { return; } SetAdbPath(adb_path); }