• Welcome to Valhalla Legends Archive.
 

Most efficient way of makeing a User Database?

Started by Tontow, July 03, 2005, 11:03 PM

Previous topic - Next topic

Tontow

(Note: this is being done in Visual Basic 6.0)
What is the Most efficient way of makeing a User Database?

I could store everyting in a single file: usernames, moderator lvl, flags, etc. and use one of the methods discussed in "Most efficient way of saving settings on a bot?" - http://forum.valhallalegends.com/phpbbs/index.php?topic=11769.0
or
I could use a seperate file for each user, useing the user's nick as the file name.

Kp

Presuming your program has the capability to cache the results in memory, it won't matter too much which of those methods you go with.  However, from a disk usage perspective, saving one file per username is horribly wasteful.

Also, if you really care about efficiency, stop using VB. :)  At least upgrade to VB.NET and use the oft-touted support for compiling to MSIL (and from there to native code), which might get you close to performance of a C application if the VB.NET->MSIL generator doesn't do too terrible a job.  Our resident .NET geeks can probably give you more advice in this area if you decide to pursue it.
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

Dyndrilliac

This is how I do it.Option Explicit

Private Type UserList
    Username As String
    Flags As String
End Type

Private UL() As UserList
Public Users As Integer

Public Sub LoadUsers()
    Dim Splt() As String, sInput As String, pfNum As Integer, I As Integer
   
    If Dir$(App.Path & "\Database.dcu") = vbNullString Then
        Call MsgBox("Error: User Database could not be found!", vbCritical, "Error!")
        Exit Sub
    End If

    ReDim UL(0)
    I = 0
    pfNum = FreeFile

    Open (App.Path & "\Database.dcu") For Input As #pfNum
        Do Until EOF(pfNum)
            Input #pfNum, sInput
            If Not sInput = vbNullString Then
                Splt() = Split(sInput, ":", 1) '// Userlist syntax is "Username:Flags"
                UL(I).Username = Splt(0)
                If (InStr(Splt(1), "B") <> 0) Then
                    If (InStr(Splt(1), "L") <> 0) Then
                        Splt(1) = "BL"
                    Else
                        Splt(1) = "B"
                    End If
                ElseIf (InStr(Splt(1), "K") <> 0) Then
                    If (InStr(Splt(1), "L") <> 0) Then
                        Splt(1) = "KL"
                    Else
                        Splt(1) = "K"
                    End If
                ElseIf (InStr(Splt(1), "Z") <> 0) Then
                    If (InStr(Splt(1), "L") <> 0) Then
                        Splt(1) = "ZL"
                    Else
                        Splt(1) = "Z"
                    End If
                End If
                UL(I).Flags = UCase$(Alphabetize(Splt(1)))
                I = I + 1
                ReDim Preserve UL(0 To UBound(UL) + 1)
            End If
        Loop
    Close #pfNum

    Users = I
End Sub
Quote from: Edsger W. DijkstraIt is practically impossible to teach good programming to students that have had a prior exposure to BASIC; as potential programmers they are mentally mutilated beyond hope of regeneration.

Kp

Let me be the first to say: eww!  Even without writing VB, I can point out a few improvements (aside from the obvious one of not using VB, of course).

  • Compose your pathname once, not twice.  It's not a relevant performance hit to do it twice, but it could become a maintenance issue later if you change one and not the other.  Compose it to a temporary variable, then do both your existence test and your open against the contents of that variable.
  • Modify the error message to print out the file it sought (and ideally the path also).  Expecting the user to guess what to do about a missing database is a bad idea, and that's effectively what your code does.  Remember, documentation is only meant to kill trees: we don't want to write it, and users don't want to read it.  An error message announcing what is missing and what to do about it (or at least containing a reference on where to find that information) would be helpful.
  • Compose your flags in a temporary buffer, rather than trying to filter the existing one.  That is, do this (your problem to convert pseudocode to VB):flags = "";
    if (fromFile[1] contains "B")
        flags += "B";
    if (fromFile[1] contains "L")
        flags += "L";
    if (fromFile[1] contains "Z")
        flags += "Z";
    user_record.flags = flags;
    My approach is much more extensible, and IMO more readable.  Of course, I'd still highly advise going to a binary flags approach internally, but at least get away from enumerating every possible combination of access flags!
  • Reallocate the record array less often.  Consider having it grow in size by z% when needed, and then truncate it down to the minimal size when you're done.  For a good choice of z, that should get you down to a half dozen or so reallocations even for a huge list.
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

Tontow

I may oneday leave the darkside of the programming force and learn C# or C++ :P
----------------------------------------------------------------------------------------------------------------------------------------------------------

