Parse Log File - Reading from the end first - vb.net

Having some trouble with a log file I need to parse and display certain fields in a data grid view. The log file is a large tab delimited file and I can read it and parse the array easily. The issue is that I want to start at the END of the file and read backwards until I hit a certain value in one of the fields. Then I want to start reading forward again until it hits the end of the file. While it's reading to the end I want it to fill the data grid view with certain fields. Here's what I have so far -
Looking at the code - it needs to read from the end until the 4th field equals zero and then start reading forward again. I can also read from the end, saving the values to the data grid view as I go until I hit that 0 if that's easier.
Thanks for the help!
The other option would be to read the file and save it in reverse, line by line - the last line first, the 2nd to last line 2nd, etc. That would be really nice as I have another function that will need to read the file and it also needs to go from the end to the beginning.
Private Sub btnParseLog_Click(sender As Object, e As EventArgs) Handles btnParseLog.Click
Dim FileLocation As String = "BLAHBLAHBLAH"
Dim LogFile As String = "LOGFILE"
DataGridView1.ColumnCount = 3
DataGridView1.ColumnHeadersVisible = True
' Set the column header style.
Dim columnHeaderStyle As New DataGridViewCellStyle()
columnHeaderStyle.BackColor = Color.Beige
columnHeaderStyle.Font = New Font("Verdana", 10, FontStyle.Bold)
DataGridView1.ColumnHeadersDefaultCellStyle = columnHeaderStyle
DataGridView1.Columns(0).Name = "User"
DataGridView1.Columns(1).Name = "Number"
DataGridView1.Columns(2).Name = "Time"
Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser(LogFile)
MyReader.TextFieldType =
Microsoft.VisualBasic.FileIO.FieldType.Delimited
MyReader.Delimiters = New String() {vbTab}
Dim TotalLineCount As Integer = File.ReadAllLines(LogFile).Length
Dim currentRow As String()
Dim RowCount As Integer = 0
'Loop through all of the fields in the file.
'If any lines are corrupt, report an error and continue parsing.
While Not MyReader.EndOfData
Try
currentRow = MyReader.ReadFields()
RowCount = RowCount + 1
' Include code here to handle the row.
If RowCount = TotalLineCount Then
While Not currentRow(4).ToString = 0
DataGridView1.Rows.Add(currentRow(1).ToString, currentRow(4).ToString, currentRow(7).ToString)
RowCount = RowCount - 1
End While
End If
Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
MsgBox("Line " & ex.Message &
" is invalid. Skipping")
End Try
End While
End Using
End Sub

Related

StreamReader Only Reads and Outputs Last Line From MultiLine, Spaced Text File

I'm trying to read values from a text file and input them into an array from where I can assign them to text boxes. My text file has the first line as a title name (string/char) and all subsequent lines contain numbers:
There are multiple lines and each value is split by a white-space. My current code is:
If openFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
Dim openreader As System.IO.StreamReader = New System.IO.StreamReader(openFileDialog1.FileName)
Try
While Not openreader.EndOfStream
Dim currentline As String
currentline = openreader.ReadLine()
currentline = currentline.Trim(" "c)
Dim inputparts() As String = currentline.Split(" "c)
TextBox1.Text = inputparts(0)
TextBox2.Text = inputparts(1) 'This gives out of bounds error
TextBox3.Text = inputparts(2) 'This gives out of bounds error
TextBox4.Text = inputparts(3) 'This gives out of bounds error
End While
Catch Ex As Exception
MessageBox.Show("The file could not be read. The original error is: " & Ex.Message)
End Try
openreader.Close()
End If
The issue with this is that the array inputparts has an out of bounds error for anything higher than inputparts(0) and inputparts(0), which is the only element recorded, is always the very last number of the last line. I did not want to define the dimensions of inputparts() since I was my input file to have the freedom to have a range of different values.
Why is the array not recording any values other than the final one - is it because my currentline ends up being the last line - and how can I fix this? Any help would be appreciated!
One way to put the parts which come from the split into textboxes would be to have references to the textboxes in an array and set them from the array of items from the line.
With Math.Min we can be sure that if there are not enough items on the line then we don't try to set the text to something which doesn't exist.
Using openreader As StreamReader = New StreamReader(openFileDialog1.FileName)
Dim tb = {TextBox1, TextBox2, TextBox3, TextBox4}
Try
While Not openreader.EndOfStream
Dim currentline As String
currentline = openreader.ReadLine()
currentline = currentline.Trim(" "c)
Dim inputparts() As String = currentline.Split(" "c)
For i = 0 To Math.Min(tb.Length, inputparts.Length)
tb(i).Text = inputparts(i)
Next
End While
Catch ex As Exception
MessageBox.Show("The file could not be read. The original error is: " & ex.Message)
End Try
End Using
I used the Using statement because it makes sure that the file is closed even if an exception occurs.
If you add Imports System.IO at the very top of the code, you don't have to keep typing it in things like System.IO.StreamReader.

