Click here to Skip to main content
15,891,828 members
Articles / General Programming / Tools

Regex Logviewer

Rate me:
Please Sign up or sign in to vote.
4.97/5 (8 votes)
16 Jun 2013GPL38 min read 32.4K   2.2K   48  
Using the power of regex to parse your logs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Configuration;
using System.Linq;
using LogViewer.GUI;

namespace LogViewer
{

    public partial class MainForm : Form
    {
        public static string DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm:ss,fff";
        private TimeBuffer _refreshFilterTimeBuff = null;
        private EntryCard _frmCard = new EntryCard();
        PreFilterCard _frmBatchCollector = new PreFilterCard();
        public const int NUM_LATEST_FILES_TO_COLLECT = 1;
        private int _intUserSelectionKey = -1;
        private LogEngine _engine = new LogEngine();
        private bool _dateFilterActive = false;
        SortableBindingList<LogEntry> _gridModel = null;

        /// <summary>
        /// main form constructor
        /// </summary>
        public MainForm()
        {
            FindPositionRow = 0;
            _refreshFilterTimeBuff = new TimeBuffer(RefreshFilter, TimeSpan.FromMilliseconds(500));
            string strDateTimeFormat = ConfigurationManager.AppSettings["GridDateTimeFormat"];
            if (strDateTimeFormat != null)
                DATE_TIME_FORMAT = strDateTimeFormat;

            _engine.InitEngine();

            try
            {
                InitializeComponent();

                foreach (LogBehavior b in _engine.Behaviors)
                {
                    cmbBehaviors.Items.Add(b);
                }

                _gridModel = new SortableBindingList<LogEntry>(_engine.MainView);
                dataGridView1.DataSource = _gridModel;

                lblMemory.Text = "Used Ram: " + ((double)Process.GetCurrentProcess().WorkingSet64 / 1000000d).ToString(".00") + " MB";
                dataGridView1.AutoGenerateColumns = false;
                cmbBehaviors.SelectedItem = LogBehavior.AutoDetectBehaviour;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.StackTrace, "Error");
            }
        }

        /// <summary>
        /// adds a new file to the log viewer
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public bool AddFile(string file)
        {
            if (_engine.WatchedFiles.Contains(file))
                return true;

            if (file.Trim() == "")
                return true;
            LogBehavior behaviorForCurrentFile = _engine.ChosenBehavior;

            bool blnLiveListen = false;
            string strLiveListen = ConfigurationManager.AppSettings["LiveListeningOnByDefault"];
            if (strLiveListen != null && strLiveListen.Equals("true", StringComparison.InvariantCultureIgnoreCase))
                blnLiveListen = true;

            if (timer1.Enabled == false && blnLiveListen)
            {
                timer1.Enabled = true;
                timer1.Start();
            }

            if (!_engine.WatchedFiles.Contains(file))
            {

                behaviorForCurrentFile = _engine.FindCorrectBehaviorForFile(file);

                if (!_engine.IsAutoDetectMode)
                {
                    //_engine.ChosenBehavior = behaviorForCurrentFile;
                    cmbBehaviors.SelectedItem = _engine.ChosenBehavior;

                    if (_engine.ChosenBehavior != null)
                    {
                        //set the grid columns
                        dataGridView1.Columns.Clear();
                        _engine.ChosenBehavior.CreateGridCols(dataGridView1);

                        //set the default error level filter to initialize display.
                        SetDefaultLogLevel();
                    }
                }
                //if there was no chosen behavior, return false
                if (behaviorForCurrentFile == null)
                {
                    OpenNotepad(file);
                    return false;
                }

                //parse the log file (using the chosen behaviour's regexp)
                _engine.AddLogFile(file);
            }

            if (!lstFiles.Items.Contains(file))
                lstFiles.Items.Add(file);

            lblCount.Text = "Total Count: " + _engine.MainView.Count;
            lblMemory.Text = "Used Ram: " + ((double)Process.GetCurrentProcess().WorkingSet64 / 1000000d).ToString(".00") + " MB";
            RefreshFilter();

            //success
            return true;
        }