I would think useing a single user defined type with some properties as arrays in a binary file would be better and maby smaller than that
IE: (I'm not shure if the syntax is correct, but..)

public type database
users() as string
IndexOfLastEntrie as integer
flaga() as string
flagb() as string
flagc() as string
flagd() as string
end type

  So as to store and grap everything with just one put or get statment and not have to split anything; though, you would have to loop through to find the correct user.
Yet,
Useing one file for each user with the filename being the username would remove the need to loop through; just open put or get a single user defined varable and close.

It is possable that the userdata base could get very large and the method used should be able to access a single user when the database could possably contain 10,000 or more users.- I think that that big of a database would not make it possable to loop through all those users.

I could also use a string varable to store all the usernames with the array position. IE: username,index,username,index,username,index,username,index,username,index
and then use find or somthing to retreave a carater position and then work from there to find the index number


R.a.B.B.i.T

'----------------------------------------------------------------------------
' Module    : modDatabase
' DateTime  : 5/5/2005 12:44
' Author    : Spencer Ragen
' Purpose   : Database handling, duh
'----------------------------------------------------------------------------

Option Explicit

Public Type s_DatabaseEntry
    UserName    As String * 25
    AddBy       As String * 25
    AddOn       As String * 8
    AddAt       As String * 12
    Access      As Integer
    Master      As Boolean
End Type

Private DBUsers() As s_DatabaseEntry

Public Sub DBStartup()
    ReDim DBUsers(0)
End Sub

Public Sub debugdb()
    Dim c As s_DatabaseEntry
    Dim i As Integer
    For i = LBound(DBUsers) To UBound(DBUsers)
        c = DBUsers(i)
        Debug.Print c.UserName
        Debug.Print c.Master
        Debug.Print c.Access
        Debug.Print c.AddBy
        Debug.Print c.AddOn
        Debug.Print c.AddAt & vbNewLine
    Next i
End Sub

Public Sub LoadDatabase()
    Dim t As String
   
    If varDatabase = "" Or Dir$(varDatabase) = "" Then
        AddC vbRed, "Database file is invalid"
    End If
   
    Dim c As s_DatabaseEntry
    Dim k As String
   
    Open varDatabase For Random As #1
    Do While Not EOF(1)
        Get #1, , c
        If c.UserName <> String(25, vbNullChar) Then
            c.UserName = Trim(c.UserName)
            c.AddBy = Trim(c.AddBy)
            AddDBUser c
        End If
    Loop
    Close #1
   
    If Not IsFound(BotMaster) Then
        GetTimeStamp t
        c.UserName = LCase(BotMaster)
        c.Access = 101
        c.Master = True
           
        t = Replace(t, " ", "")
        t = Replace(t, "[", "")
           
        c.AddAt = Replace(t, "]", "")
        c.AddBy = "#settings"
        c.AddOn = Format(Date, "mm.dd.yy")
        AddDBUser c
    End If
   
    'Ignore this, it's for my bot :\
    'If ShowMisc Then AddC colInfo, UBound(DBUsers) + 1 & " database entries loaded"
End Sub

Public Sub SaveDatabase()
   
    Dim i As Integer
    Dim o As Integer
    Dim ent As String
    Dim list() As String
   
    ReDim list(0)
   
    Open varDatabase For Random As #1
    For i = LBound(DBUsers) To UBound(DBUsers)
        For o = 0 To UBound(list)
            If LCase(list(o)) = LCase(DBUsers(i).UserName) Then GoTo SkipSave
        Next o
       
        If list(UBound(list)) <> "" Then
            ReDim Preserve list(UBound(list) + 1)
        End If
       
        list(UBound(list)) = DBUsers(i).UserName
       
        If DBUsers(i).UserName <> Null And DBUsers(i).UserName <> "" Then Put #1, , DBUsers(i)
       
SkipSave:
    Next i
    Close #1
End Sub

Public Sub AddDBUser(c As s_DatabaseEntry)
    If DBUsers(UBound(DBUsers)).UserName <> "" And DBUsers(UBound(DBUsers)).UserName <> String(25, vbNullChar) Then
        ReDim Preserve DBUsers(UBound(DBUsers) + 1)
    End If
    DBUsers(UBound(DBUsers)) = c
    If c.Access >= 30 Or c.Master Then AddSafe c.UserName
End Sub

Public Sub DelDBUser(strName As String)
    Dim i As Integer
    For i = LBound(DBUsers) To UBound(DBUsers)
        If LCase(strName) = Trim(DBUsers(i).UserName) Then
            DBUsers(i).UserName = ""
            Call SaveDatabase
            Exit Sub
        End If
    Next i
End Sub

Public Function IsFound(strUser As String) As Boolean
    Dim i As Integer
    For i = LBound(DBUsers) To UBound(DBUsers)
        If LCase(strUser) = Trim(DBUsers(i).UserName) Then
            IsFound = True
            Exit Function
        End If
    Next i
End Function

Public Function GetDBEntry(strName As String) As s_DatabaseEntry
    Dim i As Integer
    For i = LBound(DBUsers) To UBound(DBUsers)
        If LCase(strName) = Trim(DBUsers(i).UserName) Then
            GetDBEntry = DBUsers(i)
            Exit Function
        End If
    Next i
    GetDBEntry.UserName = "not found"
End Function

Public Function GetAccess(strName As String) As Integer
    Dim i As Integer
    For i = LBound(DBUsers) To UBound(DBUsers)
        If LCase(strName) = Trim(DBUsers(i).UserName) Then
            GetAccess = DBUsers(i).Access
            Exit Function
        End If
    Next i
    GetAccess = -1
End Function

Tontow

What about an application data file?  Perhaps a MS Access file?  Would that be better than any of the other methods discussed thus far?