Creating multiple .txt files while restricting size of each

In my program, I collect bits of information on a massive scale, hundreds of thousands to millions of lines each. I am trying to limit each file I create to a certain size in order to be able to quickly open it and read the data. I am using a HashSet to collect all the data without duplicates.
Here's my code so far:
Dim Founds As HashSet(Of String)
Dim filename As String = (Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\Sorted_byKING\sorted" + Label4.Text + ".txt")
Using writer As New System.IO.StreamWriter(filename)
For Each line As String In Founds
writer.WriteLine(line)
Next
Label4.Text = Label4.Text + 1 'Increments sorted1.txt, sorted2.txt etc
End Using
So, my question is:
How do I go about saving, let's say 250,000 lines in a text file before moving to another one and adding the next 250,000?
First of all, do not use Labels to simply store values. You should use variables instead, that's what variables are for.
Another advice, always use Path.Combine to concatenate paths, that way you don't have to worry about if each part of the path ends with a separator character or not.
Now, to answer your question:
If you'd like to insert the text line by line, you can use something like:
Sub SplitAndWriteLineByLine()
Dim Founds As HashSet(Of String) 'Don't forget to initialize and fill your HashSet
Dim maxLinesPerFile As Integer = 250000
Dim fileNum As Integer = 0
Dim counter As Integer = 0
Dim filename As String = String.Empty
Dim writer As IO.StreamWriter = Nothing
For Each line As String In Founds
If counter Mod maxLinesPerFile = 0 Then
fileNum += 1
filename = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"Sorted_byKING\sorted{fileNum.ToString}.txt")
If writer IsNot Nothing Then writer.Close()
writer = New IO.StreamWriter(filename)
End If
writer.WriteLine(line)
counter += 1
Next
writer.Dispose()
End Sub
However, if you will be inserting the text from the HashSet as is, you probably don't need to write line by line, instead you can write each "bunch" of lines at once. You could use something like the following:
Sub SplitAndWriteAll()
Dim Founds As HashSet(Of String) 'Don't forget to initialize and fill your HashSet
Dim maxLinesPerFile As Integer = 250000
Dim fileNum As Integer = 0
Dim filename As String = String.Empty
For i = 0 To Founds.Count - 1 Step maxLinesPerFile
fileNum += 1
filename = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"Sorted_byKING\sorted{fileNum.ToString}.txt")
IO.File.WriteAllLines(filename, Founds.Skip(i).Take(maxLinesPerFile))
Next
End Sub

VB "Index was out of range, must be non-negative and less than the size of the collection." When trying to generate a random number more than once

