How to enlarge a List(of String)(bytes) - vb.net

I have a variable Queue in which I write information from a stream. The variable is initiated as follows:
Public Shared Queue As List(Of String) = New List(Of String)(1024)
The code to read the stream is
Public Shared Sub ReadStreamForever(ByVal stream As Stream)
Dim encoder = New UTF8Encoding()
Dim buffer = New Byte(2047) {}
Dim counter as Integer = 0
While True
If stream.CanRead Then
Dim len As Integer = stream.Read(buffer, 0, 2048)
Counter = Counter + 1
If len > 0 Then
Dim text = encoder.GetString(buffer, 0, len)
SSEApplication.Push(text)
Else
Exit While
End If
Else
Exit While
End If
End While
End Sub
Where the push methode just does a few string manipulation and adds line after line into the Queue Variable
Public Shared Sub Push(ByVal text As String)
If String.IsNullOrWhiteSpace(text) Then
Return
End If
Dim lines = text.Trim().Split(vbLf)
SSEApplication.Queue.AddRange(lines)
End Sub
I have different big datasets I want to stream but the Queue length after filling it up is always 2691, so it looks like it is kind of limited in length. I just do not know where I limit the Queue Variable and how to enlarge it. Could anyone help me here?

In general, List doesn't have fixed length, Add method resizes List and makes space for another element.
If you want to have fixed length, you could use simple array: Dim Queue(1024) As string
But then, you will get an exception when trying to add more elements, so you can check the condition in Push method:
If lines.Count < 1024 Then
SSEApplication.Queue.AddRange(lines)
End If
That check will also prevent having more than 1024 elements when using List, but if you have collection of fixed length, I would recommend using simple array.
Useful resource: Arrays in Visual Basic, there you can also read, how to enlarge array, when you want to add extra elements using ReDim keyword.

Related

Random() doesn't seem to be so random at all

Random() doesn't seem to be so random at all, it keeps repeating the pattern all the time.
How can I make this "more" random?
Dim ioFile As New System.IO.StreamReader("C:\names.txt")
Dim lines As New List(Of String)
Dim rnd As New Random()
Dim line As Integer
While ioFile.Peek <> -1
lines.Add(ioFile.ReadLine())
End While
line = rnd.Next(lines.Count + 0)
NAMES.AppendText(lines(line).Trim())
ioFile.Close()
ioFile.Dispose()
Clipboard.SetText(NAMES.Text)
This works fine for me. I changed a few things like implementing a using block, removed a redundant addition of 0, and added a loop to test 100 times out to debug. a sample of 200 that you are just "eyeballing" is not enough to say that a random sequence is "not working".
Using ioFile As New System.IO.StreamReader("C:\names.txt")
Dim lines As New List(Of String)
Dim rnd As New Random()
Dim line As Integer
While ioFile.Peek <> -1
lines.Add(ioFile.ReadLine())
End While
For i As Integer = 1 To 100
line = rnd.Next(lines.Count)
Debug.WriteLine(lines(line).Trim())
Next
End Using
You don't need a stream reader to read a text file. File.ReadAllLines will return an array of lines in the file. Calling .ToList on this method gets you the desired List(Of String)
We will loop through the length of the list in a for loop. We subtract one because indexes start at zero.
To get the random index we call .Next on our instance of the Random class that was declared outside the method (a form level variable) The .Next method is inclusive of the first variable and exclusive of the second. I used a variable to store the original value of lines.Count because this value will change in the loop and it would mess with for loop if we used lines.Count -1 directly in the To portion of the For.
Once we get the random index we add that line to the TextBox and remove it from the list.
Private Sub ShuffleNames()
Dim index As Integer
Dim lines = File.ReadAllLines("C:\Users\xxx\Desktop\names.txt").ToList
Dim loopLimit = lines.Count - 1
For i = 0 To loopLimit
index = rnd.Next(0, lines.Count)
TextBox1.AppendText(lines(index).Trim & Environment.NewLine)
lines.RemoveAt(index)
Next
End Sub

Displaying the ASCII Table Number for each item in a byte array

