Why is each String being written to a different file? - vb.net

I am trying to generate a size-based list of files. The current size being passed is 10 MB worth of file-names per text file. Instead of it counting to 10 MB and then incrementing the version letter, it is writing each file-name to its own individual file. This is strange as each file is ~150 kb, but I cannot figure out why it is reporting total as > number every time the code loops.
Private Function GenerateListsForSize(source As String, destination As String, name As String, number As Integer)
Dim files As ArrayList = New ArrayList
Dim total As Integer
Dim version As Char = "A"
Dim path As String
Dim counter As Integer = 0
Dim passTexts As ArrayList = New ArrayList
Dim infoReader As System.IO.FileInfo
For Each foundFile As String In My.Computer.FileSystem.GetFiles(source)
files.Add(foundFile)
Next
If files.Count > 1 Then 'If files exist in dir, count them and get how many lists
path = destination & "\" & name & version & ".txt"
Dim fs As FileStream = File.Create(path) 'creates the first text file
fs.Close()
passTexts.Add(path)
For Each foundfile As String In files
Using sw As StreamWriter = New StreamWriter(path)
Console.WriteLine(foundfile)
sw.WriteLine(foundfile)
End Using
infoReader = My.Computer.FileSystem.GetFileInfo(foundfile)
total = total + infoReader.Length
If total >= number Then 'If max file size is reached
version = Chr(Asc(version) + 1) 'Increments Version
path = destination & "\" & name & version & ".txt" 'Corrects path
fs = File.Create(path) 'creates the new text file with updated path
fs.Close()
passTexts.Add(path)
total = 0 'resets total
End If
Next
End If
Return passTexts
End Function

Every time through the loop, you open the file (using the StreamWriter) which overwrites the previous contents. Your file will only ever have one filename inside it. Instead of opening and writing every time through the loop, only write the file when you have accumulated all the filenames. I removed the calls to File.Create as they aren't necessary. The StreamWriter will create the file if it doesn't exist. And I changed the ArrayList's to List(Of String) since they're easier to work with. Also, be sure to turn Option Strict On. This code has not been tested, but it should get my point across. I hope I haven't misunderstood what you were trying to do.
Private Function GenerateListsForSize(source As String, destination As String, name As String, number As Integer) As List(Of String)
Dim files As New List(Of String)()
Dim filenamesToWrite As New List(Of String)()
Dim total As Integer
Dim version As Char = "A"
Dim filename As String
Dim counter As Integer = 0
Dim passTexts As New List(Of String)()
Dim infoReader As System.IO.FileInfo
files.AddRange(My.Computer.FileSystem.GetFiles(source))
If files.Count > 1 Then 'If files exist in dir, count them and get how many lists
'Path.Combine is preferable to concatenating strings.
filename = Path.Combine(destination, String.Format("{0}{1}.txt", name, version))
passTexts.Add(filename)
For Each foundfile As String In files
filenamesToWrite.Add(foundfile)
infoReader = My.Computer.FileSystem.GetFileInfo(foundfile)
total = total + infoReader.Length
If total >= number Then 'If max file size is reached
'Only write when the list is complete for this batch.
Using sw As StreamWriter = New StreamWriter(filename)
For Each fname As String In filenamesToWrite
Console.WriteLine(foundfile)
sw.WriteLine(foundfile)
Next
End Using
version = Chr(Asc(version) + 1) 'Increments Version
filename = Path.Combine(destination, String.Format("{0}{1}.txt", name, version)) 'corrects path
passTexts.Add(filename) 'IS THIS A DUPLICATE????
total = 0 'resets total
filenamesToWrite.Clear() 'clear the list of file names to write
End If
Next
End If
Return passTexts
End Function

Related

How to search multiple text files in a directory for a string of text at once