So I'm trying to generate a random number on button click. Now this number needs to be between two numbers that are inside my text file with various other things all separated by the "|" symbol. The number is then put into the text of a textbox which is being created after i run the form. I can get everything to work perfectly once, but as soon as i try to generate a different random number it gives me the error: "Index was out of range, must be non-negative and less than the size of the collection." Here is the main code as well as the block that generates the textbox after loading the form. As well as the contents of my text file.
Private Sub generate()
Dim newrandom As New Random
Try
Using sr As New StreamReader(itemfile) 'Create a stream reader object for the file
'While we have lines to read in
Do Until sr.EndOfStream
Dim line As String
line = sr.ReadLine() 'Read a line out one at a time
Dim tmp()
tmp = Split(line, "|")
rows(lineNum).buybutton.Text = tmp(1)
rows(lineNum).buyprice.Text = newrandom.Next(tmp(2), tmp(3)) 'Generate the random number between two values
rows(lineNum).amount.Text = tmp(4)
rows(lineNum).sellprice.Text = tmp(5)
rows(lineNum).sellbutton.Text = tmp(1)
lineNum += 1
If sr.EndOfStream = True Then
sr.Close()
End If
Loop
End Using
Catch x As Exception ' Report any errors in reading the line of code
Dim errMsg As String = "Problems: " & x.Message
MsgBox(errMsg)
End Try
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
rows = New List(Of duplicate)
For dupnum = 0 To 11
'There are about 5 more of these above this one but they all have set values, this is the only troublesome one
Dim buyprice As System.Windows.Forms.TextBox
buyprice = New System.Windows.Forms.TextBox
buyprice.Width = textbox1.Width
buyprice.Height = textbox1.Height
buyprice.Left = textbox1.Left
buyprice.Top = textbox1.Top + 30 * dupnum
buyprice.Name = "buypricetxt" + Str(dupnum)
Me.Controls.Add(buyprice)
pair = New itemrow
pair.sellbutton = sellbutton
pair.amount = amounttxt
pair.sellprice = sellpricetxt
pair.buybutton = buybutton
pair.buyprice = buypricetxt
rows.Add(pair)
next
end sub
'textfile contents
0|Iron Sword|10|30|0|0
1|Steel Sword|20|40|0|0
2|Iron Shield|15|35|0|0
3|Steel Shield|30|50|0|0
4|Bread|5|10|0|0
5|Cloak|15|30|0|0
6|Tent|40|80|0|0
7|Leather Armour|50|70|0|0
8|Horse|100|200|0|0
9|Saddle|50|75|0|0
10|Opium|200|500|0|0
11|House|1000|5000|0|0
Not sure what else to add, if you know whats wrong please help :/ thanks
Add the following two lines to the start of generate():
Private Sub generate()
Dim lineNum
lineNum = 0
This ensures that you don't point to a value of lineNum outside of the collection.
I usually consider it a good idea to add
Option Explicit
to my code - it forces me to declare my variables, and then I think about their initialization more carefully. It helps me consider their scope, too.
Try this little modification.
I took your original Sub and changed a little bit take a try and let us know if it solve the issue
Private Sub generate()
Dim line As String
Dim lineNum As Integer = 0
Dim rn As New Random(Now.Millisecond)
Try
Using sr As New StreamReader(_path) 'Create a stream reader object for the file
'While we have lines to read in
While sr.Peek > 0
line = sr.ReadLine() 'Read a line out one at a time
If Not String.IsNullOrEmpty(line) And Not String.IsNullOrWhiteSpace(line) Then
Dim tmp()
tmp = Split(line, "|")
rows(lineNum).buybutton.Text = tmp(1)
rows(lineNum).buyprice.Text = rn.Next(CInt(tmp(2)), CInt(tmp(3))) 'Generate the random number between two values
rows(lineNum).amount.Text = tmp(4)
rows(lineNum).sellprice.Text = tmp(5)
rows(lineNum).sellbutton.Text = tmp(1)
lineNum += 1
End If
End While
End Using
Catch x As Exception ' Report any errors in reading the line of code
Dim errMsg As String = "Problems: " & x.Message
MsgBox(errMsg)
End Try
End Sub

Remove items from a listbox if it appears in another

