Advertisement
Arc13

Database Manager

Nov 1st, 2017
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.62 KB | None | 0 0
  1. -- Declare vars
  2. local bRun = true
  3. local nStage = 1
  4.  
  5. local nAnimationStage = 1
  6. local tAnimatedPoints = {".", "..", "...", " ..", "  .", "   "}
  7.  
  8. local sUsername = ""
  9. local sPasswd = ""
  10. local sHashPasswd = ""
  11. local sToken = ""
  12. local nAccessLevel = 1
  13.  
  14. local usersFile = {Users = {}, Password = {}, Biolock = {}, AccessLevel = {}, Token = {}, Where = {}}
  15.  
  16. -- Check dependencies
  17. if not fs.exists("/StringUtils") then shell.run("pastebin get ad3aUsVw StringUtils") end
  18. os.loadAPI("StringUtils")
  19.  
  20. -- Check peripherals
  21. if not peripheral.find("biolock") then
  22.   error("No biolock attached to this computer", 0)
  23. end
  24.  
  25. -- Setup UI
  26. term.setBackgroundColor(colors.lightGray)
  27. term.clear()
  28. paintutils.drawLine(1, 1, 51, 1, colors.gray)
  29.  
  30. -- Functions
  31. local function updateWindowTitle(newTitle)
  32.   -- Function to change the window title
  33.   paintutils.drawLine(1, 1, 50, 1, colors.gray)
  34.   term.setCursorPos(1, 1)
  35.   term.write(newTitle)
  36. end
  37.  
  38. local function close(errorMessage)
  39.   -- Function to clear the screen and stop the loops
  40.   term.setBackgroundColor(colors.black)
  41.   term.clear()
  42.   term.setCursorPos(1, 1)
  43.  
  44.   if errorMessage then
  45.     error(tostring(errorMessage), 0)
  46.   end
  47.  
  48.   bRun = false
  49. end
  50.  
  51. local function clearWorkingArea(clearTitle)
  52.   -- Clear the screen except for the title bar
  53.   if not clearTitle then
  54.     paintutils.drawFilledBox(1, 2, 51, 19, colors.lightGray)
  55.   else
  56.     paintutils.drawFilledBox(1, 4, 51, 19, colors.lightGray)
  57.   end
  58. end
  59.  
  60. local function updateStage(newStage, clearTitle)
  61.   -- Update the stage:
  62.   --[[
  63.       1: Waiting for a biolock event
  64.       2: Waiting for the user to enter his/her username
  65.       3: Waiting for the user to enter his/her password
  66.       4: Waiting for the user to enter his/her access level
  67.       5: Waiting for the user to verify his/her settings
  68.   ]]
  69.   clearWorkingArea(clearTitle)
  70.   nStage = newStage
  71. end
  72.  
  73. local function updateTitle(newTitle)
  74.   -- Function to change the working area title
  75.   term.setBackgroundColor(colors.lightGray)
  76.   term.setCursorPos(1, 3)
  77.   term.clearLine()
  78.   term.write(newTitle)
  79. end
  80.  
  81. local function allowCloseButton(isCloseButtonAllowed)
  82.   -- Change the visibility of the close button in the title bar (Shown in stage 1)
  83.   if isCloseButtonAllowed then
  84.     if term.isColor() then
  85.       paintutils.drawPixel(51, 1, colors.red)
  86.       term.setCursorPos(51, 1)
  87.       term.write("X")
  88.     else
  89.       term.setCursorPos(51, 1)
  90.       term.setTextColor(colors.lightGray)
  91.       term.write("X")
  92.       term.setTextColor(colors.white)
  93.     end
  94.   else
  95.     paintutils.drawPixel(51, 1, colors.gray)
  96.   end
  97. end
  98.  
  99. local function checkAccessLevel(isIncreasing)
  100.   -- Check if nAccessLevel is in a valid range [-1; 5] \ {0}
  101.   if nAccessLevel == 0 then
  102.     if isIncreasing then
  103.       nAccessLevel = 1
  104.     else
  105.       nAccessLevel = -1
  106.     end
  107.   elseif nAccessLevel < -1 then
  108.     nAccessLevel = -1
  109.   elseif nAccessLevel > 5 then
  110.     nAccessLevel = 5
  111.   end
  112. end
  113.  
  114. local function getLastID()
  115.   -- Get the number of players registered
  116.   local nLastID = 0
  117.  
  118.   for k, v in pairs(usersFile) do
  119.     if #v > nLastID then
  120.       nLastID = #v
  121.     end
  122.   end
  123.  
  124.   return nLastID
  125. end
  126.  
  127. local function loadUsersFile()
  128.   -- Load the /users file if it exists and is valid
  129.   if fs.exists("/users") then
  130.     local file = fs.open("/users", "r")
  131.     local fileContent = file.readAll()
  132.     local tFileContent = textutils.unserialise(fileContent)
  133.  
  134.     if tFileContent then
  135.       usersFile = tFileContent
  136.     end
  137.  
  138.     file.close()
  139.   end
  140. end
  141.  
  142. local function saveUsersFile()
  143.   -- Save the current usersFile var to a file
  144.   local file = fs.open("/users", "w")
  145.   file.write(textutils.serialise(usersFile))
  146.   file.close()
  147. end
  148.  
  149. local function checkUsersFile()
  150.   -- Check the current usersFile var to find corrupted entries
  151.   local entryCount = getLastID()
  152.   local repairDatabase = false
  153.   local isCorrupted = false
  154.   local corruptedEntries = {}
  155.  
  156.   -- Checking all the entries
  157.   for i = 1, entryCount do
  158.     if usersFile.Users[i] == nil or usersFile.Password[i] == nil or usersFile.Biolock[i] == nil or usersFile.AccessLevel[i] == nil or usersFile.Token[i] == nil or usersFile.Where[i] == nil then
  159.       isCorrupted = true
  160.       table.insert(corruptedEntries, i)
  161.     end
  162.   end
  163.  
  164.   if isCorrupted then
  165.     -- Asking the user if the database needs to be fixed
  166.     updateWindowTitle("userAPI - Database corrupted")
  167.     updateTitle("Do you want to fix the database ?")
  168.  
  169.     -- Draw the UI
  170.     if term.isColor() then
  171.       paintutils.drawFilledBox(1, 17, 3, 19, colors.orange)
  172.     else
  173.       paintutils.drawFilledBox(1, 17, 3, 19, colors.black)
  174.     end
  175.     term.setCursorPos(2, 18)
  176.     term.write("X")
  177.     if term.isColor() then
  178.       paintutils.drawFilledBox(49, 17, 51, 19, colors.green)
  179.     else
  180.       paintutils.drawFilledBox(49, 17, 51, 19, colors.white)
  181.       term.setTextColor(colors.black)
  182.     end
  183.     term.setCursorPos(50, 18)
  184.     term.write(">")
  185.     term.setTextColor(colors.white)
  186.  
  187.     while true do
  188.       local event, p1, p2, p3 = os.pullEvent()
  189.  
  190.       if event == "mouse_click" then
  191.         if p2 >= 1 and p2 <= 3 and p3 >= 17 and p3 <= 19 then
  192.           -- Cancel button clicked
  193.           break
  194.         elseif p2 >= 49 and p2 <= 51 and p3 >= 17 and p3 <= 19 then
  195.           -- Accept button clicked
  196.           repairDatabase = true
  197.           break
  198.         end
  199.       elseif event == "key" then
  200.         if p1 == 14 or p1 == 45 then
  201.           -- Cancel button pressed
  202.           break
  203.         elseif p1 == 28 or p1 == 205 then
  204.           -- Accept button pressed
  205.           repairDatabase = true
  206.           break
  207.         end
  208.       end
  209.     end
  210.  
  211.     if repairDatabase then
  212.       for i = #corruptedEntries, 1, -1 do
  213.         for k, v in pairs(usersFile) do
  214.           table.remove(v, corruptedEntries[i])
  215.         end
  216.       end
  217.  
  218.       for k, v in pairs(usersFile) do
  219.         local tableIndexs = {}
  220.         for k, v in pairs(v) do
  221.           table.insert(tableIndexs, k)
  222.         end
  223.         for i = 1, #tableIndexs do
  224.           table.insert(v, i, table.remove(v, tableIndexs[i]))
  225.         end
  226.       end
  227.  
  228.       saveUsersFile()
  229.     end
  230.   end
  231. end
  232.  
  233. local function TOKEN_GEN()
  234.     local TOKEN_LENGHT = 25
  235.     local TOKEN = ""
  236.     local TOKEN_TABLE = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","#", "$", "*", "!"}
  237.  local x    = 0
  238.  while TOKEN_LENGHT > 0 do
  239.   x=x+1
  240.         local mRandom = math.random(1,#TOKEN_TABLE)
  241.         TOKEN = TOKEN..TOKEN_TABLE[mRandom]
  242.         TOKEN_LENGHT = TOKEN_LENGHT - 1
  243.     end
  244.     return TOKEN
  245. end
  246.  
  247. local function checkTokenAvailability(tokenToVerify)
  248.   -- Check if a generated token is not already used
  249.   for i = 1, #usersFile.Token do
  250.     if usersFile.Token[i] == tokenToVerify then
  251.       return false
  252.     end
  253.   end
  254.  
  255.   return true
  256. end
  257.  
  258. local function resetPersonalData()
  259.   -- Reset vars used in the creation procedure
  260.   sUsername = ""
  261.   sPasswd = ""
  262.   sHashPasswd = ""
  263.   sToken = ""
  264.   nAccessLevel = 1
  265. end
  266.  
  267. local function manageStageFour()
  268.   -- Manage the Stage 4 UI
  269.   while nStage == 4 do
  270.     -- Reset the NumericUpDown UI
  271.     if term.isColor() then
  272.       paintutils.drawLine(16, 6, 35, 6, colors.lightBlue)
  273.     else
  274.       paintutils.drawLine(16, 6, 35, 6, colors.white)
  275.       term.setTextColor(colors.black)
  276.     end
  277.  
  278.     -- Write the actual nAccessLevel
  279.     term.setCursorPos(26 - math.floor(tostring(nAccessLevel):len() / 2), 6)
  280.     term.write(tostring(nAccessLevel))
  281.     term.setTextColor(colors.white)
  282.  
  283.     -- Wait for user interaction
  284.     event, p1, p2, p3 = os.pullEvent()
  285.  
  286.     if event == "mouse_click" then
  287.       if p2 == 15 and p3 == 6 then
  288.         -- Minus button clicked
  289.         nAccessLevel = nAccessLevel - 1
  290.         checkAccessLevel(false)
  291.       elseif p2 == 36 and p3 == 6 then
  292.         -- Plus button clicked
  293.         nAccessLevel = nAccessLevel + 1
  294.         checkAccessLevel(true)
  295.       elseif p2 == 51 and p3 == 6 then
  296.         -- Next button clicked
  297.         updateStage(5)
  298.       end
  299.     elseif event == "key" then
  300.       if p1 == 74 or p1 == 208 then
  301.         -- Minus button pressed
  302.         nAccessLevel = nAccessLevel - 1
  303.         checkAccessLevel(false)
  304.       elseif p1 == 78 or p1 == 200 then
  305.         -- Plus button pressed
  306.         nAccessLevel = nAccessLevel + 1
  307.         checkAccessLevel(true)
  308.       elseif p1 == 28 or p1 == 205 then
  309.         -- Next button pressed
  310.         updateStage(5)
  311.       end
  312.     end
  313.   end
  314. end
  315.  
  316. local function manageStageFive()
  317.   -- Manage the Stage 5 UI
  318.   while nStage == 5 do
  319.     -- Wait for user interaction
  320.     event, p1, p2, p3 = os.pullEvent()
  321.  
  322.     if event == "mouse_click" then
  323.       if p2 >= 49 and p2 <= 51 and p3 >= 17 and p3 <= 19 then
  324.         -- Accept button clicked
  325.         return false, true
  326.       elseif p2 >= 1 and p2 <= 3 and p3 >= 17 and p3 <= 19 then
  327.         -- Back button clicked
  328.         return true, false
  329.       elseif p2 >= 24 and p2 <= 26 and p3 >= 17 and p3 <= 19 then
  330.         -- Cancel button clicked
  331.         return false, false
  332.       end
  333.     elseif event == "key" then
  334.       if p1 == 28 or p1 == 205 then
  335.         -- Accept button pressed
  336.         return false, true
  337.       elseif p1 == 14 or p1 == 203 then
  338.         -- Back button pressed
  339.         return true, false
  340.       elseif p1 == 45 then
  341.         -- Cancel button clicked
  342.         return false, false
  343.       end
  344.     end
  345.   end
  346. end
  347.  
  348. local function isUserExisting(username)
  349.   -- Check if the username already exists in the database
  350.   for i = 1, #usersFile.Users do
  351.     if usersFile.Users[i] == username then
  352.       return true
  353.     end
  354.   end
  355.  
  356.   return false
  357. end
  358.  
  359. local function isBiolockExisting(biolockID)
  360.   -- Check if the bioclock ID already exists in the database
  361.   for i = 1, #usersFile.Biolock do
  362.     if usersFile.Biolock[i] == biolockID then
  363.       return true, i
  364.     end
  365.   end
  366.  
  367.   return false, -1
  368. end
  369.  
  370. local function isStringEmpty(string)
  371.   -- Function to check if a string is empty
  372.   return string:match("%S") == nil
  373. end
  374.  
  375. local function sleepCheck(seconds)
  376.   -- Sleep function that returns if bRun is false (The program needs to be stopped)
  377.   for i = 1, seconds * 10 do
  378.     sleep(0.1)
  379.  
  380.     if not bRun then
  381.       break
  382.     end
  383.   end
  384. end
  385.  
  386. local function userSetup(biolockID, biolockSide)
  387.   -- A biolock event has been queued, create/modify a user
  388.  
  389.   -- Def var
  390.   local bNotAccepted = true
  391.   local saveChanges = true
  392.   local bSettingOK = false
  393.  
  394.   -- Check if the user already exists to modify
  395.   local isAlreadyRegistered, registringID = isBiolockExisting(biolockID)
  396.   if not isAlreadyRegistered then
  397.     updateWindowTitle("userAPI - Create a new user")
  398.   else
  399.     updateWindowTitle("userAPI - Modify an existing user")
  400.  
  401.     nAccessLevel = usersFile.AccessLevel[registringID]
  402.   end
  403.  
  404.   while bNotAccepted do
  405.     -- Stage 1: Asking for a username
  406.     allowCloseButton(false)
  407.     updateStage(2)
  408.     updateTitle("User setup 1/4 - "..biolockID:sub(1, 4).."..."..biolockID:sub(biolockID:len() - 3, biolockID:len()))
  409.  
  410.     -- Draw the UI
  411.     term.setCursorPos(1, 5)
  412.     term.write("Enter your username")
  413.     if term.isColor() then
  414.       paintutils.drawLine(1, 6, 51, 6, colors.lightBlue)
  415.     else
  416.       paintutils.drawLine(1, 6, 51, 6, colors.white)
  417.       term.setTextColor(colors.black)
  418.     end
  419.  
  420.     -- Loop to check the username
  421.     bSettingOK = false
  422.     while not bSettingOK do
  423.       term.setCursorPos(1, 6)
  424.       local username = read()
  425.  
  426.       --[[
  427.           The username is verified if:
  428.             - The username is NOT empty
  429.             - The username IS empty AND a username is already set (if the user wants to edit/modify his/her settings)
  430.       ]]
  431.       if isStringEmpty(username) and isAlreadyRegistered then
  432.         sUsername = usersFile.Users[registringID]
  433.         bSettingOK = true
  434.       elseif isStringEmpty(username) and not isStringEmpty(sUsername) then
  435.         bSettingOK = true
  436.       elseif not isStringEmpty(username) then
  437.         sUsername = username
  438.         bSettingOK = true
  439.       end
  440.     end
  441.     term.setTextColor(colors.white)
  442.  
  443.     -- Stage 2: Asking for a password
  444.     updateStage(3)
  445.     updateTitle("User setup 2/4 - "..sUsername)
  446.  
  447.     -- Draw the UI
  448.     term.setBackgroundColor(colors.lightGray)
  449.     term.setCursorPos(1, 5)
  450.     term.write("Enter your password")
  451.     if term.isColor() then
  452.       paintutils.drawLine(1, 6, 51, 6, colors.lightBlue)
  453.     else
  454.       paintutils.drawLine(1, 6, 51, 6, colors.white)
  455.       term.setTextColor(colors.black)
  456.     end
  457.  
  458.     -- Loop to check the password
  459.     bSettingOK = false
  460.     while not bSettingOK do
  461.       term.setCursorPos(1, 6)
  462.       local password = read("*")
  463.  
  464.       --[[
  465.           The password is verified if:
  466.             - The password is NOT empty
  467.             - The password IS empty AND a password is already set (if the user wants to edit/modify his/her settings)
  468.       ]]
  469.       if isStringEmpty(password) and isAlreadyRegistered then
  470.         sHashPasswd = usersFile.Password[registringID]
  471.         bSettingOK = true
  472.       elseif isStringEmpty(password) and not isStringEmpty(sPasswd) then
  473.         bSettingOK = true
  474.       elseif not isStringEmpty(password) then
  475.         sHashPasswd = StringUtils.SHA1(password)
  476.         sPasswd = password
  477.         bSettingOK = true
  478.       end
  479.     end
  480.     term.setTextColor(colors.white)
  481.  
  482.     -- Stage 3: Asking for the access level
  483.     updateStage(4)
  484.     updateTitle("User setup 3/4 - "..sUsername)
  485.  
  486.     -- Draw the UI
  487.     term.setCursorPos(1, 5)
  488.     term.write("Set up your access level")
  489.     if term.isColor() then
  490.       paintutils.drawLine(16, 6, 35, 6, colors.lightBlue)
  491.       paintutils.drawPixel(15, 6, colors.blue)
  492.       paintutils.drawPixel(36, 6, colors.blue)
  493.     else
  494.       paintutils.drawLine(16, 6, 35, 6, colors.white)
  495.       paintutils.drawPixel(15, 6, colors.black)
  496.       paintutils.drawPixel(36, 6, colors.black)
  497.     end
  498.     term.setCursorPos(15, 6)
  499.     term.write("-")
  500.     term.setCursorPos(36, 6)
  501.     term.write("+")
  502.  
  503.     if term.isColor() then
  504.       paintutils.drawPixel(51, 6, colors.green)
  505.     else
  506.       paintutils.drawPixel(51, 6, colors.black)
  507.     end
  508.     term.setCursorPos(51, 6)
  509.     term.write(">")
  510.  
  511.     -- Manage the UI
  512.     manageStageFour()
  513.  
  514.     -- Generate a new token and check it's availability
  515.     local bGenerateToken = true
  516.     while bGenerateToken do
  517.       sToken = TOKEN_GEN()
  518.       bGenerateToken = not checkTokenAvailability(sToken)
  519.     end
  520.  
  521.     -- Stage 4: Verify the settings and modify them if necessary
  522.     updateStage(5)
  523.     updateTitle("User setup 4/4 - Verify your settings")
  524.  
  525.     -- Draw the UI
  526.     term.setCursorPos(1, 5)
  527.     term.write("Username: "..sUsername)
  528.     term.setCursorPos(1, 6)
  529.     term.write("Password: "..sPasswd:sub(1, 1).."***"..sPasswd:sub(sPasswd:len(), sPasswd:len()))
  530.     term.setCursorPos(1, 7)
  531.     term.write("Access level: "..nAccessLevel)
  532.     term.setCursorPos(1, 9)
  533.     term.write("Token: "..sToken:sub(1, 4).."..."..sToken:sub(sToken:len() - 3, sToken:len()))
  534.     term.setCursorPos(1, 10)
  535.     term.write("Password hash: "..sHashPasswd:sub(1, 4).."..."..sHashPasswd:sub(sHashPasswd:len() - 3, sHashPasswd:len()))
  536.     if term.isColor() then
  537.       paintutils.drawFilledBox(1, 17, 3, 19, colors.orange)
  538.     else
  539.       paintutils.drawFilledBox(1, 17, 3, 19, colors.white)
  540.       term.setTextColor(colors.black)
  541.     end
  542.     term.setCursorPos(2, 18)
  543.     term.write("<")
  544.     if term.isColor() then
  545.       paintutils.drawFilledBox(49, 17, 51, 19, colors.green)
  546.     else
  547.       paintutils.drawFilledBox(49, 17, 51, 19, colors.white)
  548.       term.setTextColor(colors.black)
  549.     end
  550.     term.setCursorPos(50, 18)
  551.     term.write(">")
  552.     if term.isColor() then
  553.       paintutils.drawFilledBox(24, 17, 26, 19, colors.red)
  554.     else
  555.       paintutils.drawFilledBox(24, 17, 26, 19, colors.black)
  556.       term.setTextColor(colors.white)
  557.     end
  558.     term.setCursorPos(25, 18)
  559.     term.write("X")
  560.  
  561.     -- Manage the UI, check if the user wants to accept, modify or abort
  562.     bNotAccepted, saveChanges = manageStageFive()
  563.   end
  564.  
  565.   -- Save changes if asked, create a new entry if the user isn't already existing
  566.   if saveChanges then
  567.     if not isAlreadyRegistered then
  568.       local lastID = getLastID()
  569.       lastID = lastID + 1
  570.  
  571.       table.insert(usersFile.Users, lastID, sUsername)
  572.       table.insert(usersFile.Password, lastID, sHashPasswd)
  573.       table.insert(usersFile.Biolock, lastID, biolockID)
  574.       table.insert(usersFile.AccessLevel, lastID, nAccessLevel)
  575.       table.insert(usersFile.Token, lastID, sToken)
  576.       table.insert(usersFile.Where, lastID, "unknown")
  577.     else
  578.       usersFile.Users[registringID] = sUsername
  579.       usersFile.Password[registringID] = sHashPasswd
  580.       usersFile.AccessLevel[registringID] = nAccessLevel
  581.       usersFile.Token[registringID] = sToken
  582.     end
  583.  
  584.     saveUsersFile()
  585.   end
  586.  
  587.   -- Reset the app, return to Stage 1
  588.   updateStage(1)
  589.   updateWindowTitle("userAPI - Database manager")
  590.   allowCloseButton(true)
  591.   resetPersonalData()
  592. end
  593.  
  594. -- Main loop
  595. local function loop()
  596.   while bRun do
  597.     -- Wait for an event
  598.     local event, p1, p2, p3, p4, p5 = os.pullEvent()
  599.  
  600.     if event == "mouse_click" then
  601.       -- Clicked on the X button
  602.       if p2 == 51 and p3 == 1 then
  603.         close()
  604.       end
  605.     elseif event == "peripheral_detach" then
  606.       -- Check if there's a biolock left
  607.       if not peripheral.find("biolock") then
  608.         close("No biolock(s) left")
  609.       end
  610.     elseif event == "biolock" then
  611.       -- A connected biolock has been triggered, start the user registration
  612.       userSetup(p1, p2)
  613.     elseif event == "key" then
  614.       if p1 == 19 then
  615.         -- R key pressed
  616.         os.reboot()
  617.       elseif p1 == 45 then
  618.         -- X key pressed
  619.         close()
  620.       end
  621.     end
  622.  
  623.   end
  624. end
  625.  
  626. -- Secondaries loops
  627. local function animate()
  628.   -- Title animation for Stage 1
  629.   while bRun do
  630.     if nStage == 1 then
  631.       updateTitle("Waiting for the biolock"..tAnimatedPoints[nAnimationStage])
  632.  
  633.       nAnimationStage = nAnimationStage + 1
  634.       if nAnimationStage > #tAnimatedPoints then
  635.         nAnimationStage = 1
  636.       end
  637.  
  638.       sleepCheck(1)
  639.     else
  640.       while nStage ~= 1 and bRun do
  641.         sleep(0.1)
  642.       end
  643.  
  644.       nAnimationStage = 1
  645.  
  646.       term.setBackgroundColor(colors.lightGray)
  647.       term.setCursorPos(1, 19)
  648.       term.write("Press R to reboot")
  649.     end
  650.   end
  651. end
  652.  
  653. -- First time init
  654. loadUsersFile()
  655. checkUsersFile()
  656.  
  657. updateWindowTitle("userAPI - Database manager")
  658. allowCloseButton(true)
  659.  
  660. clearWorkingArea()
  661. term.setBackgroundColor(colors.lightGray)
  662. term.setCursorPos(1, 19)
  663. term.write("Press R to reboot")
  664. term.setCursorPos(1, 3)
  665.  
  666. -- Start loops in parallel
  667. parallel.waitForAll(loop, animate)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement