├── .gitignore
├── Screenshot.png
├── Source
├── App.ico
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Configuration.cs
├── ControlLog.xaml
├── ControlLog.xaml.cs
├── Global.cs
├── Icons
│ ├── App.png
│ ├── Line.png
│ ├── Search.png
│ └── SearchTerms.png
├── LogFile.cs
├── LogLine.cs
├── LogViewer.toml
├── LogViewer2.csproj
├── LogViewer2.sln
├── Methods.cs
├── SearchCriteria.cs
├── Searches.cs
├── WaitCursor.cs
├── WindowConfiguration.xaml
├── WindowConfiguration.xaml.cs
├── WindowGoToLine.xaml
├── WindowGoToLine.xaml.cs
├── WindowLine.xaml
├── WindowLine.xaml.cs
├── WindowMain.xaml
├── WindowMain.xaml.cs
├── WindowMultiSearch.xaml
├── WindowMultiSearch.xaml.cs
├── WindowSearchTerms.xaml
└── WindowSearchTerms.xaml.cs
├── help.md
├── help.pdf
├── readme.md
└── readme.pdf
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 |
33 | # Visual Studio 2015/2017 cache/options directory
34 | .vs/
35 | # Uncomment if you have tasks that create the project's static files in wwwroot
36 | #wwwroot/
37 |
38 | # Visual Studio 2017 auto generated files
39 | Generated\ Files/
40 |
41 | # MSTest test Results
42 | [Tt]est[Rr]esult*/
43 | [Bb]uild[Ll]og.*
44 |
45 | # NUnit
46 | *.VisualState.xml
47 | TestResult.xml
48 | nunit-*.xml
49 |
50 | # Build Results of an ATL Project
51 | [Dd]ebugPS/
52 | [Rr]eleasePS/
53 | dlldata.c
54 |
55 | # Benchmark Results
56 | BenchmarkDotNet.Artifacts/
57 |
58 | # .NET Core
59 | project.lock.json
60 | project.fragment.lock.json
61 | artifacts/
62 |
63 | # StyleCop
64 | StyleCopReport.xml
65 |
66 | # Files built by Visual Studio
67 | *_i.c
68 | *_p.c
69 | *_h.h
70 | *.ilk
71 | *.meta
72 | *.obj
73 | *.iobj
74 | *.pch
75 | *.pdb
76 | *.ipdb
77 | *.pgc
78 | *.pgd
79 | *.rsp
80 | *.sbr
81 | *.tlb
82 | *.tli
83 | *.tlh
84 | *.tmp
85 | *.tmp_proj
86 | *_wpftmp.csproj
87 | *.log
88 | *.vspscc
89 | *.vssscc
90 | .builds
91 | *.pidb
92 | *.svclog
93 | *.scc
94 |
95 | # Chutzpah Test files
96 | _Chutzpah*
97 |
98 | # Visual C++ cache files
99 | ipch/
100 | *.aps
101 | *.ncb
102 | *.opendb
103 | *.opensdf
104 | *.sdf
105 | *.cachefile
106 | *.VC.db
107 | *.VC.VC.opendb
108 |
109 | # Visual Studio profiler
110 | *.psess
111 | *.vsp
112 | *.vspx
113 | *.sap
114 |
115 | # Visual Studio Trace Files
116 | *.e2e
117 |
118 | # TFS 2012 Local Workspace
119 | $tf/
120 |
121 | # Guidance Automation Toolkit
122 | *.gpState
123 |
124 | # ReSharper is a .NET coding add-in
125 | _ReSharper*/
126 | *.[Rr]e[Ss]harper
127 | *.DotSettings.user
128 |
129 | # JustCode is a .NET coding add-in
130 | .JustCode
131 |
132 | # TeamCity is a build add-in
133 | _TeamCity*
134 |
135 | # DotCover is a Code Coverage Tool
136 | *.dotCover
137 |
138 | # AxoCover is a Code Coverage Tool
139 | .axoCover/*
140 | !.axoCover/settings.json
141 |
142 | # Visual Studio code coverage results
143 | *.coverage
144 | *.coveragexml
145 |
146 | # NCrunch
147 | _NCrunch_*
148 | .*crunch*.local.xml
149 | nCrunchTemp_*
150 |
151 | # MightyMoose
152 | *.mm.*
153 | AutoTest.Net/
154 |
155 | # Web workbench (sass)
156 | .sass-cache/
157 |
158 | # Installshield output folder
159 | [Ee]xpress/
160 |
161 | # DocProject is a documentation generator add-in
162 | DocProject/buildhelp/
163 | DocProject/Help/*.HxT
164 | DocProject/Help/*.HxC
165 | DocProject/Help/*.hhc
166 | DocProject/Help/*.hhk
167 | DocProject/Help/*.hhp
168 | DocProject/Help/Html2
169 | DocProject/Help/html
170 |
171 | # Click-Once directory
172 | publish/
173 |
174 | # Publish Web Output
175 | *.[Pp]ublish.xml
176 | *.azurePubxml
177 | # Note: Comment the next line if you want to checkin your web deploy settings,
178 | # but database connection strings (with potential passwords) will be unencrypted
179 | *.pubxml
180 | *.publishproj
181 |
182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
183 | # checkin your Azure Web App publish settings, but sensitive information contained
184 | # in these scripts will be unencrypted
185 | PublishScripts/
186 |
187 | # NuGet Packages
188 | *.nupkg
189 | # NuGet Symbol Packages
190 | *.snupkg
191 | # The packages folder can be ignored because of Package Restore
192 | **/[Pp]ackages/*
193 | # except build/, which is used as an MSBuild target.
194 | !**/[Pp]ackages/build/
195 | # Uncomment if necessary however generally it will be regenerated when needed
196 | #!**/[Pp]ackages/repositories.config
197 | # NuGet v3's project.json files produces more ignorable files
198 | *.nuget.props
199 | *.nuget.targets
200 |
201 | # Microsoft Azure Build Output
202 | csx/
203 | *.build.csdef
204 |
205 | # Microsoft Azure Emulator
206 | ecf/
207 | rcf/
208 |
209 | # Windows Store app package directories and files
210 | AppPackages/
211 | BundleArtifacts/
212 | Package.StoreAssociation.xml
213 | _pkginfo.txt
214 | *.appx
215 | *.appxbundle
216 | *.appxupload
217 |
218 | # Visual Studio cache files
219 | # files ending in .cache can be ignored
220 | *.[Cc]ache
221 | # but keep track of directories ending in .cache
222 | !?*.[Cc]ache/
223 |
224 | # Others
225 | ClientBin/
226 | ~$*
227 | *~
228 | *.dbmdl
229 | *.dbproj.schemaview
230 | *.jfm
231 | *.pfx
232 | *.publishsettings
233 | orleans.codegen.cs
234 |
235 | # Including strong name files can present a security risk
236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
237 | #*.snk
238 |
239 | # Since there are multiple workflows, uncomment next line to ignore bower_components
240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
241 | #bower_components/
242 |
243 | # RIA/Silverlight projects
244 | Generated_Code/
245 |
246 | # Backup & report files from converting an old project file
247 | # to a newer Visual Studio version. Backup files are not needed,
248 | # because we have git ;-)
249 | _UpgradeReport_Files/
250 | Backup*/
251 | UpgradeLog*.XML
252 | UpgradeLog*.htm
253 | ServiceFabricBackup/
254 | *.rptproj.bak
255 |
256 | # SQL Server files
257 | *.mdf
258 | *.ldf
259 | *.ndf
260 |
261 | # Business Intelligence projects
262 | *.rdl.data
263 | *.bim.layout
264 | *.bim_*.settings
265 | *.rptproj.rsuser
266 | *- [Bb]ackup.rdl
267 | *- [Bb]ackup ([0-9]).rdl
268 | *- [Bb]ackup ([0-9][0-9]).rdl
269 |
270 | # Microsoft Fakes
271 | FakesAssemblies/
272 |
273 | # GhostDoc plugin setting file
274 | *.GhostDoc.xml
275 |
276 | # Node.js Tools for Visual Studio
277 | .ntvs_analysis.dat
278 | node_modules/
279 |
280 | # Visual Studio 6 build log
281 | *.plg
282 |
283 | # Visual Studio 6 workspace options file
284 | *.opt
285 |
286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
287 | *.vbw
288 |
289 | # Visual Studio LightSwitch build output
290 | **/*.HTMLClient/GeneratedArtifacts
291 | **/*.DesktopClient/GeneratedArtifacts
292 | **/*.DesktopClient/ModelManifest.xml
293 | **/*.Server/GeneratedArtifacts
294 | **/*.Server/ModelManifest.xml
295 | _Pvt_Extensions
296 |
297 | # Paket dependency manager
298 | .paket/paket.exe
299 | paket-files/
300 |
301 | # FAKE - F# Make
302 | .fake/
303 |
304 | # CodeRush personal settings
305 | .cr/personal
306 |
307 | # Python Tools for Visual Studio (PTVS)
308 | __pycache__/
309 | *.pyc
310 |
311 | # Cake - Uncomment if you are using it
312 | # tools/**
313 | # !tools/packages.config
314 |
315 | # Tabs Studio
316 | *.tss
317 |
318 | # Telerik's JustMock configuration file
319 | *.jmconfig
320 |
321 | # BizTalk build output
322 | *.btp.cs
323 | *.btm.cs
324 | *.odx.cs
325 | *.xsd.cs
326 |
327 | # OpenCover UI analysis results
328 | OpenCover/
329 |
330 | # Azure Stream Analytics local run output
331 | ASALocalRun/
332 |
333 | # MSBuild Binary and Structured Log
334 | *.binlog
335 |
336 | # NVidia Nsight GPU debugger configuration file
337 | *.nvuser
338 |
339 | # MFractors (Xamarin productivity tool) working folder
340 | .mfractor/
341 |
342 | # Local History for Visual Studio
343 | .localhistory/
344 |
345 | # BeatPulse healthcheck temp database
346 | healthchecksdb
347 |
348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
349 | MigrationBackup/
350 |
351 | # Ionide (cross platform F# VS Code tools) working folder
352 | .ionide/
--------------------------------------------------------------------------------
/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Screenshot.png
--------------------------------------------------------------------------------
/Source/App.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Source/App.ico
--------------------------------------------------------------------------------
/Source/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Source/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace LogViewer2
4 | {
5 | ///
6 | /// Interaction logic for App.xaml
7 | ///
8 | public partial class App : Application
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Source/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/Source/Configuration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Drawing;
4 | using Nett;
5 |
6 | namespace LogViewer2
7 | {
8 | ///
9 | /// Allows us to save/load the configuration file to/from TOML
10 | ///
11 | public class Configuration
12 | {
13 | #region Member Variables
14 | public string HighlightColour { get; set; } = "Lime";
15 | public string ContextColour { get; set; } = "LightGray";
16 | public int MultiSelectLimit { get; set; } = 1000;
17 | public int NumContextLines { get; set; } = 0;
18 | private const string FILENAME = "LogViewer.toml";
19 | #endregion
20 |
21 | #region Public Methods
22 | ///
23 | ///
24 | ///
25 | ///
26 | public string Load()
27 | {
28 | try
29 | {
30 | if (File.Exists(this.GetPath()) == false)
31 | {
32 | return string.Empty;
33 | }
34 |
35 | Configuration c = Toml.ReadFile(this.GetPath());
36 | this.HighlightColour = c.HighlightColour;
37 | this.ContextColour = c.ContextColour;
38 | this.MultiSelectLimit = c.MultiSelectLimit;
39 | this.NumContextLines = c.NumContextLines;
40 |
41 | if (this.MultiSelectLimit > 10000)
42 | {
43 | this.MultiSelectLimit = 10000;
44 | return "The multiselect limit is 10000";
45 | }
46 |
47 | if (this.NumContextLines > 10)
48 | {
49 | this.NumContextLines = 10;
50 | return "The maximum number of context lines is 10";
51 | }
52 | return string.Empty;
53 | }
54 | catch (FileNotFoundException fileNotFoundEx)
55 | {
56 | return fileNotFoundEx.Message;
57 | }
58 | catch (UnauthorizedAccessException unauthAccessEx)
59 | {
60 | return unauthAccessEx.Message;
61 | }
62 | catch (IOException ioEx)
63 | {
64 | return ioEx.Message;
65 | }
66 | catch (Exception ex)
67 | {
68 | return ex.Message;
69 | }
70 | }
71 |
72 | ///
73 | ///
74 | ///
75 | ///
76 | public string Save()
77 | {
78 | try
79 | {
80 | Toml.WriteFile(this, this.GetPath());
81 | return string.Empty;
82 | }
83 | catch (FileNotFoundException fileNotFoundEx)
84 | {
85 | return fileNotFoundEx.Message;
86 | }
87 | catch (UnauthorizedAccessException unauthAccessEx)
88 | {
89 | return unauthAccessEx.Message;
90 | }
91 | catch (IOException ioEx)
92 | {
93 | return ioEx.Message;
94 | }
95 | catch (Exception ex)
96 | {
97 | return ex.Message;
98 | }
99 | }
100 |
101 | ///
102 | ///
103 | ///
104 | ///
105 | public Color GetHighlightColour()
106 | {
107 | Color temp = Color.FromName(this.HighlightColour);
108 | if (temp.IsKnownColor == false)
109 | {
110 | return Color.Lime;
111 | }
112 |
113 | return temp;
114 | }
115 |
116 | ///
117 | ///
118 | ///
119 | ///
120 | public Color GetContextColour()
121 | {
122 | Color temp = Color.FromName(this.ContextColour);
123 | if (temp.IsKnownColor == false)
124 | {
125 | return Color.LightGray;
126 | }
127 |
128 | return temp;
129 | }
130 | #endregion
131 |
132 | #region Misc Methods
133 | ///
134 | ///
135 | ///
136 | ///
137 | private string GetPath()
138 | {
139 | return System.IO.Path.Combine(Methods.GetApplicationDirectory(), FILENAME);
140 | }
141 | #endregion
142 | }
143 | }
--------------------------------------------------------------------------------
/Source/ControlLog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Source/ControlLog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Data;
11 | using System.Windows.Interop;
12 |
13 | namespace LogViewer2
14 | {
15 | ///
16 | ///
17 | ///
18 | public partial class ControlLog : System.Windows.Controls.UserControl
19 | {
20 | #region Delegates
21 | public delegate void SearchCompleteEvent(ControlLog cl, string fileName, TimeSpan duration, long matches, int numSearchTerms, bool cancelled);
22 | public delegate void CompleteEvent(ControlLog cl, string fileName, TimeSpan duration, bool cancelled);
23 | public delegate void BoolEvent(string fileName, bool val);
24 | public delegate void MessageEvent(string fileName, string message);
25 | public delegate void ProgressUpdateEvent(int percent);
26 | public delegate void ExportInitiatedEvent(ControlLog cl, bool all);
27 | public delegate void MultiSearchInitiatedEvent(ControlLog cl, List searches);
28 | #endregion
29 |
30 | #region Events
31 | public event SearchCompleteEvent SearchComplete;
32 | public event CompleteEvent LoadComplete;
33 | public event CompleteEvent ExportComplete;
34 | public event ProgressUpdateEvent ProgressUpdate;
35 | public event MessageEvent LoadError;
36 | public event ExportInitiatedEvent ExportInitiated;
37 | public event MultiSearchInitiatedEvent MultiSearchInitiated;
38 | #endregion
39 |
40 | #region Member Variables
41 | //private Color highlightColour { get; set; } = Color.Lime;
42 | // private Color contextColour { get; set; } = Color.LightGray;
43 | private Configuration config;
44 | public Searches Searches { get; set; }
45 | public Global.ViewMode ViewMode { get; set; } = Global.ViewMode.Standard;
46 | public List Lines { get; private set; } = new List();
47 | public LogLine LongestLine { get; private set; } = new LogLine();
48 | public int LineCount { get; private set; } = 0;
49 | private FileStream fileStream;
50 | private Mutex readMutex = new Mutex();
51 | public string FileName { get; private set; }
52 | public List FilterIds { get; private set; } = new List();
53 | public string Guid { get; private set; }
54 | #endregion
55 |
56 | #region Constructor
57 | ///
58 | ///
59 | ///
60 | ///
61 | public ControlLog(Configuration config)
62 | {
63 | InitializeComponent();
64 |
65 | this.AllowDrop = true;
66 | this.config = config;
67 | this.Guid = System.Guid.NewGuid().ToString();
68 | this.Searches = new Searches();
69 | }
70 | #endregion
71 |
72 | #region Public Methods
73 | ///
74 | ///
75 | ///
76 | ///
77 | ///
78 | public void Load(string filePath, CancellationToken ct)
79 | {
80 | this.Dispose();
81 | this.FileName = Path.GetFileName(filePath);
82 |
83 | Task.Run(() => {
84 |
85 | DateTime start = DateTime.Now;
86 | bool cancelled = false;
87 | bool error = false;
88 | try
89 | {
90 | byte[] tempBuffer = new byte[1024 * 1024];
91 |
92 | this.fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
93 | FileInfo fileInfo = new FileInfo(filePath);
94 |
95 | // Calcs and finally point the position to the end of the line
96 | long position = 0;
97 | // Holds the offset to the start of the next line
98 | long lineStartOffset = 0;
99 | // Checks if we have read less than requested e.g. buffer not filled/end of file
100 | bool lastSection = false;
101 | // Counter for process reporting
102 | int counter = 0;
103 | // Holds a counter to start checking for the next indexOf('\r')
104 | int startIndex = 0;
105 | // Once all of the \r (lines) have been emnumerated, there might still be data left in the
106 | // buffer, so this holds the number of bytes that need to be added onto the next line
107 | int bufferRemainder = 0;
108 | // Holds how many bytes were read from the last file stream read
109 | int numBytesRead = 0;
110 | // Holds the temporary string generated from the file stream buffer
111 | string tempStr = string.Empty;
112 | // Length of the current line
113 | int charCount;
114 | // Return value from IndexOf function
115 | int indexOf;
116 |
117 | while (position < this.fileStream.Length)
118 | {
119 | numBytesRead = this.fileStream.Read(tempBuffer, 0, 1024 * 1024);
120 | if (numBytesRead < 1048576)
121 |
122 | {
123 | lastSection = true;
124 | }
125 |
126 | tempStr = Encoding.ASCII.GetString(tempBuffer).Substring(0, numBytesRead);
127 | startIndex = 0;
128 |
129 | // Does the buffer contain at least one "\n", so now enumerate all instances of "\n"
130 | if (tempStr.IndexOf('\n') != -1)
131 | {
132 | while ((indexOf = tempStr.IndexOf('\n', startIndex)) != -1 && startIndex < numBytesRead)
133 | {
134 | if (indexOf != -1)
135 | {
136 | charCount = 0;
137 |
138 | // Check if the line contains a CR as well, if it does then we remove the last char as the char count
139 | if (indexOf != 0 && (int)tempBuffer[Math.Max(0, indexOf - 1)] == 13)
140 | {
141 | charCount = bufferRemainder + (indexOf - startIndex - 1);
142 | position += (long)charCount + 2L;
143 | }
144 | else
145 | {
146 | charCount = bufferRemainder + (indexOf - startIndex);
147 | position += (long)charCount + 1L;
148 | }
149 |
150 | AddLine(lineStartOffset, charCount);
151 |
152 | // The remaining number in the buffer gets set to 0 e.g. after
153 | //the first iteration as it would add onto the first line
154 | bufferRemainder = 0;
155 |
156 | // Set the offset to the end of the last line that has just been added
157 | lineStartOffset = position;
158 | startIndex = indexOf + 1;
159 | }
160 | }
161 |
162 | // We had some '\r' in the last buffer read, now they are processing, so just add the rest as the last line
163 | if (lastSection == true)
164 | {
165 | AddLine(lineStartOffset, bufferRemainder + (numBytesRead - startIndex));
166 | return;
167 | }
168 |
169 | bufferRemainder += numBytesRead - startIndex;
170 | }
171 | else
172 | {
173 | // The entire content of the buffer doesn't contain \r so just add the rest of content as the last line
174 | if (lastSection == true)
175 | {
176 | AddLine(lineStartOffset, bufferRemainder + (numBytesRead - startIndex));
177 | return;
178 | }
179 |
180 | bufferRemainder += numBytesRead;
181 | }
182 |
183 | if (counter++ % 50 == 0)
184 | {
185 | OnProgressUpdate((int)((double)position / (double)fileInfo.Length * 100));
186 |
187 | if (ct.IsCancellationRequested)
188 | {
189 | cancelled = true;
190 | return;
191 | }
192 | }
193 | } // WHILE
194 | }
195 | catch (IOException ex)
196 | {
197 | OnLoadError(ex.Message);
198 | error = true;
199 | }
200 | finally
201 | {
202 | if (error == false)
203 | {
204 | DateTime end = DateTime.Now;
205 |
206 | OnProgressUpdate(100);
207 | OnLoadComplete(end - start, cancelled);
208 | }
209 | }
210 | });
211 | }
212 |
213 | ///
214 | ///
215 | ///
216 | public void Dispose()
217 | {
218 | this.Searches = new Searches();
219 | this.Lines.Clear();
220 | this.LongestLine = new LogLine();
221 | this.LineCount = 0;
222 | this.FileName = String.Empty;
223 | this.FilterIds = new List();
224 | this.FilterIds.Clear();
225 | this.ItemsSource = null;
226 |
227 | if (this.fileStream != null)
228 | {
229 | this.fileStream.Dispose();
230 | }
231 | }
232 |
233 | ///
234 | ///
235 | ///
236 | ///
237 | ///
238 | public void SearchMulti(List scs, CancellationToken ct, int numContextLines)
239 | {
240 | Task.Run(() => {
241 |
242 | DateTime start = DateTime.Now;
243 | bool cancelled = false;
244 | long matches = 0;
245 | try
246 | {
247 | long counter = 0;
248 | string line = string.Empty;
249 | bool located = false;
250 |
251 | foreach (LogLine ll in this.Lines)
252 | {
253 | // Reset the match flag
254 | ll.SearchMatches.Clear();
255 | ClearContextLine(ll.LineNumber, numContextLines);
256 |
257 | foreach (SearchCriteria sc in scs)
258 | {
259 | line = this.GetLine(ll.LineNumber);
260 |
261 | located = false;
262 | switch (sc.Type)
263 | {
264 | case Global.SearchType.SubStringCaseInsensitive:
265 | if (line.IndexOf(sc.Pattern, 0, StringComparison.OrdinalIgnoreCase) > -1)
266 | {
267 | located = true;
268 | }
269 | break;
270 |
271 | case Global.SearchType.SubStringCaseSensitive:
272 | if (line.IndexOf(sc.Pattern, 0, StringComparison.Ordinal) > -1)
273 | {
274 | located = true;
275 | }
276 | break;
277 |
278 | case Global.SearchType.RegexCaseInsensitive:
279 | if (Regex.Match(line, sc.Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled) != Match.Empty)
280 | {
281 | located = true;
282 | }
283 | break;
284 |
285 | case Global.SearchType.RegexCaseSensitive:
286 | if (Regex.Match(line, sc.Pattern, RegexOptions.Compiled) != Match.Empty)
287 | {
288 | located = true;
289 | }
290 | break;
291 |
292 | default:
293 | break;
294 | }
295 |
296 | if (located == true)
297 | {
298 | matches++;
299 | ll.SearchMatches.Add(sc.Id);
300 |
301 | if (numContextLines > 0)
302 | {
303 | this.SetContextLines(ll.LineNumber, numContextLines);
304 | }
305 | }
306 | }
307 |
308 | if (counter++ % 50 == 0)
309 | {
310 | OnProgressUpdate((int)((double)counter / (double)this.Lines.Count * 100));
311 |
312 | if (ct.IsCancellationRequested)
313 | {
314 | cancelled = true;
315 | return;
316 | }
317 | }
318 | }
319 | }
320 | finally
321 | {
322 | DateTime end = DateTime.Now;
323 |
324 | OnProgressUpdate(100);
325 | OnSearchComplete(end - start, matches, scs.Count, cancelled);
326 | }
327 | });
328 | }
329 |
330 | ///
331 | ///
332 | ///
333 | ///
334 | ///
335 | public void Search(SearchCriteria sc, bool cumulative, CancellationToken ct, int numContextLines)
336 | {
337 | Task.Run(() => {
338 |
339 | DateTime start = DateTime.Now;
340 | bool cancelled = false;
341 | long matches = 0;
342 | try
343 | {
344 | long counter = 0;
345 | string line = string.Empty;
346 | bool located = false;
347 |
348 | foreach (LogLine ll in this.Lines)
349 | {
350 | if (cumulative == false)
351 | {
352 | // Reset the match flag
353 | ll.SearchMatches.Clear();
354 | //ll.IsContextLine = false;
355 |
356 | ClearContextLine(ll.LineNumber, numContextLines);
357 | }
358 | else
359 | {
360 | if (ll.SearchMatches.Count > 0)
361 | {
362 | continue;
363 | }
364 | }
365 |
366 | line = this.GetLine(ll.LineNumber);
367 |
368 | located = false;
369 | switch (sc.Type)
370 | {
371 | case Global.SearchType.SubStringCaseInsensitive:
372 | if (line.IndexOf(sc.Pattern, 0, StringComparison.OrdinalIgnoreCase) > -1)
373 | {
374 | located = true;
375 | }
376 | break;
377 |
378 | case Global.SearchType.SubStringCaseSensitive:
379 | if (line.IndexOf(sc.Pattern, 0, StringComparison.Ordinal) > -1)
380 | {
381 | located = true;
382 | }
383 | break;
384 |
385 | case Global.SearchType.RegexCaseInsensitive:
386 | if (Regex.Match(line, sc.Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled) != Match.Empty)
387 | {
388 | located = true;
389 | }
390 | break;
391 |
392 | case Global.SearchType.RegexCaseSensitive:
393 | if (Regex.Match(line, sc.Pattern, RegexOptions.Compiled) != Match.Empty)
394 | {
395 | located = true;
396 | }
397 | break;
398 |
399 | default:
400 | break;
401 | }
402 |
403 | if (located == false)
404 | {
405 | ll.SearchMatches.Remove(sc.Id);
406 | }
407 | else
408 | {
409 | matches++;
410 | ll.SearchMatches.Add(sc.Id);
411 |
412 | if (numContextLines > 0)
413 | {
414 | this.SetContextLines(ll.LineNumber, numContextLines);
415 | }
416 | }
417 |
418 | if (counter++ % 50 == 0)
419 | {
420 | OnProgressUpdate((int)((double)counter / (double)this.Lines.Count * 100));
421 |
422 | if (ct.IsCancellationRequested)
423 | {
424 | cancelled = true;
425 | return;
426 | }
427 | }
428 | }
429 | }
430 | finally
431 | {
432 | DateTime end = DateTime.Now;
433 |
434 | OnProgressUpdate(100);
435 | OnSearchComplete(end - start, matches, 1, cancelled);
436 | }
437 | });
438 | }
439 |
440 | ///
441 | ///
442 | ///
443 | ///
444 | public void Export(string filePath, CancellationToken ct)
445 | {
446 | this.ExportToFile((IEnumerable)this.ItemsSource, filePath, ct);
447 | }
448 |
449 | ///
450 | ///
451 | ///
452 | ///
453 | public void ExportSelected(string filePath, CancellationToken ct)
454 | {
455 | this.ExportToFile(listLines.SelectedItems.Cast(), filePath, ct);
456 | }
457 |
458 | ///
459 | ///
460 | ///
461 | public void ShowMultiSearch()
462 | {
463 | WindowMultiSearch wms = new WindowMultiSearch(this.Searches);
464 | // Hack to set owner from a user control?!
465 | HwndSource source = HwndSource.FromVisual(this) as HwndSource;
466 | if (source != null)
467 | {
468 | WindowInteropHelper helper = new WindowInteropHelper(wms);
469 | helper.Owner = source.Handle;
470 | }
471 | if (wms.ShowDialog() == false)
472 | {
473 | return;
474 | }
475 |
476 | // Clear any existing filter ID's as we will only show the multi-string search
477 | this.FilterIds.Clear();
478 | this.Searches.Reset();
479 | foreach (SearchCriteria sc in wms.NewSearches)
480 | {
481 | // Add the ID so that any matches show up straight away
482 | this.FilterIds.Add(sc.Id);
483 | this.Searches.Add(sc);
484 | }
485 |
486 | OnMultiSearchInitiated(wms.NewSearches);
487 | }
488 |
489 | ///
490 | ///
491 | ///
492 | public void UpdateColumnWidths()
493 | {
494 | if (double.IsNaN(colData.Width))
495 | {
496 | colData.Width = colData.ActualWidth;
497 | colData.Width = double.NaN;
498 | }
499 |
500 | if (double.IsNaN(colLineNum.Width))
501 | {
502 | colLineNum.Width = colLineNum.ActualWidth + 50;
503 | colLineNum.Width = double.NaN;
504 | }
505 | }
506 |
507 | ///
508 | ///
509 | ///
510 | ///
511 | ///
512 | public string GetLine(int lineNumber)
513 | {
514 | if (lineNumber >= this.Lines.Count)
515 | {
516 | return string.Empty;
517 | }
518 |
519 | byte[] buffer = new byte[this.Lines[lineNumber].CharCount + 1];
520 | try
521 | {
522 | this.readMutex.WaitOne();
523 | this.fileStream.Seek(this.Lines[lineNumber].Offset, SeekOrigin.Begin);
524 | this.fileStream.Read(buffer, 0, this.Lines[lineNumber].CharCount);
525 | this.readMutex.ReleaseMutex();
526 | }
527 | catch (Exception) { }
528 |
529 | return Regex.Replace(Encoding.ASCII.GetString(buffer), "[\0-\b\n\v\f\x000E-\x001F\x007F-ÿ]", "", RegexOptions.Compiled);
530 | }
531 | #endregion
532 |
533 | #region Properties
534 | ///
535 | ///
536 | ///
537 | public System.Collections.IEnumerable ItemsSource
538 | {
539 | get
540 | {
541 | return listLines.ItemsSource;
542 | }
543 | set
544 | {
545 | listLines.ItemsSource = value;
546 | }
547 | }
548 | #endregion
549 |
550 | #region Private Methods
551 | ///
552 | ///
553 | ///
554 | ///
555 | ///
556 | private void SetContextLines(long lineNumber, int numLines)
557 | {
558 | long temp = numLines;
559 | if (lineNumber < this.Lines.Count)
560 | {
561 | if (numLines + lineNumber > this.Lines.Count - 1)
562 | {
563 | temp = this.Lines.Count - lineNumber - 1;
564 | }
565 | for (int index = 1; index <= temp; index++)
566 | {
567 | this.Lines[(int)lineNumber + index].IsContextLine = true;
568 | }
569 | }
570 |
571 | if (lineNumber > 0)
572 | {
573 | if (lineNumber - numLines < 0)
574 | {
575 | temp = lineNumber;
576 | }
577 | for (int index = 1; index <= temp; index++)
578 | {
579 | this.Lines[(int)lineNumber - index].IsContextLine = true;
580 | }
581 | }
582 | }
583 |
584 | ///
585 | /// Clear the line that is the next after the farthest context
586 | /// line, so the flag is reset and we won't overwrite
587 | ///
588 | ///
589 | ///
590 | private void ClearContextLine(long lineNumber, int numLines)
591 | {
592 | if ((int)lineNumber + numLines + 1 < this.Lines.Count - 1)
593 | {
594 | this.Lines[(int)lineNumber + numLines + 1].IsContextLine = false;
595 | }
596 | }
597 |
598 | ///
599 | ///
600 | ///
601 | ///
602 | ///
603 | private void ExportToFile(IEnumerable lines, string filePath, CancellationToken ct)
604 | {
605 | Task.Run(() =>
606 | {
607 | DateTime start = DateTime.Now;
608 | bool cancelled = false;
609 | try
610 | {
611 | using (FileStream fs = new FileStream(filePath, FileMode.Create))
612 | {
613 | string lineStr = string.Empty;
614 | byte[] lineBytes;
615 | byte[] endLine = new byte[2] { 13, 10 };
616 |
617 | long counter = 0;
618 | foreach (LogLine ll in lines)
619 | {
620 | lineStr = this.GetLine(ll.LineNumber);
621 | lineBytes = Encoding.ASCII.GetBytes(lineStr);
622 | fs.Write(lineBytes, 0, lineBytes.Length);
623 | // Add \r\n
624 | fs.Write(endLine, 0, 2);
625 |
626 | if (counter++ % 50 == 0)
627 | {
628 | OnProgressUpdate((int)((double)counter / (double)Lines.Count * 100));
629 |
630 | if (ct.IsCancellationRequested)
631 | {
632 | cancelled = true;
633 | return;
634 | }
635 | }
636 | }
637 |
638 | }
639 | }
640 | finally
641 | {
642 | DateTime end = DateTime.Now;
643 |
644 | OnProgressUpdate(100);
645 | OnExportComplete(end - start, cancelled);
646 | }
647 | });
648 | }
649 |
650 | ///
651 | ///
652 | ///
653 | ///
654 | ///
655 | private void AddLine(long offset, int charCount)
656 | {
657 | LogLine ll = new LogLine
658 | {
659 | Control = this,
660 | Offset = offset,
661 | CharCount = charCount,
662 | LineNumber = this.LineCount
663 | };
664 |
665 | this.Lines.Add(ll);
666 |
667 | if (charCount > this.LongestLine.CharCount)
668 | {
669 | this.LongestLine.CharCount = charCount;
670 | this.LongestLine.LineNumber = ll.LineNumber;
671 | }
672 |
673 | this.LineCount++;
674 | }
675 | #endregion
676 |
677 | #region Event Methods
678 | ///
679 | ///
680 | ///
681 | private void OnExportInitiated(bool all)
682 | {
683 | ExportInitiated?.Invoke(this, all);
684 | }
685 |
686 | ///
687 | ///
688 | ///
689 | private void OnMultiSearchInitiated(List searches)
690 | {
691 | MultiSearchInitiated?.Invoke(this, searches);
692 | }
693 |
694 | ///
695 | ///
696 | ///
697 | private void OnLoadError(string message)
698 | {
699 | LoadError?.Invoke(this.FileName, message);
700 | }
701 |
702 | ///
703 | ///
704 | ///
705 | private void OnProgressUpdate(int progress)
706 | {
707 | ProgressUpdate?.Invoke(progress);
708 | }
709 |
710 | ///
711 | ///
712 | ///
713 | private void OnLoadComplete(TimeSpan duration, bool cancelled)
714 | {
715 | LoadComplete?.Invoke(this, this.FileName, duration, cancelled);
716 | }
717 |
718 | ///
719 | ///
720 | ///
721 | private void OnExportComplete(TimeSpan duration, bool cancelled)
722 | {
723 | ExportComplete?.Invoke(this, this.FileName, duration, cancelled);
724 | }
725 |
726 | ///
727 | ///
728 | ///
729 | private void OnSearchComplete(TimeSpan duration, long matches, int numTerms, bool cancelled)
730 | {
731 | SearchComplete?.Invoke(this, this.FileName, duration, matches, numTerms, cancelled);
732 | }
733 | #endregion
734 |
735 | #region Filter Methods
736 | ///
737 | ///
738 | ///
739 | ///
740 | ///
741 | private bool ShowFilter(object item)
742 | {
743 | var ll = item as LogLine;
744 | return ll != null && (ll.SearchMatches.Intersect(this.FilterIds).Any() == true || (ll.IsContextLine == true));
745 | }
746 |
747 | ///
748 | ///
749 | ///
750 | ///
751 | ///
752 | private bool HideFilter(object item)
753 | {
754 | var ll = item as LogLine;
755 | return ll != null && (ll.SearchMatches.Intersect(this.FilterIds).Any() == false);
756 | }
757 | #endregion
758 |
759 | #region Context Menu Event Handlers
760 | ///
761 | ///
762 | ///
763 | ///
764 | ///
765 | private void CtxMenuFilteringShowMatched_Click(object sender, System.Windows.RoutedEventArgs e)
766 | {
767 | CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.ItemsSource);
768 | view.Filter = ShowFilter;
769 | }
770 |
771 | ///
772 | ///
773 | ///
774 | ///
775 | ///
776 | private void CtxMenuFilteringHideMatched_Click(object sender, System.Windows.RoutedEventArgs e)
777 | {
778 | CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.ItemsSource);
779 | view.Filter = HideFilter;
780 | }
781 |
782 | ///
783 | ///
784 | ///
785 | ///
786 | ///
787 | private void CtxMenuFilteringClear_Click(object sender, System.Windows.RoutedEventArgs e)
788 | {
789 | CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.ItemsSource);
790 | view.Filter = null;
791 | }
792 |
793 | ///
794 | ///
795 | ///
796 | ///
797 | ///
798 | private void CtxMenuSearchViewTerms_Click(object sender, System.Windows.RoutedEventArgs e)
799 | {
800 | WindowSearchTerms wst = new WindowSearchTerms(this.Searches);
801 | // Hack to set owner from a user control?!
802 | HwndSource source = HwndSource.FromVisual(this) as HwndSource;
803 | if (source != null)
804 | {
805 | WindowInteropHelper helper = new WindowInteropHelper(wst);
806 | helper.Owner = source.Handle;
807 | }
808 | if (wst.ShowDialog() == false)
809 | {
810 | return;
811 | }
812 |
813 | this.Searches = wst.Searches;
814 | this.FilterIds.Clear();
815 | foreach (SearchCriteria sc in this.Searches.Items)
816 | {
817 | if (sc.Enabled == false)
818 | {
819 | continue;
820 | }
821 |
822 | this.FilterIds.Add(sc.Id);
823 | }
824 |
825 | listLines.ItemsSource = null;
826 | listLines.ItemsSource = this.Lines;
827 | }
828 |
829 | ///
830 | ///
831 | ///
832 | ///
833 | ///
834 | private void CtxMenuExportAll_Click(object sender, System.Windows.RoutedEventArgs e)
835 | {
836 | OnExportInitiated(true);
837 | }
838 |
839 | ///
840 | ///
841 | ///
842 | ///
843 | ///
844 | private void CtxMenuExportSelected_Click(object sender, System.Windows.RoutedEventArgs e)
845 | {
846 | OnExportInitiated(false);
847 | }
848 |
849 | ///
850 | ///
851 | ///
852 | ///
853 | ///
854 | private void CtxMenuCopy_Click(object sender, System.Windows.RoutedEventArgs e)
855 | {
856 | StringBuilder sb = new StringBuilder();
857 | //LogFile lf = logs[tabControl.SelectedTab.Tag.ToString()];
858 |
859 | foreach (LogLine ll in listLines.SelectedItems)
860 | {
861 | sb.AppendLine(this.GetLine(ll.LineNumber));
862 | }
863 |
864 | Clipboard.SetText(sb.ToString());
865 | }
866 |
867 | ///
868 | ///
869 | ///
870 | ///
871 | ///
872 | private void CtxMenu_ContextMenuOpening(object sender, System.Windows.Controls.ContextMenuEventArgs e)
873 | {
874 | bool enableLineOps = true;
875 | if (this.LineCount == 0)
876 | {
877 | enableLineOps = false;
878 | }
879 |
880 | ctxMenuLinesFirst.IsEnabled = enableLineOps;
881 | ctxMenuLinesLast.IsEnabled = enableLineOps;
882 | ctxMenuLinesGoTo.IsEnabled = enableLineOps;
883 |
884 | if (listLines.SelectedItems.Count > this.config.MultiSelectLimit)
885 | {
886 | ctxMenuCopy.IsEnabled = false;
887 | ctxMenuExportSelected.IsEnabled = false;
888 | return;
889 | }
890 |
891 | ctxMenuCopy.IsEnabled = true;
892 | ctxMenuExportSelected.IsEnabled = true;
893 | }
894 |
895 | ///
896 | ///
897 | ///
898 | ///
899 | ///
900 | private void CtxMenuLinesGoTo_Click(object sender, System.Windows.RoutedEventArgs e)
901 | {
902 | WindowGoToLine wgtl = new WindowGoToLine(this.LineCount);
903 | // Hack to set owner from a user control?!
904 | HwndSource source = HwndSource.FromVisual(this) as HwndSource;
905 | if (source != null)
906 | {
907 | WindowInteropHelper helper = new WindowInteropHelper(wgtl);
908 | helper.Owner = source.Handle;
909 | }
910 |
911 | if (wgtl.ShowDialog() == false)
912 | {
913 | return;
914 | }
915 |
916 | this.listLines.ScrollIntoView(this.listLines.Items[wgtl.LineNo]);
917 | this.listLines.SelectedItem = this.listLines.Items[wgtl.LineNo];
918 | }
919 |
920 | ///
921 | ///
922 | ///
923 | ///
924 | ///
925 | private void CtxMenuLinesFirst_Click(object sender, System.Windows.RoutedEventArgs e)
926 | {
927 | this.listLines.ScrollIntoView(this.listLines.Items[0]);
928 | this.listLines.SelectedItem = this.listLines.Items[0];
929 | }
930 |
931 | ///
932 | ///
933 | ///
934 | ///
935 | ///
936 | private void CtxMenuLinesLast_Click(object sender, System.Windows.RoutedEventArgs e)
937 | {
938 | this.listLines.ScrollIntoView(this.listLines.Items[this.LineCount - 1]);
939 | this.listLines.SelectedItem = this.listLines.Items[this.LineCount - 1];
940 | }
941 | #endregion
942 |
943 | #region Listview Event Handlers
944 | ///
945 | ///
946 | ///
947 | ///
948 | ///
949 | private void ListLines_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
950 | {
951 | if (listLines.SelectedItems.Count != 1)
952 | {
953 | return;
954 | }
955 |
956 | LogLine ll = (LogLine)listLines.SelectedItem;
957 |
958 | WindowLine wl = new WindowLine(this.GetLine(ll.LineNumber));
959 | // Hack to set owner from a user control?!
960 | HwndSource source = HwndSource.FromVisual(this) as HwndSource;
961 | if (source != null)
962 | {
963 | WindowInteropHelper helper = new WindowInteropHelper(wl);
964 | helper.Owner = source.Handle;
965 | }
966 |
967 | wl.ShowDialog();
968 | }
969 | #endregion
970 | }
971 | }
972 |
--------------------------------------------------------------------------------
/Source/Global.cs:
--------------------------------------------------------------------------------
1 | namespace LogViewer2
2 | {
3 | public class Global
4 | {
5 | #region Enums
6 | ///
7 | ///
8 | ///
9 | public enum SearchType
10 | {
11 | SubStringCaseInsensitive = 0,
12 | SubStringCaseSensitive = 1,
13 | RegexCaseInsensitive = 2,
14 | RegexCaseSensitive = 3,
15 | }
16 |
17 | ///
18 | ///
19 | ///
20 | public enum ViewMode
21 | {
22 | Standard = 1,
23 | FilterShow = 2,
24 | FilterHide = 3
25 | }
26 | #endregion
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Icons/App.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Source/Icons/App.png
--------------------------------------------------------------------------------
/Source/Icons/Line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Source/Icons/Line.png
--------------------------------------------------------------------------------
/Source/Icons/Search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Source/Icons/Search.png
--------------------------------------------------------------------------------
/Source/Icons/SearchTerms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/Source/Icons/SearchTerms.png
--------------------------------------------------------------------------------
/Source/LogFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using System.Windows.Controls;
12 |
13 | namespace LogViewer2
14 | {
15 | ///
16 | ///
17 | ///
18 | internal class LogFile
19 | {
20 | #region Delegates
21 | public delegate void SearchCompleteEvent(LogFile lf, string fileName, TimeSpan duration, long matches, int numSearchTerms, bool cancelled);
22 | public delegate void CompleteEvent(LogFile lf, string fileName, TimeSpan duration, bool cancelled);
23 | public delegate void BoolEvent(string fileName, bool val);
24 | public delegate void MessageEvent(string fileName, string message);
25 | public delegate void ProgressUpdateEvent(int percent);
26 | #endregion
27 |
28 | #region Events
29 | public event SearchCompleteEvent SearchComplete;
30 | public event CompleteEvent LoadComplete;
31 | public event CompleteEvent ExportComplete;
32 | public event ProgressUpdateEvent ProgressUpdate;
33 | public event MessageEvent LoadError;
34 | #endregion
35 |
36 | #region Member Variables
37 | //private Color highlightColour { get; set; } = Color.Lime;
38 | // private Color contextColour { get; set; } = Color.LightGray;
39 | public Searches Searches { get; set; }
40 | public Global.ViewMode ViewMode { get; set; } = Global.ViewMode.Standard;
41 | public List Lines { get; private set; } = new List();
42 | public LogLine LongestLine { get; private set; } = new LogLine();
43 | public int LineCount { get; private set; } = 0;
44 | private FileStream fileStream;
45 | private Mutex readMutex = new Mutex();
46 | public string FileName { get; private set; }
47 | public List FilterIds { get; private set; } = new List();
48 | public ControlLog List { get; set; }
49 | public string Guid { get; private set; }
50 | #endregion
51 |
52 | ///
53 | ///
54 | ///
55 | public LogFile()
56 | {
57 | this.Guid = System.Guid.NewGuid().ToString();
58 | this.Searches = new Searches();
59 | }
60 |
61 | #region Public Methods
62 | ///
63 | ///
64 | ///
65 | ///
66 | ///
67 | public void Load(string filePath, SynchronizationContext st, CancellationToken ct)
68 | {
69 | this.Dispose();
70 | this.FileName = Path.GetFileName(filePath);
71 |
72 | Task.Run(() => {
73 |
74 | DateTime start = DateTime.Now;
75 | bool cancelled = false;
76 | bool error = false;
77 | try
78 | {
79 | byte[] tempBuffer = new byte[1024 * 1024];
80 |
81 | this.fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
82 | FileInfo fileInfo = new FileInfo(filePath);
83 |
84 | // Calcs and finally point the position to the end of the line
85 | long position = 0;
86 | // Holds the offset to the start of the next line
87 | long lineStartOffset = 0;
88 | // Checks if we have read less than requested e.g. buffer not filled/end of file
89 | bool lastSection = false;
90 | // Counter for process reporting
91 | int counter = 0;
92 | // Holds a counter to start checking for the next indexOf('\r')
93 | int startIndex = 0;
94 | // Once all of the \r (lines) have been emnumerated, there might still be data left in the
95 | // buffer, so this holds the number of bytes that need to be added onto the next line
96 | int bufferRemainder = 0;
97 | // Holds how many bytes were read from the last file stream read
98 | int numBytesRead = 0;
99 | // Holds the temporary string generated from the file stream buffer
100 | string tempStr = string.Empty;
101 | // Length of the current line
102 | int charCount;
103 | // Return value from IndexOf function
104 | int indexOf;
105 |
106 | while (position < this.fileStream.Length)
107 | {
108 | numBytesRead = this.fileStream.Read(tempBuffer, 0, 1024 * 1024);
109 | if (numBytesRead < 1048576)
110 |
111 | {
112 | lastSection = true;
113 | }
114 |
115 | tempStr = Encoding.ASCII.GetString(tempBuffer).Substring(0, numBytesRead);
116 | startIndex = 0;
117 |
118 | // Does the buffer contain at least one "\n", so now enumerate all instances of "\n"
119 | if (tempStr.IndexOf('\n') != -1)
120 | {
121 | while ((indexOf = tempStr.IndexOf('\n', startIndex)) != -1 && startIndex < numBytesRead)
122 | {
123 | if (indexOf != -1)
124 | {
125 | charCount = 0;
126 |
127 | // Check if the line contains a CR as well, if it does then we remove the last char as the char count
128 | if (indexOf != 0 && (int)tempBuffer[Math.Max(0, indexOf - 1)] == 13)
129 | {
130 | charCount = bufferRemainder + (indexOf - startIndex - 1);
131 | position += (long)charCount + 2L;
132 | }
133 | else
134 | {
135 | charCount = bufferRemainder + (indexOf - startIndex);
136 | position += (long)charCount + 1L;
137 | }
138 |
139 | AddLine(lineStartOffset, charCount);
140 |
141 | // The remaining number in the buffer gets set to 0 e.g. after
142 | //the first iteration as it would add onto the first line
143 | bufferRemainder = 0;
144 |
145 | // Set the offset to the end of the last line that has just been added
146 | lineStartOffset = position;
147 | startIndex = indexOf + 1;
148 | }
149 | }
150 |
151 | // We had some '\r' in the last buffer read, now they are processing, so just add the rest as the last line
152 | if (lastSection == true)
153 | {
154 | AddLine(lineStartOffset, bufferRemainder + (numBytesRead - startIndex));
155 | return;
156 | }
157 |
158 | bufferRemainder += numBytesRead - startIndex;
159 | }
160 | else
161 | {
162 | // The entire content of the buffer doesn't contain \r so just add the rest of content as the last line
163 | if (lastSection == true)
164 | {
165 | AddLine(lineStartOffset, bufferRemainder + (numBytesRead - startIndex));
166 | return;
167 | }
168 |
169 | bufferRemainder += numBytesRead;
170 | }
171 |
172 | if (counter++ % 50 == 0)
173 | {
174 | OnProgressUpdate((int)((double)position / (double)fileInfo.Length * 100));
175 |
176 | if (ct.IsCancellationRequested)
177 | {
178 | cancelled = true;
179 | return;
180 | }
181 | }
182 | } // WHILE
183 | }
184 | catch (IOException ex)
185 | {
186 | OnLoadError(ex.Message);
187 | error = true;
188 | }
189 | finally
190 | {
191 | if (error == false)
192 | {
193 | DateTime end = DateTime.Now;
194 |
195 | OnProgressUpdate(100);
196 | OnLoadComplete(end - start, cancelled);
197 | }
198 | }
199 | });
200 | }
201 |
202 | ///
203 | ///
204 | ///
205 | public void Dispose()
206 | {
207 | this.Searches = new Searches();
208 | this.Lines.Clear();
209 | this.LongestLine = new LogLine();
210 | this.LineCount = 0;
211 | this.FileName = String.Empty;
212 | this.FilterIds = new List();
213 | //this.List.ModelFilter = null;
214 | this.FilterIds.Clear();
215 | //this.List.ClearObjects();
216 |
217 | if (this.fileStream != null)
218 | {
219 | this.fileStream.Dispose();
220 | }
221 | }
222 |
223 | ///
224 | ///
225 | ///
226 | ///
227 | ///
228 | public void SearchMulti(List scs, CancellationToken ct, int numContextLines)
229 | {
230 | Task.Run(() => {
231 |
232 | DateTime start = DateTime.Now;
233 | bool cancelled = false;
234 | long matches = 0;
235 | try
236 | {
237 | long counter = 0;
238 | string line = string.Empty;
239 | bool located = false;
240 |
241 | foreach (LogLine ll in this.Lines)
242 | {
243 | // Reset the match flag
244 | ll.SearchMatches.Clear();
245 | ClearContextLine(ll.LineNumber, numContextLines);
246 |
247 | foreach (SearchCriteria sc in scs)
248 | {
249 | line = this.GetLine(ll.LineNumber);
250 |
251 | located = false;
252 | switch (sc.Type)
253 | {
254 | case Global.SearchType.SubStringCaseInsensitive:
255 | if (line.IndexOf(sc.Pattern, 0, StringComparison.OrdinalIgnoreCase) > -1)
256 | {
257 | located = true;
258 | }
259 | break;
260 |
261 | case Global.SearchType.SubStringCaseSensitive:
262 | if (line.IndexOf(sc.Pattern, 0, StringComparison.Ordinal) > -1)
263 | {
264 | located = true;
265 | }
266 | break;
267 |
268 | case Global.SearchType.RegexCaseInsensitive:
269 | if (Regex.Match(line, sc.Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled) != Match.Empty)
270 | {
271 | located = true;
272 | }
273 | break;
274 |
275 | case Global.SearchType.RegexCaseSensitive:
276 | if (Regex.Match(line, sc.Pattern, RegexOptions.Compiled) != Match.Empty)
277 | {
278 | located = true;
279 | }
280 | break;
281 |
282 | default:
283 | break;
284 | }
285 |
286 | if (located == true)
287 | {
288 | matches++;
289 | ll.SearchMatches.Add(sc.Id);
290 |
291 | if (numContextLines > 0)
292 | {
293 | this.SetContextLines(ll.LineNumber, numContextLines);
294 | }
295 | }
296 | }
297 |
298 | if (counter++ % 50 == 0)
299 | {
300 | OnProgressUpdate((int)((double)counter / (double)this.Lines.Count * 100));
301 |
302 | if (ct.IsCancellationRequested)
303 | {
304 | cancelled = true;
305 | return;
306 | }
307 | }
308 | }
309 | }
310 | finally
311 | {
312 | DateTime end = DateTime.Now;
313 |
314 | OnProgressUpdate(100);
315 | OnSearchComplete(end - start, matches, scs.Count, cancelled);
316 | }
317 | });
318 | }
319 |
320 | ///
321 | ///
322 | ///
323 | ///
324 | ///
325 | public void Search(SearchCriteria sc, bool cumulative, CancellationToken ct, int numContextLines)
326 | {
327 | Task.Run(() => {
328 |
329 | DateTime start = DateTime.Now;
330 | bool cancelled = false;
331 | long matches = 0;
332 | try
333 | {
334 | long counter = 0;
335 | string line = string.Empty;
336 | bool located = false;
337 |
338 | foreach (LogLine ll in this.Lines)
339 | {
340 | if (cumulative == false)
341 | {
342 | // Reset the match flag
343 | ll.SearchMatches.Clear();
344 | //ll.IsContextLine = false;
345 |
346 | ClearContextLine(ll.LineNumber, numContextLines);
347 | }
348 | else
349 | {
350 | if (ll.SearchMatches.Count > 0) {
351 | continue;
352 | }
353 | }
354 |
355 | line = this.GetLine(ll.LineNumber);
356 |
357 | located = false;
358 | switch (sc.Type)
359 | {
360 | case Global.SearchType.SubStringCaseInsensitive:
361 | if (line.IndexOf(sc.Pattern, 0, StringComparison.OrdinalIgnoreCase) > -1)
362 | {
363 | located = true;
364 | }
365 | break;
366 |
367 | case Global.SearchType.SubStringCaseSensitive:
368 | if (line.IndexOf(sc.Pattern, 0, StringComparison.Ordinal) > -1)
369 | {
370 | located = true;
371 | }
372 | break;
373 |
374 | case Global.SearchType.RegexCaseInsensitive:
375 | if (Regex.Match(line, sc.Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled) != Match.Empty)
376 | {
377 | located = true;
378 | }
379 | break;
380 |
381 | case Global.SearchType.RegexCaseSensitive:
382 | if (Regex.Match(line, sc.Pattern, RegexOptions.Compiled) != Match.Empty)
383 | {
384 | located = true;
385 | }
386 | break;
387 |
388 | default:
389 | break;
390 | }
391 |
392 | if (located == false)
393 | {
394 | ll.SearchMatches.Remove(sc.Id);
395 | }
396 | else
397 | {
398 | matches++;
399 | ll.SearchMatches.Add(sc.Id);
400 |
401 | if (numContextLines > 0)
402 | {
403 | this.SetContextLines(ll.LineNumber, numContextLines);
404 | }
405 | }
406 |
407 | if (counter++ % 50 == 0)
408 | {
409 | OnProgressUpdate((int)((double)counter / (double)this.Lines.Count * 100));
410 |
411 | if (ct.IsCancellationRequested)
412 | {
413 | cancelled = true;
414 | return;
415 | }
416 | }
417 | }
418 | }
419 | finally
420 | {
421 | DateTime end = DateTime.Now;
422 |
423 | OnProgressUpdate(100);
424 | OnSearchComplete(end - start, matches, 1, cancelled);
425 | }
426 | });
427 | }
428 |
429 | ///
430 | ///
431 | ///
432 | ///
433 | ///
434 | public void Export(string filePath, CancellationToken ct)
435 | {
436 | this.ExportToFile(this.Lines, filePath, ct);
437 | }
438 |
439 | ///
440 | ///
441 | ///
442 | ///
443 | ///
444 | ///
445 | public void Export(IEnumerable lines, string filePath, CancellationToken ct)
446 | {
447 | this.ExportToFile(lines, filePath, ct);
448 | }
449 | #endregion
450 |
451 | //public TabItem Initialise(string filePath)
452 | //{
453 | // //OLVColumn colLineNumber = ((OLVColumn)(new OLVColumn()));
454 | // //OLVColumn colText = ((OLVColumn)(new OLVColumn()));
455 |
456 | // //colLineNumber.Text = "Line No.";
457 | // //colLineNumber.Width = 95;
458 | // //colText.Text = "Data";
459 |
460 | // //colLineNumber.AspectGetter = delegate (object x)
461 | // //{
462 | // // if (((LogLine)x) == null)
463 | // // {
464 | // // return "";
465 | // // }
466 |
467 | // // return (((LogLine)x).LineNumber + 1);
468 | // //};
469 |
470 | // //colText.AspectGetter = delegate (object x)
471 | // //{
472 | // // if (((LogLine)x) == null)
473 | // // {
474 | // // return "";
475 | // // }
476 |
477 | // // return (this.GetLine(((LogLine)x).LineNumber));
478 | // //};
479 |
480 | // //FastObjectListView lv = new FastObjectListView();
481 |
482 | // //lv.AllColumns.Add(colLineNumber);
483 | // //lv.AllColumns.Add(colText);
484 | // //lv.AllowDrop = true;
485 | // //lv.AutoArrange = false;
486 | // //lv.CausesValidation = false;
487 | // //lv.CellEditUseWholeCell = false;
488 | // //lv.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
489 | // //colLineNumber,
490 | // //colText});
491 | // ////lv.ContextMenuStrip = this.contextMenu;
492 | // //lv.Dock = System.Windows.Forms.DockStyle.Fill;
493 | // //lv.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
494 | // //lv.FullRowSelect = true;
495 | // //lv.GridLines = true;
496 | // //lv.HasCollapsibleGroups = false;
497 | // //lv.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
498 | // //lv.HideSelection = false;
499 | // //lv.IsSearchOnSortColumn = false;
500 | // //lv.Location = new System.Drawing.Point(3, 3);
501 | // //lv.Margin = new System.Windows.Forms.Padding(4);
502 | // //lv.Name = "listLines0";
503 | // //lv.SelectColumnsMenuStaysOpen = false;
504 | // //lv.SelectColumnsOnRightClick = false;
505 | // //lv.SelectColumnsOnRightClickBehaviour = BrightIdeasSoftware.ObjectListView.ColumnSelectBehaviour.None;
506 | // //lv.ShowFilterMenuOnRightClick = false;
507 | // //lv.ShowGroups = false;
508 | // //lv.ShowSortIndicators = false;
509 | // //lv.Size = new System.Drawing.Size(1679, 940);
510 | // //lv.TabIndex = 1;
511 | // //lv.TriggerCellOverEventsWhenOverHeader = false;
512 | // //lv.UseCompatibleStateImageBehavior = false;
513 | // //lv.UseFiltering = true;
514 | // //lv.View = System.Windows.Forms.View.Details;
515 | // //lv.VirtualMode = true;
516 | // //lv.Tag = this.Guid;
517 | // //lv.FormatRow += new System.EventHandler(this.FormatRow);
518 |
519 | // ControlLog cl = new ControlLog();
520 |
521 | // this.List = cl;
522 |
523 | // TabItem tp = new TabItem
524 | // {
525 | // Content = cl,
526 | // //tp.Location = new System.Drawing.Point(4, 33);
527 | // //tp.Name = "tabPage" + this.Guid;
528 | // //tp.Padding = new System.Windows.Forms.Padding(3);
529 | // //tp.Size = new System.Drawing.Size(1685, 946);
530 | // //tp.TabIndex = 0;
531 | // Header = "Loading...",
532 | // //tp.UseVisualStyleBackColor = true;
533 | // //tp.Tag = this.Guid;
534 | // //
535 |
536 | // ToolTip = filePath
537 | // };
538 |
539 | // return tp;
540 | //}
541 |
542 | /////
543 | /////
544 | /////
545 | //public void SetContextMenu(ContextMenuStrip ctx)
546 | //{
547 | // this.List.ContextMenuStrip = ctx;
548 | //}
549 |
550 | /////
551 | /////
552 | /////
553 | /////
554 | /////
555 | //public void FormatRow(object sender, FormatRowEventArgs e)
556 | //{
557 | // //if (this.viewMode != Global.ViewMode.FilterHide)
558 | // //{
559 | // if ((LogLine)e.Model == null)
560 | // {
561 | // return;
562 | // }
563 |
564 | // if (((LogLine)e.Model).SearchMatches.Intersect(this.FilterIds).Any() == true)
565 | // {
566 | // e.Item.BackColor = highlightColour;
567 | // }
568 | // else if (((LogLine)e.Model).IsContextLine == true)
569 | // {
570 | // e.Item.BackColor = contextColour;
571 | // }
572 | // //}
573 | //}
574 |
575 | ///
576 | ///
577 | ///
578 | ///
579 | ///
580 | private void SetContextLines(long lineNumber, int numLines)
581 | {
582 | long temp = numLines;
583 | if (lineNumber < this.Lines.Count)
584 | {
585 | if (numLines + lineNumber > this.Lines.Count - 1)
586 | {
587 | temp = this.Lines.Count - lineNumber - 1;
588 | }
589 | for (int index = 1; index <= temp; index++)
590 | {
591 | this.Lines[(int)lineNumber + index].IsContextLine = true;
592 | }
593 | }
594 |
595 | if (lineNumber > 0)
596 | {
597 | if (lineNumber - numLines < 0)
598 | {
599 | temp = lineNumber;
600 | }
601 | for (int index = 1; index <= temp; index++)
602 | {
603 | this.Lines[(int)lineNumber - index].IsContextLine = true;
604 | }
605 | }
606 | }
607 |
608 | ///
609 | /// Clear the line that is the next after the farthest context
610 | /// line, so the flag is reset and we won't overwrite
611 | ///
612 | ///
613 | ///
614 | private void ClearContextLine(long lineNumber, int numLines)
615 | {
616 | long temp = numLines;
617 | if ((int)lineNumber + numLines + 1 < this.Lines.Count - 1)
618 | {
619 | this.Lines[(int)lineNumber + numLines + 1].IsContextLine = false;
620 | }
621 | }
622 |
623 | ///
624 | ///
625 | ///
626 | ///
627 | ///
628 | private void ExportToFile(IEnumerable lines, string filePath, CancellationToken ct)
629 | {
630 | Task.Run(() =>
631 | {
632 | DateTime start = DateTime.Now;
633 | bool cancelled = false;
634 | try
635 | {
636 | using (FileStream fs = new FileStream(filePath, FileMode.Create))
637 | {
638 | string lineStr = string.Empty;
639 | byte[] lineBytes;
640 | byte[] endLine = new byte[2] { 13, 10 };
641 |
642 | long counter = 0;
643 | foreach (LogLine ll in lines)
644 | {
645 | lineStr = this.GetLine(ll.LineNumber);
646 | lineBytes = Encoding.ASCII.GetBytes(lineStr);
647 | fs.Write(lineBytes, 0, lineBytes.Length);
648 | // Add \r\n
649 | fs.Write(endLine, 0, 2);
650 |
651 | if (counter++ % 50 == 0)
652 | {
653 | OnProgressUpdate((int)((double)counter / (double)Lines.Count * 100));
654 |
655 | if (ct.IsCancellationRequested)
656 | {
657 | cancelled = true;
658 | return;
659 | }
660 | }
661 | }
662 |
663 | }
664 | }
665 | finally
666 | {
667 | DateTime end = DateTime.Now;
668 |
669 | OnProgressUpdate(100);
670 | OnExportComplete(end - start, cancelled);
671 | }
672 | });
673 | }
674 |
675 | ///
676 | ///
677 | ///
678 | ///
679 | ///
680 | private void AddLine(long offset, int charCount)
681 | {
682 | LogLine ll = new LogLine();
683 | // ll.lf = this;
684 | ll.Offset = offset;
685 | ll.CharCount = charCount;
686 | ll.LineNumber = this.LineCount;
687 | this.Lines.Add(ll);
688 | if (charCount > this.LongestLine.CharCount)
689 | {
690 | this.LongestLine.CharCount = charCount;
691 | this.LongestLine.LineNumber = ll.LineNumber;
692 | }
693 |
694 | this.LineCount++;
695 | }
696 |
697 | ///
698 | ///
699 | ///
700 | ///
701 | ///
702 | public string GetLine(int lineNumber)
703 | {
704 | if (lineNumber >= this.Lines.Count)
705 | {
706 | return string.Empty;
707 | }
708 |
709 | byte[] buffer = new byte[this.Lines[lineNumber].CharCount + 1];
710 | try
711 | {
712 | this.readMutex.WaitOne();
713 | this.fileStream.Seek(this.Lines[lineNumber].Offset, SeekOrigin.Begin);
714 | this.fileStream.Read(buffer, 0, this.Lines[lineNumber].CharCount);
715 | this.readMutex.ReleaseMutex();
716 | }
717 | catch (Exception){}
718 |
719 | return Regex.Replace(Encoding.ASCII.GetString(buffer), "[\0-\b\n\v\f\x000E-\x001F\x007F-ÿ]", "", RegexOptions.Compiled);
720 | }
721 |
722 | #region Event Methods
723 | ///
724 | ///
725 | ///
726 | private void OnLoadError(string message)
727 | {
728 | LoadError?.Invoke(this.FileName, message);
729 | }
730 |
731 | ///
732 | ///
733 | ///
734 | private void OnProgressUpdate(int progress)
735 | {
736 | ProgressUpdate?.Invoke(progress);
737 | }
738 |
739 | ///
740 | ///
741 | ///
742 | private void OnLoadComplete(TimeSpan duration, bool cancelled)
743 | {
744 | LoadComplete?.Invoke(this, this.FileName, duration, cancelled);
745 | }
746 |
747 | ///
748 | ///
749 | ///
750 | private void OnExportComplete(TimeSpan duration, bool cancelled)
751 | {
752 | ExportComplete?.Invoke(this, this.FileName, duration, cancelled);
753 | }
754 |
755 | ///
756 | ///
757 | ///
758 | private void OnSearchComplete(TimeSpan duration, long matches, int numTerms, bool cancelled)
759 | {
760 | SearchComplete?.Invoke(this, this.FileName, duration, matches, numTerms, cancelled);
761 | }
762 | #endregion
763 | }
764 | }
765 |
--------------------------------------------------------------------------------
/Source/LogLine.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace LogViewer2
5 | {
6 | ///
7 | ///
8 | ///
9 | public class LogLine
10 | {
11 | #region Member Variables/Properties
12 | public ControlLog Control { get; set; }
13 | public int LineNumber { get; set; } = 0;
14 | public int CharCount { get; set; } = 0;
15 | public long Offset { get; set; } = 0;
16 | public List SearchMatches { get; set; } = new List();
17 | public bool IsContextLine { get; set; } = false;
18 | #endregion
19 |
20 | #region Properties
21 | ///
22 | ///
23 | ///
24 | public string Data
25 | {
26 | get
27 | {
28 | return this.Control.GetLine(this.LineNumber);
29 | }
30 | }
31 |
32 | ///
33 | ///
34 | ///
35 | public string BackgroundColor
36 | {
37 | get
38 | {
39 | if (this.SearchMatches.Intersect(Control.FilterIds).Any() == true)
40 | {
41 | return "LimeGreen";//System.Windows.Media.Brush. .FromRgb(66, 245, 105);
42 | }
43 | else if (this.IsContextLine == true)
44 | {
45 | // return System.Windows.Media.Color.FromRgb(142, 145, 143);
46 | return "Gray";
47 | }
48 |
49 | return "White";//System.Windows.Media.Color.FromRgb(255, 255, 255);
50 | }
51 | }
52 | #endregion
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Source/LogViewer.toml:
--------------------------------------------------------------------------------
1 | HighlightColour = "Lime"
2 | ContextColour = "LightGray"
3 | MultiSelectLimit = 1000
4 | NumContextLines = 0
--------------------------------------------------------------------------------
/Source/LogViewer2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net7.0-windows
6 | enable
7 | true
8 | x64
9 | woanware ™️
10 |
11 | App.ico
12 | 1.0.1
13 | 1.0.1
14 | woanware
15 | https://github.com/woanware/LogViewer2
16 | readme.md
17 | 1.0.1
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | True
30 | \
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | True
48 | \
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Source/LogViewer2.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33103.184
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogViewer2", "LogViewer2.csproj", "{3CA6864B-0F21-4842-87AD-7D9EBC284E8F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {3CA6864B-0F21-4842-87AD-7D9EBC284E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {3CA6864B-0F21-4842-87AD-7D9EBC284E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {3CA6864B-0F21-4842-87AD-7D9EBC284E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {3CA6864B-0F21-4842-87AD-7D9EBC284E8F}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {C30CCCEE-4724-4381-875B-2AA0CCDC11BA}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Source/Methods.cs:
--------------------------------------------------------------------------------
1 | namespace LogViewer2
2 | {
3 | class Methods
4 | {
5 | public static string GetApplicationDirectory()
6 | {
7 | return System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Source/SearchCriteria.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace LogViewer2
3 | {
4 | ///
5 | ///
6 | ///
7 | public class SearchCriteria
8 | {
9 | public bool Enabled { get; set; } = true;
10 | public ushort Id { get; set; } = 0;
11 | public Global.SearchType Type { get; set; }
12 | public string Pattern { get; set; } = "";
13 | public long Matches { get; set; } = 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Source/Searches.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace LogViewer2
5 | {
6 | ///
7 | ///
8 | ///
9 | public class Searches
10 | {
11 | #region Member Variables
12 | public List Items { get; private set; }
13 | private ushort counter = 0; // Basically a counter
14 | #endregion
15 |
16 | ///
17 | ///
18 | ///
19 | public Searches()
20 | {
21 | Reset();
22 | }
23 |
24 | ///
25 | ///
26 | ///
27 | ///
28 | /// The Id of the search criteria object
29 | public ushort Add(SearchCriteria sc, bool cumulative)
30 | {
31 | if (cumulative == false)
32 | {
33 | Reset();
34 | }
35 |
36 | if (sc.Type == Global.SearchType.RegexCaseInsensitive || sc.Type == Global.SearchType.SubStringCaseInsensitive)
37 | {
38 | var ret = this.Items.SingleOrDefault(x => x.Pattern.ToLower() == sc.Pattern.ToLower());
39 | if (ret != null)
40 | {
41 | return 0;
42 | }
43 | }
44 | else
45 | {
46 | var ret = this.Items.SingleOrDefault(x => x.Pattern == sc.Pattern);
47 | if (ret != null)
48 | {
49 | return 0;
50 | }
51 | }
52 |
53 | counter++;
54 | sc.Enabled = true;
55 | sc.Id = counter;
56 | this.Items.Add(sc);
57 |
58 | return sc.Id;
59 | }
60 |
61 | ///
62 | ///
63 | ///
64 | ///
65 | /// The Id of the search criteria object
66 | public ushort Add(SearchCriteria sc)
67 | {
68 | // If the SearchCriteria already exists then just enable it
69 | if (sc.Type == Global.SearchType.RegexCaseInsensitive || sc.Type == Global.SearchType.SubStringCaseInsensitive)
70 | {
71 | var ret = this.Items.SingleOrDefault(x => x.Pattern.ToLower() == sc.Pattern.ToLower());
72 | if (ret != null)
73 | {
74 | ret.Enabled = true;
75 | return 0;
76 | }
77 | }
78 | else
79 | {
80 | var ret = this.Items.SingleOrDefault(x => x.Pattern == sc.Pattern);
81 | if (ret != null)
82 | {
83 | ret.Enabled = true;
84 | return 0;
85 | }
86 | }
87 |
88 | counter++;
89 | sc.Enabled = true;
90 | sc.Id = counter;
91 | this.Items.Add(sc);
92 |
93 | return sc.Id;
94 | }
95 |
96 | ///
97 | ///
98 | ///
99 | ///
100 | public void Reset()
101 | {
102 | counter = 0;
103 | this.Items = new List();
104 | }
105 |
106 | ///
107 | ///
108 | ///
109 | public int Count
110 | {
111 | get
112 | {
113 | return this.Items.Count;
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Source/WaitCursor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Input;
3 |
4 | namespace LogViewer2
5 | {
6 | ///
7 | ///
8 | ///
9 | public class WaitCursor : IDisposable
10 | {
11 | #region Member Variables
12 | private Cursor previousCursor;
13 | #endregion
14 |
15 | #region Constructor
16 | ///
17 | ///
18 | ///
19 | public WaitCursor()
20 | {
21 | this.previousCursor = Mouse.OverrideCursor;
22 | Mouse.OverrideCursor = Cursors.Wait;
23 | }
24 | #endregion
25 |
26 | #region IDisposable Members
27 | ///
28 | ///
29 | ///
30 | public void Dispose()
31 | {
32 | Mouse.OverrideCursor = previousCursor;
33 | }
34 |
35 | #endregion
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/WindowConfiguration.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Source/WindowConfiguration.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace LogViewer2
4 | {
5 | ///
6 | /// Interaction logic for WindowConfiguration.xaml
7 | ///
8 | public partial class WindowConfiguration : Window
9 | {
10 | #region Properties
11 | public Configuration Config { get; private set; }
12 | #endregion
13 |
14 | #region Constructor
15 | ///
16 | ///
17 | ///
18 | public WindowConfiguration(Configuration config)
19 | {
20 | InitializeComponent();
21 |
22 | this.Config = config;
23 | if (this.Config.NumContextLines > 0)
24 | {
25 | chkShowContext.IsChecked = true;
26 | cmbContextLines.SelectedIndex = this.Config.NumContextLines - 1;
27 | ChkShowContext_Checked(this, null);
28 | }
29 | else
30 | {
31 | chkShowContext.IsChecked = false;
32 | cmbContextLines.SelectedIndex = 0;
33 | ChkShowContext_Unchecked(this, null);
34 | }
35 | }
36 | #endregion
37 |
38 | #region Button Event Handlers
39 | ///
40 | ///
41 | ///
42 | ///
43 | ///
44 | private void BtnOk_Click(object sender, RoutedEventArgs e)
45 | {
46 | if (chkShowContext.IsChecked == true)
47 | {
48 | Config.NumContextLines = cmbContextLines.SelectedIndex + 1;
49 | }
50 | else
51 | {
52 | Config.NumContextLines = 0;
53 | }
54 |
55 | DialogResult = true;
56 | Close();
57 | }
58 | #endregion
59 |
60 | #region Checkbox Event Handlers
61 | ///
62 | ///
63 | ///
64 | ///
65 | ///
66 | private void ChkShowContext_Checked(object sender, RoutedEventArgs e)
67 | {
68 | cmbContextLines.IsEnabled = true;
69 | }
70 |
71 | ///
72 | ///
73 | ///
74 | ///
75 | ///
76 | private void ChkShowContext_Unchecked(object sender, RoutedEventArgs e)
77 | {
78 | cmbContextLines.IsEnabled = false;
79 | }
80 | #endregion
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Source/WindowGoToLine.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Source/WindowGoToLine.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace LogViewer2
4 | {
5 | ///
6 | /// Interaction logic for WindowGoToLine.xaml
7 | ///
8 | public partial class WindowGoToLine : Window
9 | {
10 | #region Properties
11 | public int LineNo { get; private set; }
12 | private int maxLines = 0;
13 | #endregion
14 |
15 | #region Constructor
16 | ///
17 | ///
18 | ///
19 | public WindowGoToLine(int maxLines)
20 | {
21 | InitializeComponent();
22 |
23 | textLine.Focus();
24 | this.maxLines = maxLines;
25 | }
26 | #endregion
27 |
28 | #region Button Event Handlers
29 | ///
30 | ///
31 | ///
32 | ///
33 | ///
34 | private void BtnOk_Click(object sender, RoutedEventArgs e)
35 | {
36 | if (textLine.Text.Trim().Length == 0)
37 | {
38 | MessageBox.Show("Line number must be entered", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
39 | textLine.Focus();
40 | return;
41 | }
42 |
43 | var isNumeric = int.TryParse(textLine.Text, out int lineNo);
44 |
45 | if (isNumeric == false)
46 | {
47 | MessageBox.Show("Line number value is invalid", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
48 | textLine.Focus();
49 | return;
50 | }
51 |
52 | if (lineNo > this.maxLines)
53 | {
54 | MessageBox.Show("Line number value is greater than the number of lines available", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
55 | textLine.Focus();
56 | return;
57 | }
58 |
59 | this.LineNo = lineNo;
60 | DialogResult = true;
61 | Close();
62 | }
63 | #endregion
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Source/WindowLine.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/WindowLine.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace LogViewer2
4 | {
5 | ///
6 | /// Interaction logic for WindowSearchTerms.xaml
7 | ///
8 | public partial class WindowLine : Window
9 | {
10 | #region Constructor
11 | ///
12 | ///
13 | ///
14 | public WindowLine(string line)
15 | {
16 | InitializeComponent();
17 |
18 | textLine.Text = line;
19 | }
20 | #endregion
21 |
22 | #region Button Event Handlers
23 | ///
24 | ///
25 | ///
26 | ///
27 | ///
28 | private void BtnOk_Click(object sender, RoutedEventArgs e)
29 | {
30 | DialogResult = true;
31 | Close();
32 | }
33 | #endregion
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Source/WindowMain.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Source/WindowMain.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Threading;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Interop;
9 | using Microsoft.Win32;
10 |
11 | namespace LogViewer2
12 | {
13 | ///
14 | /// Interaction logic for MainWindow.xaml
15 | ///
16 | public partial class WindowMain : Window
17 | {
18 | private CancellationTokenSource cancellationTokenSource;
19 | private WaitCursor waitCursor;
20 | private bool processing;
21 | private Color highlightColour = Color.Lime;
22 | private Color contextColour = Color.LightGray;
23 | private Configuration config;
24 |
25 | #region Window Event Handlers
26 | ///
27 | /// Constructor
28 | ///
29 | public WindowMain()
30 | {
31 | InitializeComponent();
32 | }
33 |
34 | ///
35 | ///
36 | ///
37 | ///
38 | ///
39 | private void Window_Loaded(object sender, RoutedEventArgs e)
40 | {
41 | this.config = new Configuration();
42 | string ret = this.config.Load();
43 | if (ret.Length > 0)
44 | {
45 | MessageBox.Show(ret, Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Error);
46 | }
47 |
48 | this.highlightColour = config.GetHighlightColour();
49 | this.contextColour = config.GetContextColour();
50 |
51 | mnuFileOpen.IsEnabled = false;
52 | mnuFileClose.IsEnabled = false;
53 | toolBtnSearch.IsEnabled = false;
54 | }
55 | #endregion
56 |
57 | #region Menu Event Handlers
58 | ///
59 | ///
60 | ///
61 | ///
62 | ///
63 | private void MnuFileOpenNew_Click(object sender, RoutedEventArgs e)
64 | {
65 | //LoadFile("access.log.txt", true);
66 | //LoadFile("access_log2", true);
67 |
68 | OpenFileDialog openFileDialog = new OpenFileDialog();
69 | openFileDialog.Filter = "All Files|*.*";
70 | openFileDialog.FileName = "*.*";
71 | openFileDialog.Title = "Select log file";
72 |
73 | if (openFileDialog.ShowDialog(this) == false)
74 | {
75 | return;
76 | }
77 |
78 | LoadFile(openFileDialog.FileName, true);
79 | }
80 |
81 | ///
82 | ///
83 | ///
84 | ///
85 | ///
86 | private void MnuFileOpen_Click(object sender, RoutedEventArgs e)
87 | {
88 | //LoadFile("access.log.txt", false);
89 |
90 | OpenFileDialog openFileDialog = new OpenFileDialog();
91 | openFileDialog.Filter = "All Files|*.*";
92 | openFileDialog.FileName = "*.*";
93 | openFileDialog.Title = "Select log file";
94 |
95 | if (openFileDialog.ShowDialog(this) == false)
96 | {
97 | return;
98 | }
99 |
100 | LoadFile(openFileDialog.FileName, false);
101 | }
102 |
103 | ///
104 | ///
105 | ///
106 | ///
107 | ///
108 | private void MnuFileClose_Click(object sender, RoutedEventArgs e)
109 | {
110 | if (tabMain.SelectedItem == null)
111 | {
112 | MessageBox.Show("Cannot identify current tab", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
113 | return;
114 | }
115 |
116 | TabItem ti = (TabItem)tabMain.SelectedItem;
117 | ti.Header = "...";
118 | ti.ToolTip = "";
119 | ControlLog cl = (ControlLog)ti.Content;
120 | cl.Dispose();
121 | }
122 |
123 | ///
124 | ///
125 | ///
126 | ///
127 | ///
128 | private void MnuToolsConfiguration_Click(object sender, RoutedEventArgs e)
129 | {
130 | WindowConfiguration wc = new WindowConfiguration(this.config);
131 | // Hack to set owner from a user control?!
132 | HwndSource source = HwndSource.FromVisual(this) as HwndSource;
133 | if (source != null)
134 | {
135 | WindowInteropHelper helper = new WindowInteropHelper(wc);
136 | helper.Owner = source.Handle;
137 | }
138 | if (wc.ShowDialog() == false)
139 | {
140 | return;
141 | }
142 | }
143 |
144 | ///
145 | ///
146 | ///
147 | ///
148 | ///
149 | private void MnuToolsMultiStringSearch_Click(object sender, RoutedEventArgs e)
150 | {
151 | ControlLog cl = (ControlLog)((TabItem)tabMain.SelectedItem).Content;
152 | cl.ShowMultiSearch();
153 |
154 | //LogFile lf = logs[tabControl.SelectedTab.Tag.ToString()];
155 |
156 | //using (FormSearch f = new FormSearch(lf.Searches))
157 | //{
158 | // DialogResult dr = f.ShowDialog(this);
159 | // if (dr == DialogResult.Cancel)
160 | // {
161 | // return;
162 | // }
163 |
164 | // // Clear any existing filter ID's as we will only show the multi-string search
165 | // lf.FilterIds.Clear();
166 | // lf.Searches.Reset();
167 | // foreach (SearchCriteria sc in f.NewSearches)
168 | // {
169 | // // Add the ID so that any matches show up straight away
170 | // lf.FilterIds.Add(sc.Id);
171 | // lf.Searches.Add(sc);
172 | // }
173 |
174 | // this.processing = true;
175 | // this.hourGlass = new HourGlass(this);
176 | // SetProcessingState(false);
177 | // statusProgress.Visible = true;
178 | // this.cancellationTokenSource = new CancellationTokenSource();
179 | // lf.SearchMulti(f.NewSearches, cancellationTokenSource.Token, config.NumContextLines);
180 | //}
181 | }
182 |
183 | ///
184 | ///
185 | ///
186 | ///
187 | ///
188 | private void MnuFileExit_Click(object sender, RoutedEventArgs e)
189 | {
190 | Application.Current.Shutdown();
191 | }
192 | #endregion
193 |
194 | #region Core Methods
195 | ///
196 | ///
197 | ///
198 | ///
199 | ///
200 | private void LoadFile(string filePath, bool newTab)
201 | {
202 | SetProcessingState(false);
203 | this.cancellationTokenSource = new CancellationTokenSource();
204 |
205 | if (newTab == true)
206 | {
207 | ControlLog cl = new ControlLog(this.config)
208 | {
209 | ViewMode = Global.ViewMode.Standard,
210 | };
211 |
212 | cl.ProgressUpdate += LogFile_LoadProgress;
213 | cl.LoadComplete += LogFile_LoadComplete;
214 | cl.SearchComplete += LogFile_SearchComplete;
215 | cl.ExportInitiated += LogFile_ExportInitiated;
216 | cl.ExportComplete += LogFile_ExportComplete; ;
217 | cl.LoadError += LogFile_LoadError;
218 | cl.MultiSearchInitiated += LogFile_MultiSearchInitiated;
219 | cl.DragEnter += LogFile_DragEnter;
220 | cl.Drop += LogFile_Drop;
221 |
222 | TabItem ti = new TabItem
223 | {
224 | Content = cl,
225 | Header = Path.GetFileName(filePath),
226 | Tag = cl.Guid,
227 | ToolTip = filePath
228 | };
229 |
230 | tabMain.Items.Add(ti);
231 | tabMain.SelectedIndex = tabMain.Items.Count - 1;
232 |
233 | cl.Load(filePath, cancellationTokenSource.Token);
234 | }
235 | else
236 | {
237 | if (tabMain.SelectedItem == null)
238 | {
239 | MessageBox.Show("Cannot identify current tab", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
240 | return;
241 | }
242 |
243 | ControlLog cl = (ControlLog)((TabItem)tabMain.SelectedItem).Content;
244 | ((TabItem)tabMain.SelectedItem).ToolTip = filePath;
245 | cl.Dispose();
246 | cl.Load(filePath, cancellationTokenSource.Token);
247 | }
248 | }
249 |
250 | ///
251 | ///
252 | ///
253 | ///
254 | private void SearchFile()
255 | {
256 | ControlLog cl = (ControlLog)((TabItem)tabMain.SelectedItem).Content;
257 |
258 | SearchCriteria sc = new SearchCriteria();
259 | sc.Type = (Global.SearchType)comboType.SelectedIndex;
260 | sc.Pattern = txtSearch.Text;
261 | sc.Id = cl.Searches.Add(sc, (bool)toolBtnCumulative.IsChecked);
262 |
263 | if (sc.Id == 0)
264 | {
265 | MessageBox.Show("Search pattern already exists", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
266 | return;
267 | }
268 |
269 | // Add the ID so that any matches show up straight away
270 | cl.FilterIds.Add(sc.Id);
271 |
272 | SetProcessingState(false);
273 | this.cancellationTokenSource = new CancellationTokenSource();
274 | cl.Search(sc, (bool)toolBtnCumulative.IsChecked, cancellationTokenSource.Token, config.NumContextLines);
275 | }
276 | #endregion
277 |
278 | #region Log File Event Handlers
279 | ///
280 | ///
281 | ///
282 | ///
283 | ///
284 | private void LogFile_ExportInitiated(ControlLog cl, bool all)
285 | {
286 | SaveFileDialog sfd = new SaveFileDialog();
287 | sfd.Filter = "All Files|*.*";
288 | sfd.FileName = "*.*";
289 | sfd.Title = "Select export file";
290 |
291 | if (sfd.ShowDialog() == false)
292 | {
293 | return;
294 | }
295 |
296 | this.cancellationTokenSource = new CancellationTokenSource();
297 | SetProcessingState(false);
298 |
299 | if (all == true)
300 | {
301 | cl.Export(sfd.FileName, cancellationTokenSource.Token);
302 | }
303 | else
304 | {
305 | cl.ExportSelected(sfd.FileName, cancellationTokenSource.Token);
306 | }
307 | }
308 |
309 | ///
310 | ///
311 | ///
312 | ///
313 | private void LogFile_MultiSearchInitiated(ControlLog cl, List searches)
314 | {
315 | this.cancellationTokenSource = new CancellationTokenSource();
316 | SetProcessingState(false);
317 |
318 | cl.SearchMulti(searches, cancellationTokenSource.Token, config.NumContextLines);
319 | }
320 |
321 | ///
322 | ///
323 | ///
324 | ///
325 | ///
326 | ///
327 | ///
328 | private void LogFile_ExportComplete(ControlLog cl, string fileName, TimeSpan duration, bool cancelled)
329 | {
330 | Dispatcher.Invoke(() =>
331 | {
332 | this.cancellationTokenSource.Dispose();
333 | SetProcessingState(true);
334 | UpdateStatusLabel("Export complete # Duration: " + duration + " (" + fileName + ")");
335 | });
336 | }
337 |
338 | ///
339 | ///
340 | ///
341 | ///
342 | ///
343 | ///
344 | ///
345 | private void LogFile_LoadComplete(ControlLog cl, string fileName, TimeSpan duration, bool cancelled)
346 | {
347 | Dispatcher.Invoke(() =>
348 | {
349 | // Update the UI
350 | cl.ItemsSource = cl.Lines;
351 | cl.UpdateColumnWidths();
352 | tabMain.UpdateLayout();
353 |
354 | // Enable the menu items that only apply if a file is open
355 | mnuFileClose.IsEnabled = true;
356 | mnuFileOpen.IsEnabled = true;
357 |
358 | UpdateStatusLabel(cl.Lines.Count + " Lines # Duration: " + duration + " (" + fileName + ")");
359 | this.cancellationTokenSource.Dispose();
360 | SetProcessingState(true);
361 |
362 | });
363 | }
364 |
365 | ///
366 | ///
367 | ///
368 | ///
369 | private void LogFile_LoadProgress(int percent)
370 | {
371 | Dispatcher.Invoke(() =>
372 | {
373 | statusProgressBar.Value = (int)percent;
374 | });
375 | }
376 |
377 | ///
378 | ///
379 | ///
380 | ///
381 | private void LogFile_LoadError(string fileName, string message)
382 | {
383 | MessageBox.Show(message + " (" + fileName + ")", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Error);
384 |
385 | Dispatcher.Invoke(() =>
386 | {
387 | this.cancellationTokenSource.Dispose();
388 | SetProcessingState(true);
389 |
390 | // Lets clear the LogFile state and set the UI correctly
391 | MnuFileClose_Click(this, null);
392 | });
393 | }
394 |
395 | ///
396 | ///
397 | ///
398 | private void LogFile_SearchComplete(ControlLog cl, string fileName, TimeSpan duration, long matches, int numTerms, bool cancelled)
399 | {
400 | Dispatcher.Invoke(() =>
401 | {
402 | // Update the list
403 | cl.ItemsSource = null;
404 | cl.ItemsSource = cl.Lines;
405 |
406 | UpdateStatusLabel("Matched " + matches + " lines (Search Terms: " + numTerms + ") # Duration: " + duration + " (" + fileName + ")");
407 | this.cancellationTokenSource.Dispose();
408 | SetProcessingState(true);
409 | });
410 | }
411 |
412 | ///
413 | ///
414 | ///
415 | ///
416 | ///
417 | private void LogFile_Drop(object sender, DragEventArgs e)
418 | {
419 | if (processing == true)
420 | {
421 | return;
422 | }
423 |
424 | string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
425 | if (files.Length == 0)
426 | {
427 | return;
428 | }
429 |
430 | if (files.Length > 1)
431 | {
432 | MessageBox.Show("Only one file can be processed at one time", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
433 | return;
434 | }
435 |
436 | LoadFile(files[0], false);
437 | }
438 |
439 | ///
440 | ///
441 | ///
442 | ///
443 | ///
444 | private void LogFile_DragEnter(object sender, DragEventArgs e)
445 | {
446 | if (processing == true)
447 | {
448 | return;
449 | }
450 |
451 | if (e.Data.GetDataPresent(DataFormats.FileDrop))
452 | {
453 | e.Effects = DragDropEffects.Move;
454 | }
455 | else
456 | {
457 | e.Effects = DragDropEffects.None;
458 | }
459 |
460 | e.Handled = true;
461 | }
462 | #endregion
463 |
464 | #region UI Methods
465 | ///
466 | ///
467 | ///
468 | ///
469 | private void SetProcessingState(bool enabled)
470 | {
471 | Dispatcher.Invoke(() =>
472 | {
473 | mnuFileOpenNew.IsEnabled = enabled;
474 | mnuFileOpen.IsEnabled = enabled;
475 | mnuFileClose.IsEnabled = enabled;
476 | mnuFileExit.IsEnabled = enabled;
477 | //menuToolsMultiStringSearch.IsEnabled = enabled;
478 | toolBtnCumulative.IsEnabled = enabled;
479 | toolBtnSearch.IsEnabled = enabled;
480 |
481 | if (enabled == true)
482 | {
483 | this.waitCursor.Dispose();
484 | this.processing = false;
485 | statusProgressBar.Visibility = Visibility.Hidden;
486 | }
487 | else
488 | {
489 | this.waitCursor = new WaitCursor();
490 | this.processing = true;
491 | statusProgressBar.Visibility = Visibility.Visible;
492 | }
493 |
494 | });
495 | }
496 |
497 | ///
498 | ///
499 | ///
500 | ///
501 | private void UpdateStatusLabel(string text)
502 | {
503 | Dispatcher.Invoke(() =>
504 | {
505 | statusLabelMain.Content = text;
506 | });
507 | }
508 | #endregion
509 |
510 | #region Toolbar Event Handlers
511 | ///
512 | ///
513 | ///
514 | ///
515 | ///
516 | private void toolBtnSearch_Click(object sender, RoutedEventArgs e)
517 | {
518 | if (comboType.SelectedIndex == -1)
519 | {
520 | MessageBox.Show("Type is not selected", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
521 | comboType.Focus();
522 | return;
523 | }
524 |
525 | SearchFile();
526 | }
527 | #endregion
528 | }
529 | }
530 |
--------------------------------------------------------------------------------
/Source/WindowMultiSearch.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Source/WindowMultiSearch.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Windows;
5 |
6 | namespace LogViewer2
7 | {
8 | ///
9 | /// Interaction logic for WindowMultiSearch.xaml
10 | ///
11 | public partial class WindowMultiSearch : Window
12 | {
13 | #region Member Variables
14 | public List Patterns { get; private set; } = new List();
15 | private Searches existingSearches;
16 | public List NewSearches { get; private set; } = new List();
17 | #endregion
18 |
19 | #region Constructor
20 | ///
21 | ///
22 | ///
23 | public WindowMultiSearch(Searches searches)
24 | {
25 | InitializeComponent();
26 |
27 | cmbSearchType.SelectedIndex = 0;
28 | this.existingSearches = searches;
29 | }
30 | #endregion
31 |
32 | #region Button Event Handlers
33 | ///
34 | ///
35 | ///
36 | ///
37 | ///
38 | private void BtnOk_Click(object sender, RoutedEventArgs e)
39 | {
40 | if (cmbSearchType.SelectedIndex == -1)
41 | {
42 | MessageBox.Show("Type is not selected", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
43 | cmbSearchType.Focus();
44 | return;
45 | }
46 |
47 | if (this.Patterns.Count == 0)
48 | {
49 | MessageBox.Show("No search patterns loaded", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
50 | listSearchTerms.Focus();
51 | return;
52 | }
53 |
54 | // Disable all existing SearchCriteria
55 | foreach (SearchCriteria sc in this.existingSearches.Items)
56 | {
57 | sc.Enabled = false;
58 | }
59 |
60 | bool exists = false;
61 | foreach (string p in this.Patterns)
62 | {
63 | SearchCriteria sc = new SearchCriteria();
64 | sc.Type = (Global.SearchType)cmbSearchType.SelectedIndex;
65 | sc.Pattern = p;
66 | sc.Enabled = true;
67 | sc.Id = this.existingSearches.Add(sc);
68 | //if (sc.Id == 0)
69 | //{
70 | // sc.Enabled = true;
71 | // exists = true;
72 | // continue;
73 | //}
74 |
75 | NewSearches.Add(sc);
76 | }
77 |
78 | if (exists == true)
79 | {
80 | MessageBox.Show("At least one pattern already exists and has not been added", Application.ResourceAssembly.GetName().Name, MessageBoxButton.OK, MessageBoxImage.Exclamation);
81 | }
82 |
83 | DialogResult = true;
84 | Close();
85 | }
86 |
87 | ///
88 | ///
89 | ///
90 | ///
91 | ///
92 | private void BtnImport_Click(object sender, RoutedEventArgs e)
93 | {
94 | OpenFileDialog openFileDialog = new OpenFileDialog();
95 | openFileDialog.Filter = "All Files|*.*";
96 | openFileDialog.FileName = "*.*";
97 | openFileDialog.Title = "Select file";
98 |
99 | if (openFileDialog.ShowDialog(this) == false)
100 | {
101 | return;
102 | }
103 |
104 | string line = string.Empty;
105 | using (StreamReader sr = new StreamReader(openFileDialog.FileName))
106 | {
107 | while ((line = sr.ReadLine()) != null)
108 | {
109 | this.Patterns.Add(line);
110 | }
111 | }
112 |
113 | listSearchTerms.ItemsSource = null;
114 | listSearchTerms.ItemsSource = this.Patterns;
115 | }
116 | #endregion
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Source/WindowSearchTerms.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Source/WindowSearchTerms.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace LogViewer2
4 | {
5 | ///
6 | /// Interaction logic for WindowSearchTerms.xaml
7 | ///
8 | public partial class WindowSearchTerms : Window
9 | {
10 | #region Member Variables/Properties
11 | public Searches Searches { get; private set; }
12 | #endregion
13 |
14 | #region Constructor
15 | ///
16 | ///
17 | ///
18 | public WindowSearchTerms(Searches searches)
19 | {
20 | InitializeComponent();
21 |
22 | listSearches.ItemsSource = searches.Items;
23 | this.Searches = searches;
24 | }
25 | #endregion
26 |
27 | #region Button Event Handlers
28 | ///
29 | ///
30 | ///
31 | ///
32 | ///
33 | private void BtnOk_Click(object sender, RoutedEventArgs e)
34 | {
35 | DialogResult = true;
36 | Close();
37 | }
38 | #endregion
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/help.md:
--------------------------------------------------------------------------------
1 | # Help
2 |
3 | The following provides a brief help guide for the core operations within LogViewer.
4 |
5 | ## Opening a file
6 |
7 | Either use the File->Open menu item or drag and drop a file onto the list
8 |
9 | ## Search
10 |
11 | - Enter the search term/pattern in the toolbar text box
12 | - Select the Search Type from the drop down
13 | - If you want the search to be performed on the lines currently displayed then ensure the **Cumulative** button is checked. If the button is not checked then the entire file will be searched, any existing search terms will also be removed
14 | - Next click the **Search** button (magnify glass icon)
15 |
16 | ### Search Types
17 |
18 | There are four different search types available:
19 |
20 | - Sub String Case Insensitive (Default)
21 | - Sub String Case Sensitive
22 | - Regex Case Insensitive
23 | - Regex Case Sensitive
24 |
25 | ### Search Terms
26 |
27 | By using the **Cumulative** search facility, there can be multiple search terms. Each of the terms can be enabled/disabled individually. The search terms can be accessed via the list context menu. When the **Search Terms** window is displayed, check or uncheck the search terms required, upon exiting the display will be refreshed.
28 |
29 | ### Multi-String Search
30 |
31 | To search for multiple strings in one search, use the Tools->Multi-String Search menu. Select the search type, then use the import button to import the search patterns. Any previous search results are cleared when using the multi-string search.
32 |
33 | ### Highlight Line Colour
34 |
35 | The search match highlight colour can be modified via the list context menu (Search->Colour->Match). The colour is persisted to the configuration file.
36 |
37 | ### Context Line Colour
38 |
39 | If the **Show context lines** option is enabled, then the context line colour can be modified via the list context menu (Search->Colour->Context). The colour is persisted to the configuration file.
40 |
41 | ## Filtering
42 |
43 | There are two modes for filtering; hide matched and show matched. Filtering and filter clearing is accessed via the list context menu.
44 |
45 | - **Show matched**: Hides all lines where there is not a search match; therefore only show the matched lines
46 | - **Hide matched**: Hides the lines that matched the search; therefore only show the lines that don't match
47 |
48 | ## Export
49 |
50 | The export functionality exports all lines within the current view or the selected lines. There is a maximum limit of 10000 lines. This functionality is accessed via the list content menu.
51 |
52 | ## Copy Line
53 |
54 | The selected line's contents can be copied to the clipboard via the list context menu. There is a maximum limit of 10000 lines
55 |
56 | ## Context Lines
57 |
58 | To show context around a match, the searches can be configured (Tools->Configuration) to show a user configurable number of lines before and after a search match.
59 |
60 | ## Go To Line
61 |
62 | The various line functions can be accessed via the context menu. For example to go to a specific line, right click and select the **Lines->Go To Line** context menu item. A window will be displayed, enter the line number and click OK.
63 |
64 |
65 |
--------------------------------------------------------------------------------
/help.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/help.pdf
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # LogViewer2
2 |
3 | LogViewer2 is a WPF rewrite of the LogViewer WinForms application. It is 2019 and I thought it was about time I embraced the **new** technology known as WPF, released in 2006 ;-)
4 |
5 | LogViewer2 is designed to work with any large text files, so that even very large files can be opened, viewed and searched.
6 |
7 | It's original use case is for DFIR cases that involve log analysis. Whilst I use grep (well actually I use [sift](https://github.com/svent/sift) to extract data from logs, it is handy to be able to view log files, search for terms, hide lines whilst you get an idea what the log file contains, what actions are being performed.
8 |
9 | I normally use a combination of various text editors, glogg, and the Mandiant Highlighter tool. The Mandiant Highlighter tool is great but hasn't been updated since 2011. It has lots of functionality, most of which I don't use. I wanted to implement my own log viewer and looked at the source code for Highlighter and realised it uses a custom owner drawn textbox, which is how it can work on large files.
10 |
11 | So that is how LogViewer was born, by design it is simpler, it doesn't have field operations, it doesn't have the line view etc.
12 |
13 | The use of the custom control would make debugging any future issues a lot harder, so after a bit of thought I used the [ObjectListView](http://objectlistview.sourceforge.net/cs/index.html) library. The ObjectListView library is a custom list view control for use with .Net projects, I have used it extensively as it is easy to use and works with large data sets.
14 |
15 | The core operation of LogViewer works in the same way as Highlighter e.g. parse the file, find the line offsets and line lengths, then when a line is needed for display, an existing file stream is used to seek to the offset, and then read X bytes.
16 |
17 | I tested the v0.0.1 release of LogViewer against v1.1.3 of Mandiant Highlighter. My test log file was 1.2 GB and had 4.4 million rows. The following shows the operation and duration of the operation to compare:
18 |
19 | - Load (LogViewer): 15s
20 | - Load (Highlighter): 42s
21 | - Search (LogViewer): 1m 5s
22 | - Search (Highlighter): 2m 15s
23 | - Show Only Highlighted (LogViewer): 2s (+ the search operation above 1m 5s) Total: 1m 7s
24 | - Show Only Highlighted (Highlighter): Killed after 35m
25 |
26 | The main reasons for this being faster is that it has removed some functionality and I have optimised the file load code so that there is less memory allocation and unnecessary checks/logic, plus Highlighter does some Md5 calcs etc.
27 |
28 | ## Features
29 |
30 | - Very fast
31 | - Supports huge files
32 | - Cumulative search
33 | - Can disable/enable search terms that are cumulative and the results are displayed instantly
34 | - Export current view
35 | - Show/Hide matched lines
36 | - Four search modes (Sub String Case Insensitive, Sub String Case Sensitive, Regex Case Insensitive, Regex Case Sensitive)
37 |
38 | ## General
39 |
40 | - To stop an action such as load, search, export, you double click on the progress bar, located in the status bar
41 | - The context menu holds the majority of actions
42 | - Lots of stuff to be fixed/added! :-)
43 |
44 | ## Screenshot
45 |
46 | 
47 |
48 | ## Third Party
49 |
50 | [ObjectListView](http://objectlistview.sourceforge.net/cs/index.html): Used for displaying the file lines
51 |
52 | [Nett](https://github.com/paiden/Nett): Used for the TOML configuration file reading/writing
53 |
54 | [icons8](https://icons8.com): Icons used within the application
--------------------------------------------------------------------------------
/readme.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woanware/LogViewer2/3c2260ec2373d5c9439f35e0302c7eef165b6bcc/readme.pdf
--------------------------------------------------------------------------------