I am writing a Message builder and in it I have to send a byte array to a remote machine requesting data.
I have an 8 bit number that counts how many items are being requested (count) and am trying to get the number inserted into a byte array.
The Byte array locations are in positions 70 & 71 of the array and in little endian order.
I am trying to just convert the byte and then insert the converted bytes with the following code.
Dim count As Byte = 3
Dim countbyteArray As Byte() = BitConverter.GetBytes(count)
sendBytes(70) = (countbyteArray(0))
sendBytes(71) = (countbyteArray(1))
when I do this for anything greater than 1 it does not get accepted by the device I am sending the byte array to.
Am I in the right ball Park or completely wrong as I am not completely up to speed with Bytes and Bits. Also any example or explanation would be greatly appreciated.
Little endian is the natural order in .NET so it is easy to work with.
As for a message builder, wouldn't it be easier to say
Dim message = New Message With { .Command = Command.Request, .Device = &hF, .Count = 8, .Filter = &h1234 }
Dim bytes = message.GetBytes()
' Hex for debugging
Dim hex = BitConverter.ToString(bytes)
You just have to layout the structure of the message. One way is with LayoutKind.Explicit. I'd probably choose the LayoutKind that most closely aligns with the device documentation.
Public Enum Command As Byte
Reset = 0
Request = 1
End Enum
<StructLayout(LayoutKind.Explicit)>
Public Structure Message
<FieldOffset(0)> Public Command As Command
<FieldOffset(1)> Public Device As Byte
' unused bytes default to 0
<FieldOffset(10)> Public Count As UInt16
<FieldOffset(12)> Public Filter As Int16
Public Function GetBytes() As Byte()
Dim rawData(0 To Marshal.SizeOf(Me) - 1) As Byte
Dim handle = GCHandle.Alloc(rawData, GCHandleType.Pinned)
Try
Dim rawDataPtr = handle.AddrOfPinnedObject()
Marshal.StructureToPtr(Me, rawDataPtr, False)
Return rawData
Finally
handle.Free()
End Try
End Function
End Structure
'TODO define enums, etc to make it easier for caller.
'TODO update struct per device documentation

VB.NET Display progress of file decryption?