I have 2 listbox's on my form. The first populates from an array and displays files names that relate to the value of a Date Time picker. When that item is double clicked it moves over to the 2nd list box, clears from the 1st and the relevant files are transferred from one directory to another. The problem I have is that as the population is part of the load event once the application is closed and then re-opened the files names appear in both listbox's.
Is there a way to say if the object appears in 1 textbox then it shouldn't appear in the other?
I've tried the following but re-opening still displays the object in both
Dim item As Object
For Each item In lstPlanned.Items
If lstProgress.Contains(item) Then
lstPlanned.Items.Remove(item)
End If
Next
For the 2nd listbox I'm using the following to populate it
For Each Dir As String In System.IO.Directory.GetDirectories(aMailbox)
Dim dirInfo As New System.IO.DirectoryInfo(Dir)
lstProgress.Items.Add(dirInfo.Name)
Full Load code as follows
Private Sub Main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim loaddate As String = Calendar.Value.ToString("dd/MM/yy")
ReDim AllDetail(0 To 0)
numfiles = 0
lstPlanned.Items.Clear()
Dim allfiles = lynxin.GetFiles("*.txt")
ReDim AllDetails(allfiles.Count)
lstProgress.Items.Clear()
lstPlanned.Items.Add("No Jobs Planned Today!")
lstPlanned.Enabled = False
For Each txtfi In (allfiles)
Dim allLines() As String = File.ReadAllLines(txtfi.FullName)
AllDetails(numfiles) = New FileDetail()
AllDetails(numfiles).uPath = Microsoft.VisualBasic.Left((txtfi.FullName), Len(txtfi.FullName) - 4)
AllDetails(numfiles).uFile = Path.GetFileNameWithoutExtension(txtfi.Name)
Dim line = allLines.Where(Function(x) (x.StartsWith("unitname="))).SingleOrDefault()
If line IsNot Nothing Then
AllDetails(numfiles).uName = line.Split("="c)(1)
End If
line = allLines.Where(Function(x) (x.StartsWith("unitcode="))).SingleOrDefault()
If line IsNot Nothing Then
AllDetails(numfiles).uCode = line.Split("="c)(1)
End If
line = allLines.Where(Function(x) (x.StartsWith("opername="))).SingleOrDefault()
If line IsNot Nothing Then
AllDetails(numfiles).uOps = line.Split("="c)(1)
End If
line = allLines.Where(Function(x) (x.StartsWith("plandate="))).SingleOrDefault()
If line IsNot Nothing Then
AllDetails(numfiles).uPlan = line.Split("="c)(1)
End If
line = allLines.Where(Function(x) (x.StartsWith("cliecode="))).SingleOrDefault()
If line IsNot Nothing Then
AllDetails(numfiles).uClient = line.Split("="c)(1)
End If
If AllDetails(numfiles).uPlan = loaddate Then
lstPlanned.Items.Remove("No Jobs Planned Today!")
lstPlanned.Enabled = True
lstPlanned.Items.Insert(0, AllDetails(numfiles).uName & " - " & AllDetails(numfiles).uCode & " - " & AllDetails(numfiles).uOps)
numfiles = numfiles + 1
End If
Next
For Each Dir As String In System.IO.Directory.GetDirectories(aMailbox)
Dim dirInfo As New System.IO.DirectoryInfo(Dir)
lstProgress.Items.Add(dirInfo.Name)
Dim item As Object
For Each item In lstPlanned.Items
If lstProgress.Contains(item) Then
lstPlanned.Items.Remove(item)
End If
Next
Next
End Sub
The Contains method of a listbox checks the controls collections not the items collection. It should have been lstProgress.Items.Contains(item). Also you can use GetDirectories of the DirectoryInfo class to get the directoryinfo objects directly.
Checking to see if lstPlanned contains each item as you add it to lstProgress will eliminate the extra loop, which wouldn't work right anyway, because you're not allowed to modify the iterated collection in a For Each loop.
I was looking over your code and noticed an improvement that could be made. Using the LINQ extension methods each you want to add a property value means a lot of extra iterating through each file line collection. Using select means you only iterate through the collection once.
For Each txtfi In (allfiles)
Dim allLines() As String = File.ReadAllLines(txtfi.FullName)
AllDetails(numfiles) = New FileDetail()
AllDetails(numfiles).uPath = Microsoft.VisualBasic.Left((txtfi.FullName), Len(txtfi.FullName) - 4)
AllDetails(numfiles).uFile = Path.GetFileNameWithoutExtension(txtfi.Name)
AllDetails(numfiles).uPlan = allLines.Where(Function(x) (x.StartsWith("plandate="))).SingleOrDefault().Split("="c)(1)
If AllDetails(numfiles).uPlan = loaddate Then
For Each line In allLines
If line Is Not Nothing Then
Dim fields As String() = line.Split("="c)
Select Case fields(0)
Case "unitname"
AllDetails(numfiles).uName = fields(1)
Case "unitcode"
AllDetails(numfiles).uCode = fields(1)
Case "opername"
AllDetails(numfiles).uOps = fields(1)
Case "plandate"
AllDetails(numfiles).uPlan = fields(1)
Case "cliecode"
AllDetails(numfiles).uClient = fields(1)
End Select
End If
Next
lstPlanned.Items.Remove("No Jobs Planned Today!")
lstPlanned.Enabled = True
lstPlanned.Items.Insert(0, AllDetails(numfiles).uName & " - " & AllDetails(numfiles).uCode & " - " & AllDetails(numfiles).uOps)
numfiles = numfiles + 1
End If
Next
Dim RootDir As New System.IO.DirectoryInfo(aMailbox)
For Each Dir As IO.DirectoryInfo In RootDir.GetDirectories
Dim item = Dir.Name
lstProgress.Items.Add(item)
If lstPlanned.Items.Contains(item) Then
lstPlanned.Items.Remove(item)
End If
Next
Thanks Tinstaafl
Your answer works a treat.
I also adapted your first edit to
Dim RootDir As New System.IO.DirectoryInfo(aMailbox)
For Each Dir As IO.DirectoryInfo In RootDir.GetDirectories
lstProgress.Items.Add(Dir.Name)
'item defaults to object, no need to explicitly declare it
For Each item In New System.Collections.ArrayList(lstPlanned.Items)
If lstProgress.Items.Contains(item) Then
lstPlanned.Items.Remove(item)
End If
Next
Next
amending the following line
For Each item In New System.Collections.ArrayList(lstPlanned.Items)
Thanks again