        /// <summary>
        /// set the default error level filter to initialize display.
        /// </summary>
        private void SetDefaultLogLevel()
        {
            string strDefaultLevel = ConfigurationManager.AppSettings["DefaultLogLevel"];
            SetLogLevel(strDefaultLevel);
        }

        /// <summary>
        /// sets the given log level in the combobox
        /// </summary>
        /// <param name="strDefaultLevel"></param>
        private void SetLogLevel(string strDefaultLevel)
        {
            if (!string.IsNullOrEmpty(strDefaultLevel))
            {
                for (int i = 0; i < cmbLevel.Items.Count; ++i)
                {
                    string item = (string)cmbLevel.Items[i];
                    if (item.Equals(strDefaultLevel, StringComparison.InvariantCultureIgnoreCase))
                        cmbLevel.SelectedIndex = i;
                }
                cmbBehaviors_SelectedIndexChanged(null, null);
            }
        }

        /// <summary>
        /// creates and opens a Log Entry card for the selected line in grid
        /// </summary>
        /// <param name="intRowIndex"></param>
        private void ShowLineCard(int intRowIndex)
        {
            if (intRowIndex == -1)
                return;

            List<LogEntry> rowList = new List<LogEntry>();
            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                LogEntry drRow = (LogEntry)row.DataBoundItem;
                rowList.Add(drRow);
            }

            _frmCard.Init(rowList, intRowIndex);

            DialogResult res = _frmCard.ShowDialog();
            this.TopMost = true;
            this.TopMost = false;
        }

        /// <summary>
        /// reads the filter UI components and creates a LogFilter object
        /// </summary>
        /// <returns></returns>
        private LogFilter CreateLogFilter()
        {
            LogFilter filter = new LogFilter();
            if (_dateFilterActive)
            {
                filter.From = dtpFrom.Value;
                filter.To = dtpTo.Value;
            }
            filter.Thread = txtThread.Text;
            filter.User = txtUser.Text;
            filter.TextFilter = txtFilter.Text;
            filter.Level = cmbLevel.Text;
            return filter;
        }

        private void RefreshFilter(object obj)
        {
            RefreshFilter();
        }

        /// <summary>
        /// filters the data and sets the new view into the grid
        /// </summary>
        private void RefreshFilter()
        {
            _engine.RefreshFilter(CreateLogFilter());
            _gridModel.ResetBindings();
            _gridModel.ReSort();
            MarkSelectedRowInGrid();
            //UpdateGridView();

            lblCount.Text = "Total Count: " + _engine.MainView.Count;
            lblMemory.Text = "Used Ram: " + ((double)Process.GetCurrentProcess().WorkingSet64 / 1000000d).ToString(".00") + " MB";
        }

        /// <summary>
        /// brings the selected row into view when "keep selected row in view" is selected
        /// </summary>
        private void MarkSelectedRowInGrid()
        {
            if (!chkPinTrack.Checked)
                return;
            //!dgRow.Displayed && 
            foreach (DataGridViewRow dgRow in dataGridView1.Rows)
            {
                LogEntry drRow = (LogEntry)dgRow.DataBoundItem;
                if (drRow.Key == _intUserSelectionKey)
                {
                    if (/*!dgRow.Displayed && */ chkPinTrack.Checked)
                        dataGridView1.FirstDisplayedCell = dgRow.Cells[0];
                    dgRow.Selected = true;
                    break;
                }
            }
        }

        /// <summary>
        /// open notepad to be used in case the log is incompatible with logviewer (no parser)
        /// </summary>
        /// <param name="p_strLogFileName"></param>
        private void OpenNotepad(string p_strLogFileName)
        {
            //only open a file in notepad if it's a new file causing the problem...

            FRMVanishingAlert.ShowForm(2, "Wrong Log Format", "Not a known log format,\r\n\rOpening Notepad", "", "", 0, 0, true, FormStartPosition.Manual, false);

            string strWinDir = Environment.GetEnvironmentVariable("SystemRoot");
            Process.Start(strWinDir + "\\notepad.exe", p_strLogFileName);
            //this.Visible = false;
        }

        /// <summary>
        /// create a reg file to register logviewer as the default program to open .log files
        /// </summary>
        private void SetLogFileAssociaction()
        {
            string strDirectory = Path.GetDirectoryName(Application.ExecutablePath);
            string strFilePath = strDirectory + "\\file.reg";

            string fileContent =

            "Windows Registry Editor Version 5.00\r\n" +

            "[HKEY_CLASSES_ROOT\\.log]\r\n" +
            "@=\"logfile\"\r\n" +
            "[HKEY_CLASSES_ROOT\\.log\\PersistentHandler]\r\n" +

            "@=\"{5e941d80-bf96-11cd-b579-08002b30bfeb}\"\r\n" +
            "[HKEY_CLASSES_ROOT\\logfile]\r\n" +
            "[HKEY_CLASSES_ROOT\\logfile\\shell]\r\n" +
            "@=\"open\"\r\n" +
            "[HKEY_CLASSES_ROOT\\logfile\\shell\\open]\r\n" +
            "[HKEY_CLASSES_ROOT\\logfile\\shell\\open\\command]\r\n" +

            "@=\"" + Application.ExecutablePath.Replace("\\", "\\\\") + " \\\"%1\\\"\"\r\n" +
            "[HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.log]\r\n" +
            "\"Progid\"=\"logfile\"\r\n";

            FileStream file = new FileStream(strFilePath, FileMode.Create);

            byte[] bytes = Encoding.UTF8.GetBytes(fileContent);
            file.Write(bytes, 0, bytes.Length);
            file.Flush();
            file.Close();

            Process.Start(FindRegeditPath(), "/s \"" + strFilePath + "\"").WaitForExit();
        }

        /// <summary>
        /// find the path of regedit.exe
        /// </summary>
        /// <returns></returns>
        private string FindRegeditPath()
        {
            string strSystemDir = Environment.GetEnvironmentVariable("SystemRoot");
            string strFilePath = strSystemDir + @"\system32\regedt32.exe";
            if (File.Exists(strFilePath))
                return strFilePath;

            strFilePath = strSystemDir + @"\system32\regedit32.exe";
            if (File.Exists(strFilePath))
                return strFilePath;

            strFilePath = strSystemDir + @"\system32\regedit.exe";
            if (File.Exists(strFilePath))
                return strFilePath;

            return null;
        }

        /// <summary>
        /// a callback function that is called when files are dragged & dropped into logviewer
        /// </summary>
        /// <param name="e"></param>
        private void FilesDropped(DragEventArgs e)
        {
            e.Data.GetDataPresent("FileDrop", false);
            string[] files = (string[])e.Data.GetData("FileDrop", false);

            ProgressBarManager.ShowProgressBar(100);
            AddLogFiles(files);
            ProgressBarManager.CloseProgress();
        }

        /// <summary>
        /// adds log files as batch (progress bar is updated accordingly)
        /// </summary>
        /// <param name="files"></param>
        internal void AddLogFiles(string[] files)
        {
            long totalSize = 0;
            foreach (string file in files)
            {
                if (File.Exists(file) && !_engine.WatchedFiles.Contains(file))
                    totalSize += (new FileInfo(file)).Length;
            }

            ProgressBarManager.ClearProgress();
            ProgressBarManager.SetLableText("Adding Files:");
            ProgressBarManager.ShowProgressBar(totalSize);
            foreach (string file in files)
            {
                AddFile(file);
            }

            ProgressBarManager.CloseProgress();
            if (_engine.WatchedFiles.Count == 0) Application.Exit();
        }

        /// <summary>
        ///  drag & drop plumbing
        /// </summary>
        /// <param name="e"></param>
        private void HandleDragEnter(DragEventArgs e)
        {
            if (e.Data.GetDataPresent("FileDrop", false)) e.Effect = DragDropEffects.Copy;
            else e.Effect = DragDropEffects.None;
        }

        /// <summary>
        /// exports grid content to a .csv file
        /// </summary>
        /// <param name="csvFileName"></param>
        private void ExportToCsvFile(string csvFileName)
        {

            try
            {
                //export
                using (StreamWriter wr = new StreamWriter(File.OpenWrite(csvFileName), _engine.CurrentEncoding))
                {
                    //export headings
                    foreach (DataGridViewColumn col in dataGridView1.Columns)
                    {
                        //use quotes to wrap all lines (escaping the spaces and \n \r chars), and replace " with ""
                        wr.Write("\"" + col.Name.Replace("\"", "\"\"") + "\",");
                    }
                    wr.WriteLine();

                    //export data
                    foreach (DataGridViewRow row in dataGridView1.Rows)
                    {
                        foreach (DataGridViewColumn col in dataGridView1.Columns)
                        {
                            object val = row.Cells[col.Name].Value;
                            string strVal = val.ToString();
                            if (val is DateTime)
                            {
                                strVal = ((DateTime)val).ToString(DATE_TIME_FORMAT);
                            }

                            //use quotes to wrap all lines (escaping the spaces and \n \r chars), and replace " with ""
                            wr.Write("\"" + strVal.Trim("\n\r\t ".ToCharArray()).Replace("\"", "\"\"") + "\",");
                        }
                        wr.WriteLine();
                    }
                }
                MessageBox.Show("export done successfully!");
            }
            catch (Exception ex)
            {
                MessageBox.Show("a little problem sir.." + ex.Message + ex.StackTrace);
            }
        }

        /// <summary>
        /// collects multiple logs from several directories
        /// </summary>
        private void PerformBatchCollection()
        {
            if (!string.IsNullOrEmpty(_frmBatchCollector.BehaviorName))
            {
                var beh = _engine.GetBehaviorByName(_frmBatchCollector.BehaviorName);
                if (beh != null)
                    _engine.ChosenBehavior = beh;
            }

            if (_frmBatchCollector.LogDirectories.Count == 0)
            {
                _engine.GlobalLineFilter = _frmBatchCollector.CardsLineFilter;
            }

            ProgressBarManager.ShowProgressBar(100);
            foreach (string directory in _frmBatchCollector.LogDirectories)
                _engine.ProcessLogDirectory(_frmBatchCollector.ExcludeList, _frmBatchCollector.IncludeList,
                                            _frmBatchCollector.CardsLineFilter, _frmBatchCollector.History, directory);
            ProgressBarManager.CloseProgress();

            foreach (var file in _engine.WatchedFiles)
                if (!lstFiles.Items.Contains(file))
                    lstFiles.Items.Add(file);

            //perform a memory collection
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            lblCount.Text = "Total Count: " + _engine.MainView.Count;
            lblMemory.Text = "Used Ram: " + ((double)Process.GetCurrentProcess().WorkingSet64 / 1000000d).ToString(".00") + " MB";
            RefreshFilter();
        }

        #region eventHandlers

        private void generateCvsReport3GramToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //ask user for a filename
            saveFileDialog1.AddExtension = true;
            saveFileDialog1.DefaultExt = "csv";
            DialogResult res = saveFileDialog1.ShowDialog();

            //check that user chose a file
            if (res == DialogResult.Cancel)
                return;
            string csvFileName = saveFileDialog1.FileName;
            _engine.GenerateReport(csvFileName, ReportGenMethod.ByTrigram);
        }

        private void loadServerListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_frmBatchCollector.ShowDialog() == DialogResult.OK)
            {
                PerformBatchCollection();
            }
        }

        private void generateCsvReportToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //ask user for a filename
            saveFileDialog1.AddExtension = true;
            saveFileDialog1.DefaultExt = "csv";
            DialogResult res = saveFileDialog1.ShowDialog();

            //check that user chose a file
            if (res == DialogResult.Cancel)
                return;
            string csvFileName = saveFileDialog1.FileName;
            _engine.GenerateReport(csvFileName, ReportGenMethod.ByStringCompare);
        }

        private void cmbBehaviors_SelectedIndexChanged(object sender, EventArgs e)
        {
            LogBehavior b = (LogBehavior)cmbBehaviors.SelectedItem;
            dataGridView1.Columns.Clear();
            _engine.ChosenBehavior.CreateGridCols(dataGridView1);
            _engine.ReparseAllLogs(b);
            RefreshFilter();
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (!File.Exists(_engine.BehaviorConfigFileName))
                _engine.SaveBehaviorConfig();
        }

        private void addLogFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog1.Filter = "log files|*.log";
            DialogResult res = openFileDialog1.ShowDialog();
            if (res == DialogResult.Cancel)
                return;

            string file = openFileDialog1.FileName;

            if (File.Exists(file))
            {
                ProgressBarManager.ShowProgressBar(100);
                AddFile(file);
                ProgressBarManager.CloseProgress();
            }
        }

        private void dataGridView1_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)32 || e.KeyChar == '\r')
            {
                if (dataGridView1.SelectedRows.Count > 0)
                {
                    int intSelectionIndex = dataGridView1.SelectedRows[0].Index;
                    ShowLineCard(intSelectionIndex);
                }
            }
        }

        
        public int FindPositionRow { get; set; }
        public string FindPrevSearchQuery { get; set; }

        private void FindCallback(object searchQuery)
        {
            var search = (LogSearch)searchQuery;
            bool found = false;
            DataGridViewRow dgRow = null;
            int startPos = FindPositionRow;
            int endPos = dataGridView1.Rows.Count;
            int increment = 1;

            bool searchUp = false;
            if (search.Direction == LogSearch.SearchDirection.Up)
            {
                endPos = 0;
                increment = -1;
                searchUp = true;
            }

            //if this is the same query, look for it on another row
            if (FindPrevSearchQuery == search.Query)
                startPos += increment;

            FindPrevSearchQuery = search.Query;

            for (int i = startPos; searchUp ? (i > endPos) : (i < endPos); i += increment)
            {
                dgRow = dataGridView1.Rows[i];
                LogEntry ent = (LogEntry)dgRow.DataBoundItem;
                if (ent.Info.ToLower().Contains(search.Query.ToLower())
                    || ent.ErrorInfo.ToLower().Contains(search.Query.ToLower())
                    )
                {
                    FindPositionRow = i;
                    found = true;
                    break;
                }
            }

            if (!found)
                return;

            dgRow.Selected = true;
            if (!dgRow.Displayed)
                dataGridView1.FirstDisplayedCell = dgRow.Cells[0];
        }

        private void MainForm_DragEnter(object sender, DragEventArgs e)
        {
            HandleDragEnter(e);
        }

        private void lstFiles_DragEnter(object sender, DragEventArgs e)
        {
            HandleDragEnter(e);
        }

        private void dataGridView1_DragEnter(object sender, DragEventArgs e)
        {
            HandleDragEnter(e);
        }

        private void clearEntriesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _engine.ClearAllEntries();

            RefreshFilter();
        }

        private void stopLiveListeningToolStripMenuItem_Click(object sender, EventArgs e)
        {
            timer1.Stop();
        }

        private void startLiveListeningToolStripMenuItem_Click(object sender, EventArgs e)
        {
            timer1.Start();
        }

        private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            //save selected row's key
            LogEntry drRow = null;
            if (dataGridView1.SelectedRows.Count > 0)
            {
                drRow = (LogEntry)dataGridView1.SelectedRows[0].DataBoundItem;
                _intUserSelectionKey = drRow.Key;
            }
        }

        private void associateWithlogFilesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetLogFileAssociaction();
        }

        private void dataGridView1_DragDrop(object sender, DragEventArgs e)
        {
            FilesDropped(e);
        }

        private void MainForm_DragDrop(object sender, DragEventArgs e)
        {
            FilesDropped(e);
        }

        private void lstFiles_DragDrop(object sender, DragEventArgs e)
        {
            FilesDropped(e);
        }

        private void txtFilter_TextChanged(object sender, EventArgs e)
        {
            _refreshFilterTimeBuff.Restart();
        }

        private void dtpFrom_ValueChanged(object sender, EventArgs e)
        {
            _dateFilterActive = true;
            RefreshFilter();
        }

        private void dtpTo_ValueChanged(object sender, EventArgs e)
        {
            _dateFilterActive = true;
            RefreshFilter();
        }

        private void txtUser_TextChanged(object sender, EventArgs e)
        {
            _refreshFilterTimeBuff.Restart();
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            RefreshFilter();
        }

        private void removeFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _engine.RemoveLogFile((string)lstFiles.SelectedItem);
            lstFiles.Items.Remove(lstFiles.SelectedItem);
            RefreshFilter();
        }

        private void lstFiles_MouseDown(object sender, MouseEventArgs e)
        {
            int index = lstFiles.IndexFromPoint(e.Location);
            lstFiles.SelectedIndex = index;
        }

        private void reloadLastServerListToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _engine.Clear();
            lstFiles.Items.Clear();
            PerformBatchCollection();
        }

        private void closeAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _engine.Clear();
            lstFiles.Items.Clear();
            _engine.ChosenBehavior = null;
            RefreshFilter();
        }

        private void exportToCsvFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //ask user for a filename
            saveFileDialog1.AddExtension = true;
            saveFileDialog1.DefaultExt = "csv";
            DialogResult res = saveFileDialog1.ShowDialog();

            //check that user chose a file
            if (res == DialogResult.Cancel)
                return;
            string csvFileName = saveFileDialog1.FileName;
            ExportToCsvFile(csvFileName);

        }

        /// <summary>
        /// timer controls log files refresh
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            List<string> list = new List<string>();
            list.AddRange(_engine.WatchedFiles);
            List<LogEntry> filteredNewLines = new List<LogEntry>();
            foreach (string file in list)
            {
                _engine.GetNewLinesForFile(file, CreateLogFilter());


                if (filteredNewLines.Count > 0 && !chkPinTrack.Checked && dataGridView1.Rows.Count > 0)
                    dataGridView1.FirstDisplayedCell = dataGridView1.Rows[0].Cells[0];
            }

            if (filteredNewLines.Count > 0)
            {
                _gridModel.ResetBindings();
                _gridModel.ReSort();
                MarkSelectedRowInGrid();
            }

            lblCount.Text = "Total Count: " + _engine.MainView.Count;
            lblMemory.Text = "Used Ram: " + ((double)Process.GetCurrentProcess().WorkingSet64 / 1000000d).ToString(".00") + " MB";
        }


        private void UpdateGridView()
        {
            _gridModel = new SortableBindingList<LogEntry>(_engine.MainView);

            dataGridView1.DataSource = _gridModel;
            _gridModel.ResetBindings();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            _refreshFilterTimeBuff.Restart();
        }

        private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            int intRowIndex = e.RowIndex;
            //don't distrupt header operations
            ShowLineCard(intRowIndex);

        }

        private void dataGridView1_Sorted(object sender, EventArgs e)
        {
            MarkSelectedRowInGrid();
        }
        #endregion

        FrmFind _frmFinder = new FrmFind();
        private void dataGridView1_KeyUp(object sender, KeyEventArgs e)
        {
            if(_frmFinder .IsDisposed)
                _frmFinder = new FrmFind();

            if (_frmFinder.Visible)
                _frmFinder.Focus();

            if (e.Control && e.KeyCode == Keys.F)
            {
                _frmFinder.Initialize(FindCallback);
                _frmFinder.Show();
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior) Hp Software
Israel Israel
I've been all over the coding world since earning my degrees
have worked in c++ and java, finally setteling into c# about 6 years ago, where i spent a good amount of my time in Performance tweaking & memory debugging, as well as designing new solutions and hacking at old ones to stay in line.

Computers never cease to amaze me, and i'm glad to have found a field where i get paid to do what i enjoy.

I have been toying around with the idea of publishing stuff online for years, never actually getting around to it, so i still have a lot of stuff to write up, aside from all the other new stuff i'll get excited about, hope you'll like enjoy reading it as much as i enjoy writing.

linkedin
google plus

Comments and Discussions