I'm using this code to encrypt/decrypt files:
Public Shared Sub encryptordecryptfile(ByVal strinputfile As String, _
ByVal stroutputfile As String, _
ByVal bytkey() As Byte, _
ByVal bytiv() As Byte, _
ByVal direction As CryptoAction)
Try
fsInput = New System.IO.FileStream(strinputfile, FileMode.Open, FileAccess.Read)
fsOutput = New System.IO.FileStream(stroutputfile, FileMode.OpenOrCreate, FileAccess.Write)
fsOutput.SetLength(0)
Dim bytbuffer(4096) As Byte
Dim lngbytesprocessed As Long = 0
Dim lngfilelength As Long = fsInput.Length
Dim intbytesincurrentblock As Integer
Dim cscryptostream As CryptoStream
Dim csprijndael As New System.Security.Cryptography.RijndaelManaged
Select Case direction
Case CryptoAction.ActionEncrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateEncryptor(bytkey, bytiv), _
CryptoStreamMode.Write)
Case CryptoAction.ActionDecrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateDecryptor(bytkey, bytiv), _
CryptoStreamMode.Write)
End Select
While lngbytesprocessed < lngfilelength
intbytesincurrentblock = fsInput.Read(bytbuffer, 0, 4096)
cscryptostream.Write(bytbuffer, 0, intbytesincurrentblock)
lngbytesprocessed = lngbytesprocessed + CLng(intbytesincurrentblock)
End While
cscryptostream.Close()
fsInput.Close()
fsOutput.Close()
Catch ex As Exception
End Try
End Sub
Is I need to get the percentage of this process being done as an integer. I am going to use a background worker, so I need to call for this sub from the background worker and be able to keep refreshing a progress bar that the background worker reports to. Is this possible?
Thanks in advance.
There are a couple of things you can do to make your cryptor more efficient and other issues:
A method like encryptordecryptfile which then requires a "mode" argument to know which action to take means it really might be better off as 2 methods
The way you are going, you will be raising a blizzard of ProgressChanged events which the ProgressBar wont be able to keep up with given the animation. A 700K file will result in 170 or so progress reports of tiny amounts
Some of the crypto steps can be incorporated
You have a lot of things not being disposed of; you could run out of resources if you run a number of files thru it in a loop.
It might be worth noting that you can replace the entire While block with fsInput.CopyTo(cscryptostream) to process the file all at once. This doesnt allow progress reporting though. Its also not any faster.
Rather than a BackgroundWorker (which will work fine), you might want to implement it as a Task. The reason for this is that all those variables need to make their way from something like a button click to the DoWork event where your method is actually called. Rather than using global variables or a class to hold them, a Task works a bit more directly (but does involve one extra step when reporting progress). First, a revised EncryptFile method:
Private Sub EncryptFile(inFile As String,
outFile As String,
pass As String,
Optional reporter As ProgressReportDelegate = Nothing)
Const BLOCKSIZE = 4096
Dim percentDone As Integer = 0
Dim totalBytes As Int64 = 0
Dim buffSize As Int32
' Note A
Dim key = GetHashedBytes(pass)
Dim iv = GetRandomBytes(16)
Dim cryptor As ICryptoTransform
' Note B
Using fsIn As New FileStream(inFile, FileMode.Open, FileAccess.Read),
fsOut As New FileStream(outFile, FileMode.OpenOrCreate, FileAccess.Write)
fsOut.SetLength(0)
' Note C
'ToDo: work out optimal block size for Lg vs Sm files
If fsIn.Length > (2 * BLOCKSIZE) Then
' use buffer size to limit to 20 progress reports
buffSize = CInt(fsIn.Length \ 20)
' to multiple of 4096
buffSize = CInt(((buffSize + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE)
' optional, limit to some max size like 256k?
'buffSize = Math.Min(buffSize, BLOCK256K)
Else
buffSize = BLOCKSIZE
End If
Dim buffer(buffSize-1) As Byte
' Note D
' write the IV to "naked" fs
fsOut.Write(iv, 0, iv.Length)
Using rij = Rijndael.Create()
rij.Padding = PaddingMode.ISO10126
Try
cryptor = rij.CreateEncryptor(key, iv)
Using cs As New CryptoStream(fsOut, cryptor, CryptoStreamMode.Write)
Dim bytesRead As Int32
Do Until fsIn.Position = fsIn.Length
bytesRead = fsIn.Read(buffer, 0, buffSize)
cs.Write(buffer, 0, bytesRead)
If reporter IsNot Nothing Then
totalBytes += bytesRead
percentDone = CInt(Math.Floor((totalBytes / fsIn.Length) * 100))
reporter(percentDone)
End If
Loop
End Using
Catch crEx As CryptographicException
' ToDo: Set breakpoint and inspect message
Catch ex As Exception
' ToDo: Set breakpoint and inspect message
End Try
End Using
End Using
End Sub
Note A
One of the standard crypto tasks it could handle is creating the Key and IV arrays for you. These are pretty simple and could be shared/static members.
Public Shared Function GetHashedBytes(data As String) As Byte()
Dim hBytes As Byte()
' or SHA512Managed
Using hash As HashAlgorithm = New SHA256Managed()
' convert data to bytes:
Dim dBytes = Encoding.UTF8.GetBytes(data)
' hash the result:
hBytes = hash.ComputeHash(dBytes)
End Using
Return hBytes
End Function
Public Shared Function GetRandomBytes(size As Integer) As Byte()
Dim data(size - 1) As Byte
Using rng As New RNGCryptoServiceProvider
' fill the array
rng.GetBytes(data)
End Using
Return data
End Function
As will be seen later, you can store the IV in the encrypted file rather than saving and managing it in code.
Note B
Using blocks close and dispose of resources for you. Basically, if something has a Dispose method, then you should wrap it in a Using block.
Note C
You dont want to report progress for every block read, that will just overwhelm the ProgressBar. Rather than another variable to keep track of when the progress has changed by some amount, this code starts by creating a buffer size which is 5% of the input file size so there will be about 20 reports (every 5%).
As the comments indicate, you may want to add some code to set minimum/maximum buffer size. Doing so would change the progress report frequency.
Note D
You can write the IV() to the filestream before you wrap it in the CryptoStream (and of course read it back first when Decrypting). This prevents you from having to store the IV.
The last part is kicking this off as a Task:
Dim t As Task
t = Task.Run(Sub() EncryptFile(inFile, oFile, "MyWeakPassword",
AddressOf ReportProgress))
...
What a BGW does is execute the work on one thread, but progress is reported on the UI thread. As a Task, all we need to do is use Invoke:
Delegate Sub ProgressReportDelegate(value As Int32)
Private Sub ReportProgress(v As Int32)
If progBar.InvokeRequired Then
progBar.Invoke(Sub() progBar.Value = v)
Else
progBar.Value = v
progBar.Invalidate()
End If
End Sub
The Encryptor will work either directly or as a Task. For small files, you can omit the progress report entirely:
' small file, no progress report:
EncryptFile(ifile, oFile, "MyWeakPassword")
' report progress, but run on UI thread
EncryptFile(ifile, oFile, "MyWeakPassword",
AddressOf ReportProgress)
' run as task
Dim t As Task
t = Task.Run(Sub() EncryptFile(ifile, oFile, "MyWeakPassword",
AddressOf ReportProgress))
...and if you had a list of files to do, you could run them all at once and perhaps report total progress.

visual basic .NET get user input without pressing enter

I know of the existence of the code character = System.Console.ReadKey().tostring.
This will read one character.
Also in another stack overflow post I found:
Public Shared Function ReadPipedInfo() As StreamReader
'call with a default value of 5 milliseconds
Return ReadPipedInfo(5000)
End Function
Public Shared Function ReadPipedInfo(waitTimeInMilliseconds As Integer) As StreamReader
'allocate the class we're going to callback to
Dim callbackClass As New ReadPipedInfoCallback()
'to indicate read complete or timeout
Dim readCompleteEvent As New AutoResetEvent(False)
'open the StdIn so that we can read against it asynchronously
Dim stdIn As Stream = Console.OpenStandardInput()
'allocate a one-byte buffer, we're going to read off the stream one byte at a time
Dim singleByteBuffer As Byte() = New Byte(0) {}
'allocate a list of an arbitary size to store the read bytes
Dim byteStorage As New List(Of Byte)(4096)
Dim asyncRead As IAsyncResult = Nothing
Dim readLength As Integer = 0
'the bytes we have successfully read
Do
'perform the read and wait until it finishes, unless it's already finished
asyncRead = stdIn.BeginRead(singleByteBuffer, 0, singleByteBuffer.Length, New AsyncCallback(AddressOf callbackClass.ReadCallback), readCompleteEvent)
If Not asyncRead.CompletedSynchronously Then
readCompleteEvent.WaitOne(waitTimeInMilliseconds)
End If
'end the async call, one way or another
'if our read succeeded we store the byte we read
If asyncRead.IsCompleted Then
readLength = stdIn.EndRead(asyncRead)
'If readLength > 0 Then
byteStorage.Add(singleByteBuffer(0))
'End If
End If
Loop While asyncRead.IsCompleted AndAlso readLength > 0
'we keep reading until we fail or read nothing
'return results, if we read zero bytes the buffer will return empty
Return New StreamReader(New MemoryStream(byteStorage.ToArray(), 0, byteStorage.Count))
End Function
Private Class ReadPipedInfoCallback
Public Sub ReadCallback(asyncResult As IAsyncResult)
'pull the user-defined variable and strobe the event, the read finished successfully
Dim readCompleteEvent As AutoResetEvent = TryCast(asyncResult.AsyncState, AutoResetEvent)
readCompleteEvent.[Set]()
End Sub
End Class
which reads input if the user pressed enter
how could I make some code that reads (multiple) character(s) without letting the user press enter all the time? But instead use time as the indicator to stop reading the console?
You can use System.Console.Read() here. It reads as character from standard input stream. https://msdn.microsoft.com/en-us/library/system.console.read(v=vs.110).aspx
According to here I found out how to deal with it:
Public Module ConsoleHelper
Sub Main()
msgbox(ReadKeyWithTimeOut(10000).key.ToString)
End Sub
Public Function ReadKeyWithTimeOut(timeOutMS As Integer) As ConsoleKeyInfo
Dim timeoutvalue As DateTime = DateTime.Now.AddMilliseconds(timeOutMS)
While DateTime.Now < timeoutvalue
If Console.KeyAvailable Then
Dim cki As ConsoleKeyInfo = Console.ReadKey()
Return cki
Else
System.Threading.Thread.Sleep(100)
End If
End While
Return New ConsoleKeyInfo(" "C, ConsoleKey.Spacebar, False, False, False)
End Function
End Module
Now just finding out how to give attribute key NULL or something if it timed out.

linq submitchanges runs out of memory

I have a database with about 180,000 records. I'm trying to attach a pdf file to each of those records. Each pdf is about 250 kb in size. However, after about a minute my program starts taking about about a GB of memory and I have to stop it. I tried doing it so the reference to each linq object is removed once it's updated but that doesn't seem to help. How can I make it clear the reference?
Thanks for your help
Private Sub uploadPDFs(ByVal args() As String)
Dim indexFiles = (From indexFile In dataContext.IndexFiles
Where indexFile.PDFContent = Nothing
Order By indexFile.PDFFolder).ToList
Dim currentDirectory As IO.DirectoryInfo
Dim currentFile As IO.FileInfo
Dim tempIndexFile As IndexFile
While indexFiles.Count > 0
tempIndexFile = indexFiles(0)
indexFiles = indexFiles.Skip(1).ToList
currentDirectory = 'I set the directory that I need
currentFile = 'I get the file that I need
writePDF(currentDirectory, currentFile, tempIndexFile)
End While
End Sub
Private Sub writePDF(ByVal directory As IO.DirectoryInfo, ByVal file As IO.FileInfo, ByVal indexFile As IndexFile)
Dim bytes() As Byte
bytes = getFileStream(file)
indexFile.PDFContent = bytes
dataContext.SubmitChanges()
counter += 1
If counter Mod 10 = 0 Then Console.WriteLine(" saved file " & file.Name & " at " & directory.Name)
End Sub
Private Function getFileStream(ByVal fileInfo As IO.FileInfo) As Byte()
Dim fileStream = fileInfo.OpenRead()
Dim bytesLength As Long = fileStream.Length
Dim bytes(bytesLength) As Byte
fileStream.Read(bytes, 0, bytesLength)
fileStream.Close()
Return bytes
End Function
I suggest you perform this in batches, using Take (before the call to ToList) to process a particular number of items at a time. Read (say) 10, set the PDFContent on all of them, call SubmitChanges, and then start again. (I'm not sure offhand whether you should start with a new DataContext at that point, but it might be cleanest to do so.)
As an aside, your code to read the contents of a file is broken in at least a couple of ways - but it would be simpler just to use File.ReadAllBytes in the first place.
Also, your way of handling the list gradually shrinking is really inefficient - after fetching 180,000 records, you're then building a new list with 179,999 records, then another with 179,998 records etc.
Does the DataContext have ObjectTrackingEnabled set to true (the default value)? If so, then it will try to keep a record of essentially all the data it touches, thus preventing the garbage collector from being able to collect any of it.
If so, you should be able to fix the situation by periodically disposing the DataContext and creating a new one, or turning object tracking off.
OK. To use the smallest amount of memory we have to update the datacontext in blocks. I've put a sample code below. Might have sytax errors since I'm using notepad to type it in.
Dim DB as YourDataContext = new YourDataContext
Dim BlockSize as integer = 25
Dim AllItems = DB.Items.Where(function(i) i.PDFfile.HasValue=False)
Dim count = 0
Dim tmpDB as YourDataContext = new YourDataContext
While (count < AllITems.Count)
Dim _item = tmpDB.Items.Single(function(i) i.recordID=AllItems.Item(count).recordID)
_item.PDF = GetPDF()
Count +=1
if count mod BlockSize = 0 or count = AllItems.Count then
tmpDB.SubmitChanges()
tmpDB = new YourDataContext
GC.Collect()
end if
End While
To Further optimise the speed you can get the recordID's into an array from allitems as an anonymous type, and set DelayLoading on for that PDF field.