I have a ListBox with a certain amount of items in it.
For each item in the ListBox a corresponding text file exists in the file directory.
I need to search each text file (based on what's in the ListBox) for a persons name. Each text file may contain the name or it may not.
I would then like a return which text file contains the name.
I have tried this as a way to search a text file: it works, but I'm not sure how to get this to repeat based on whats in a ListBox.
Dim sFileContents As String = String.Empty
If (System.IO.File.Exists((Application.StartupPath) & "\Project_Green.txt")) Then
sFileContents = (System.IO.File.ReadAllText((Application.StartupPath) & "\Project_Green.txt"))
End If
If sFileContents.Contains(TextBox4.Text) Then
MessageBox.Show("yup")
Else
MessageBox.Show("nope")
End If
Also, if it would be possible to ignore case that would be great.
If you have a bunch of files in a directory and you have their names in a ListBox, and you want to search their contents for something.
One liner query:
Imports System.IO
'...
Sub TheCaller()
Dim dir = My.Application.Info.DirectoryPath
Dim ext = ".txt" ' If the extensions are trimmed in the list.
Dim find = TextBox4.Text
Dim files = Directory.EnumerateFiles(dir).Where(Function(x) ListBox1.Items.Cast(Of String).
Any(Function(y) String.Concat(y, ext).
Equals(Path.GetFileName(x),
StringComparison.InvariantCultureIgnoreCase) AndAlso File.ReadLines(x).
Any(Function(z) z.IndexOf(find, StringComparison.InvariantCultureIgnoreCase) >= 0))).ToList
ListBox2.Items.Clear()
ListBox2.Items.AddRange(files.Select(Function(x) Path.GetFileNameWithoutExtension(x)).ToArray)
End Sub
Or if you prefer the For Each loop:
Sub Caller()
Dim dir = My.Application.Info.DirectoryPath
Dim find = TextBox4.Text
Dim files As New List(Of String)
For Each f As String In ListBox1.Items.Cast(Of String).
Select(Function(x) Path.Combine(dir, $"{x}.txt"))
If File.Exists(f) AndAlso
File.ReadLines(f).Any(Function(x) x.IndexOf(find,
StringComparison.InvariantCultureIgnoreCase) <> -1) Then
files.Add(f)
End If
Next
ListBox2.Items.Clear()
ListBox2.Items.AddRange(files.Select(Function(x) Path.GetFileNameWithoutExtension(x)).ToArray)
End Sub
Either way, the files list contains the matches if any.
Plus a pseudo-parallel async method, for the very heavy-duty name searches.
The Async Function SearchNameInTextFiles returns a named Tuple:
(FileName As String, Index As Integer)
where FileName is the file parsed and Index is the position where the first occurrence of the specified name (theName) was found.
If no matching sub-string is found, the Index value is set to -1.
The caseSensitive parameter allows to specify whether the match should be, well, case sensitive.
You can start the search from a Button.Click async handler (or similar), as shown here.
Imports System.IO
Imports System.Threading.Tasks
Private Async Sub btnSearchFiles_Click(sender As Object, e As EventArgs) Handles btnSearchFiles.Click
Dim filesPath = [Your files path]
Dim theName = textBox4.Text ' $" {textBox4.Text} " to match a whole word
Dim ext As String = ".txt" ' Or String.Empty, if extension is already included
Dim tasks = ListBox1.Items.OfType(Of String).
Select(Function(f) SearchNameInTextFiles(Path.Combine(filesPath, f & ext), theName, False)).ToList()
Await Task.WhenAll(tasks)
Dim results = tasks.Where(Function(t) t.Result.Index >= 0).Select(Function(t) t.Result).ToList()
results.ForEach(Sub(r) Console.WriteLine($"File: {r.FileName}, Position: {r.Index}"))
End Sub
Private Async Function SearchNameInTextFiles(filePath As String, nameToSearch As String, caseSensitive As Boolean) As Task(Of (FileName As String, Index As Integer))
If Not File.Exists(filePath) then Return (filePath, -1)
Using reader As StreamReader = New StreamReader(filePath)
Dim line As String = String.Empty
Dim linesLength As Integer = 0
Dim comparison = If(caseSensitive, StringComparison.CurrentCulture,
StringComparison.CurrentCultureIgnoreCase)
While Not reader.EndOfStream
line = Await reader.ReadLineAsync()
Dim position As Integer = line.IndexOf(nameToSearch, comparison)
If position > 0 Then Return (filePath, linesLength + position)
linesLength += line.Length
End While
Return (filePath, -1)
End Using
End Function
You can do these simple steps for your purpose:
First get all text files in the application's startup directory.
Then iterate over all names in the ListBox and for each one, search in all text files to find the file that contains that name.
To make the process case-insensitive, we first convert names and text file's contents to "lower case" and then compare them. Here is the full code:
Private Sub findTextFile()
'1- Get all text files in the directory
Dim myDirInfo As New IO.DirectoryInfo(Application.StartupPath)
Dim allTextFiles As IO.FileInfo() = myDirInfo.GetFiles("*.txt")
'2- Iterate over all names in the ListBox
For Each name As String In ListBox1.Items
'Open text files one-by-one and find the first text file that contains this name
Dim found As Boolean = False 'Changes to true once the name is found in a text file
Dim containingFile As String = ""
For Each file As IO.FileInfo In allTextFiles
If System.IO.File.ReadAllText(file.FullName).ToLower.Contains(name.ToLower) Then 'compares case-insensitive
found = True
containingFile = file.FullName
Exit For
End If
Next
'Found?
If found Then
MsgBox("The name '" + name + "' found in:" + vbNewLine + containingFile)
Else
MsgBox("The name '" + name + "' does not exist in any text file.")
End If
Next
End Sub

Copy directories and files with ProgressBar

Trying to create a console application to copy directories from the source to the destination and either the progress bar does nothing while files are copied...
My.Computer.FileSystem.CopyDirectory(source, destination)
For i = 1 To 100
Console.Write(String.Format("Copy progress: {0}%" & vbCr, i))
Threading.Thread.Sleep(100)
Next
or the ProgressBar says "Copy Progress 1%" the entire time it's copying...
For i = 1 To 100
Console.Write(String.Format("Copy progress: {0}%" & vbCr, i))
My.Computer.FileSystem.CopyDirectory(source, destination)
Threading.Thread.Sleep(100)
Next
Wondering what I am doing wrong because I am obviously putting the My.Computer line in the wrong spot!
A simple solution, using Linq Select to copy the file list returned by DirectoryInfo.GetFiles()
Pass the sample method an array of Source Directories and a Destination Directory.
The progress (0-100%) is printed to the Output window, and a ProgressBar gives a visual feedback of the copy status for each Source Path.
This method will return the list of all files copied.
Dim sourcePath As String() = New String() {"[SourcePath1]", "[SourcePath2]", "[SourcePath3]"}
Dim destinationPath As String = "[DestinationPath]"
Dim filesCopied As List(Of String) = CopyDirectoryWithProgress(sourcePath, destinationPath)
Console.ReadLine()
Private Function CopyDirectoryWithProgress(sourcePath As String(), destPath As String) As List(Of String)
Dim allFilesCopied As List(Of String) = New List(Of String)
Dim progressBarPassSymbol As Char = ChrW(&H25A0)
Dim progressBarEmptySymbol As String = New String(ChrW(&H2014), 30)
For Each sPath As String In sourcePath
Dim fileInfo As New DirectoryInfo(sPath).GetFiles()
Dim numberOfFiles As Integer = fileInfo.Length - 1
Dim progressBarPass As Double = (30 / numberOfFiles)
Dim increment As Double = 100 / numberOfFiles
Directory.CreateDirectory(destPath)
Console.CursorLeft = 0
Console.Write("Copy progress: ")
Console.CursorLeft = 20
Console.Write(progressBarEmptySymbol)
allFilesCopied.AddRange(fileInfo.
Select(Function(f, i)
File.Copy(Path.Combine(sPath, f.Name), Path.Combine(destPath, f.Name), True)
Console.CursorLeft = 15
Console.Write("{0:g}% " &
New String(progressBarPassSymbol, CInt((i + 1) * progressBarPass)),
CInt((i + 1) * increment))
Return f.FullName
End Function))
Console.WriteLine()
Next
Return allFilesCopied
End Function
For an interesting method to perform this task with a much faster file enumerator, see this CodeProject article: A Faster Directory Enumerator
You can invoke the Windows built-in progress bar when copying using the UIOption.AllDialogs:
My.Computer.FileSystem.CopyFile("C:\text.txt", "C:\my_folder\text.txt", FileIO.UIOption.AllDialogs, FileIO.UICancelOption.DoNothing)

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

Get the size of a Sub Folder/Sub Directory excluding the Parent Folder

i have a listview that shows folders and files, and i can display the size of the files and subfolders, but how do i do it with subfolders only not including the parent/root folder.
EDIT
like, if Folder1's size is 10 MB and it has a SubFolder with 20 MB size with a total of 30 MB, it should only get the size of the SubFolder which is 20 MB when displaying the contents of the Folder1 in a ListView.
Public Shared Function DirSize(ByVal d As DirectoryInfo) As Long
Dim Size As Long = 0
Dim dis As DirectoryInfo() = d.GetDirectories()
Dim di As DirectoryInfo
For Each di In dis
Size += DirSize(di)
Next di
Return Size
End Function
my listview code:
Sub lv1items()
ListView1.Items.Clear()
Dim fPath As String = Form2.TextBox1.Text
Dim di = New DirectoryInfo(fPath)
' store imagelist index for known/found file types
Dim exts As New Dictionary(Of String, Int32)
If di.Exists = False Then
MessageBox.Show("Destination path" & " " & Form2.TextBox1.Text & " is not found.", "Directory Not Found", MessageBoxButtons.OK, MessageBoxIcon.Error)
Form2.Show()
Else
Dim img As Image
Dim lvi As ListViewItem
For Each d In di.EnumerateDirectories("*.*", SearchOption.TopDirectoryOnly)
lvi = New ListViewItem(d.Name)
lvi.SubItems.Add(DirSize(di).ToString("0.00") & " MB")
lvi.SubItems.Add(d.CreationTime.Date)
ListView1.Items.Add(lvi)
img = NativeMethods.GetShellIcon(d.FullName)
ImageList1.Images.Add(img)
lvi.ImageIndex = ImageList1.Images.Count - 1
Next
End Sub
it returns a 0 size folder, but it has a file inside.
a little help please?
You can use this function:
Public Function GetDirectorySize(path As String) As Long
Dim files() As String = Directory.GetFiles(path, "*", SearchOption.AllDirectories)
Dim size As Long = 0
For Each file As String In files
Dim info As New FileInfo(file)
size += info.Length
Next
Return size
End Function
Note that this checks the size of every file in the folder and its subdirectories. Thus it is guaranteed to return the correct size.
Proof that it works:
Root:
SubFolder:
Total Size = (1483 + 25315) * 1024 = 274411152 bytes.
Program Output:
27440016 bytes ≈ 274411152 bytes.
Note: The difference exists because Windows Explorer rounds off some bytes to display the KB. If you view the properties of each file and add up then you will get the same size from both Explorer and the function.

Generate multiple files in a loop using visual basic

I am new to Visual Basic.
I wonder if I wanna generate multiple files in a while loop. It seems that the FILESTREAM cannot be reused even I set it into nothing at the end of loop.
Dim fs as FILESTREAM = nothing
Dim i as integer = 0
Dim path as string = "c:\users\...\Desktop\"
Dim name as string = nothing
while i<10
name = path + i.tostring
fs=File.Create(name)
i+=1
fs=nothing
end while
Thank you very much!
It's not clear if you're using the FileStream just to create a blank file or if you're planning on performing other operations against the new files. If you're just trying to create empty files and manipulate them later, after creation, then this should work:
Sub Main()
Dim i As Integer = 0
' You can use properties of My.Computer to find the current user's Desktop
Dim path As String = My.Computer.FileSystem.SpecialDirectories.Desktop
Dim fileName As String = String.Empty
While i < 10
fileName = path & "\" & i.ToString
System.IO.File.Create(fileName)
i += 1
End While
End Sub