How do I get a loop if elseif is true?

I created an app that will browse through my favorite game's pages to find an online person by reading the html code on their profile page. However I am having trouble coming up with a way for it to loop back if it finds "UserOfflineMessage". I inserted ((((Location I want it to loop back)))) where I wanted it to loop back. Any suggestions?
BTW: This is not for malicious purposes, this is just a project a few of us were working on.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim Rlo As New IO.StreamReader(My.Resources.Preferences & "Preferences.txt")
Dim firstLine As String
'read first line
firstLine = Rlo.ReadLine()
'read secondline
TheText.Text = Rlo.ReadLine()
'read third line
Dim thirdLine As String = Rlo.ReadLine()
Dim Ro As New IO.StreamReader(My.Resources.Preferences & "sig.txt")
Dim first1Line As String
'read first line
first1Line = Ro.ReadLine()
'read secondline
The2Text.Text = Ro.ReadLine()
((((Location I want it to loop back))))
rndnumber = New Random
number = rndnumber.Next(firstLine, TheText.Text)
TextBox2.Text = ("http://www.roblox.com/User.aspx?ID=" & number.ToString)
WebBrowser2.Navigate(TextBox2.Text)
If WebBrowser2.DocumentText.Contains("[ Offline ]</span>") Then
check1 = 1
TextBox1.Text = ("http://www.roblox.com/My/NewMessage.aspx?recipientID=" & number.ToString)
WebBrowser1.Navigate(TextBox1.Text)
MsgBox(":R")
ElseIf WebBrowser1.DocumentText.Contains("UserOfflineMessage") Then
MsgBox(":D")
End If
End Sub
You could use a Do...While loop:
Dim tryAgain as Boolean
...
Do
'...your stuff here...
tryAgain = False
if BlahBlah Then
...
ElseIf CaseWhereYouWantToLoop Then
tryAgain = True
End If
Loop While tryAgain
Change your ((((Location I want it to loop back)))) with
BackHere :
And add here
ElseIf WebBrowser1.DocumentText.Contains("UserOfflineMessage") Then
MsgBox(":D")
Goto BackHere