pazzesco

GHOSTY v0.9.4

Jul 23rd, 2013
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.37 KB | None | 0 0
  1. --- GHOSTY CLIENT/SERVER
  2. --- v0.9.4
  3. --- by Pazzesco
  4. ---
  5. ---
  6. --- DISCLAIMER:
  7. --- BY USING THIS SOFTWARE, YOU ACCEPT THE RISK OF ANY AND ALL LOSS, DAMAGE, OR UNSATISFACTORY PERFORMANCE OF THIS SOFTWARE
  8. --- OR PROGRAM RESTS WITH YOU AS THE USER. TO THE EXTENT PERMITTED BY LAW, NEITHER THE AUTHOR, NOR ANY PERSON EITHER
  9. --- EXPRESSLY OR IMPLICITLY, MAKES ANY REPRESENTATION OR WARRANTY REGARDING THE APPROPRIATENESS OF THE USE, OUTPUT, OR
  10. --- RESULTS OF THE USE OF THIS SOFTWARE OR PROGRAM IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY, BEING CURRENT OR
  11. --- OTHERWISE. IF YOU RELY UPON THIS SOFTWARE OR PROGRAM, YOU DO SO AT YOUR OWN RISK, AND YOU ASSUME THE RESPONSIBILITY FOR
  12. --- THE RESULTS. SHOULD THIS SOFTWARE OR PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL LOSSES, INCLUDING, BUT NOT
  13. --- LIMITED TO, ANY NECESSARY SERVICING, REPAIR OR CORRECTION OF ANY PROPERTY INVOLVED.
  14. ---
  15. ---
  16. ---
  17. ---
  18. ---
  19. ---
  20. --- ******************************
  21. --- ** Protocol command reference
  22. ---
  23. ---
  24. ---   *****************
  25. ---   ** Command syntax
  26. ---
  27. ---   <command><image-name>...
  28. ---   NOTE: The client command RAWxx does not follow this syntax, since it doesn't use an image-name header.
  29. ---
  30. ---
  31. ---   ************************************
  32. ---   ** Transfer commands (client/server)
  33. ---
  34. ---   PRG - Purges all files from client.
  35. ---   MDR - A mkdir command. Additional headers: <directory name>.
  36. ---   FIL - A file transfer notification. Actual file contents are transfered in a separate RAW message. Additional headers: <filename><transfer ID>
  37. ---   RAWxx - A raw file. xx = transfer ID. Note that there are no image headers, and the file contents are included at the end of the message payload.
  38. ---   FIN - Marks the end of file transfering. No more file transfer commands will be accepted.
  39. ---
  40. ---
  41. ---   ******************
  42. ---   ** Server Commands
  43. ---
  44. ---   CVR - Request server compatibility version.
  45. ---   UPD - Request a full file system update of a given image. Additional headers: <client-version>
  46. ---   PSH - Request to push an image to the server. Additional headers: <client-version>
  47. ---
  48. ---   ******************
  49. ---   ** Client Commands
  50. ---
  51. ---   CVR - Compatibility version response. Additional headers: <version>
  52. ---   NAM - Notification of server's name. Additional headers: <server-name>
  53. ---   POK - Push is accepted by server. Transfering can begin.
  54. ---   PNK - Push is rejected by server.
  55. ---
  56. ---
  57. ---
  58. --- ************
  59. --- ** Changelog
  60. ---
  61. ---   * 0.9.4
  62. ---     * Fixed a bug that wasn't allowing new images to upload to a server.
  63. ---
  64. ---   * 0.9.3
  65. ---     * Added program argument -ifile:<file>. Using this will install a local image file to the hard disk.
  66. ---     * Changed the program argument -mkimage:<file>. It now creates a single file of the hard disk's image instead of creating a blank image directory on the server.
  67. ---     * Added program argument -pbupdate. Using this will create an image of the computer's hard disk and upload it to Pastebin. Does NOT currently work with hard disks that have binary files.
  68. ---     * Added program argument -ipb:<pb-code>. Using this will download a GHOSTY image that was uploaded to Pastebin via GHOSTY.
  69. ---     * Added program argument -name, which if used, will place the server name in the lower right-hand corner of the screen.
  70. ---     * Added program argument -nopurge. When used, will solely create/replace files on the client when an image is loaded instead of first wiping the system clean.
  71. ---
  72. ---   * 0.9.2
  73. ---     * Fixed a bug that wasn't allowing channel changes using the -channel argument.
  74. ---
  75. ---   * 0.9.1
  76. ---     * Added program argument -upload, which is identical in effect to -update.
  77. ---     * Fixed a bug that was preventing normal computers from creating images on a server.
  78. ---
  79. ---
  80. ---
  81. --- ********
  82. --- ** Ideas
  83. ---
  84. ---   * Single-file retrieval; allow for single-file retrieval from a ghosty server.
  85. ---   * Per-image ignore list; images can contain a file that designates certain files/directories to ignore.
  86. ---   * An "all" image; an image that copies over every time underneath the normal image (for servers).
  87. ---   * Add in authentication for clients/servers.
  88.  
  89.  
  90. -- The GHOSTY version.
  91. local version = "0.9.4"
  92.  
  93. -- The GHOSTY compatibility version. This is the lowest version of GHOSTY that'll still work with the current version.
  94. local compatibility = "0.9.0"
  95.  
  96. -- The image file version.
  97. local fileVersion = "1.0.0"
  98.  
  99. -- The image file compatibility version. This is the lowest version of file that'll still work with the current version.
  100. local fileCompatibility = "1.0.0"
  101.  
  102. -- The default channel to transmit on.
  103. local channel = 7654
  104.  
  105. -- The different sides to look for the modem.
  106. local sides = {"left", "right", "top", "bottom", "back", "front"}
  107.  
  108. -- Files/directories to ignore when imaging.
  109. local ignoreFiles = {"/rom", "/disk", "/disk2", "/disk3", "/disk4", "/disk5", "/disk6"}
  110.  
  111. -- The default images directory.
  112. local imagesDir = "/images"
  113.  
  114. -- The client timeout when requesting an image.
  115. local clientTimeout = 60.0
  116.  
  117.  
  118. local modem
  119. local isClient = true
  120. local running = false
  121. local tIDCount = 1
  122. local hasPurged = false
  123. local lastFile = ""
  124. local transferID = ""
  125. local fileCount = 0
  126. local dirCount = 0
  127. local conflictedFileCount = 0
  128. local timedOut = false
  129. local curImage = ""
  130. local screenState = "main"
  131. local dialogYes = false
  132. local autoAccept = false
  133. local serverName = ""
  134. local keepFiles = false
  135. local locked = false
  136. local args = {...}
  137. local keys = {
  138.     f6=64,
  139.     enter=28,
  140.     upArrow=200,
  141.     downArrow=208,
  142.     leftArrow=203,
  143.     rightArrow=205
  144. }
  145. local banner = {
  146. "GHOSTY",
  147. "                       _---_     ",
  148. "                      / o o .    ",
  149. "                    /        .   ",
  150. "                    /        .   ",
  151. "                     /      .    "
  152. }
  153.  
  154. --NOTE: Had to dice up pastebin's address to avoid the spam filter picking it up.
  155. function charListToStr(charList) rtn = ""; for i,char in ipairs(charList) do rtn = rtn..char end; return rtn end
  156. local pastebinUploadAddress = charListToStr({'h','t','t','p',':','/','/','p','a','s','t','e','b','i','n','.','c','o','m','/','a','p','i','/','a','p','i','_','p','o','s','t','.','p','h','p'})
  157. local pastebinDownloadAddress = charListToStr({'h','t','t','p',':','/','/','p','a','s','t','e','b','i','n','.','c','o','m','/','r','a','w','.','p','h','p'})
  158.  
  159.  
  160.  
  161. --Draws the GUI to the terminal screen.
  162. function drawGUI()
  163.     --always draw the banner
  164.     term.clear()
  165.     for i,v in ipairs(banner) do
  166.         term.setCursorPos(1,i)
  167.         term.write(v)
  168.     end
  169.  
  170.     local line = #banner + 2
  171.    
  172.     -- version
  173.     term.setCursorPos(1,1)
  174.     termWriteRight("v"..version)
  175.    
  176.     -- server name
  177.     if serverName ~= "" then
  178.         term.setCursorPos(52 - string.len("Server: "..serverName), 18)
  179.         term.write("Server: "..serverName)
  180.     end
  181.    
  182.     -- channel
  183.     term.setCursorPos(52 - string.len("Channel: "..channel), 19)
  184.     term.write("Channel: "..channel)
  185.    
  186.     -- exit
  187.     --line = line + 4
  188.     term.setCursorPos(1,19)
  189.     term.write("[F6] Quit")
  190.    
  191.     if screenState == "main" then
  192.         line = drawMainScreen(line)
  193.     elseif screenState == "push" then
  194.         line = drawPushDialog(line)
  195.     elseif screenState == "pushWait" then
  196.         line = drawPushWait(line)
  197.     end
  198.    
  199. end
  200.  
  201.  
  202. function drawPushWait(line)
  203.     line = line + 2
  204.     term.setCursorPos(1,line)
  205.     term.write ("  Updating image "..curImage.."...")
  206.     term.setCursorBlink(true)
  207.     return line
  208. end
  209.  
  210.  
  211. function drawPushDialog(line)
  212.     line = line + 1
  213.     term.setCursorPos(1,line)
  214.     term.write("  Client request to update image: "..curImage)
  215.     line = line + 2
  216.     term.setCursorPos(1,line)
  217.     if dialogYes then
  218.         term.write ("  Accept?   [YES]   no")
  219.     else
  220.         term.write ("  Accept?    yes   [NO]")
  221.     end
  222.     term.setCursorBlink(false)
  223.     return line
  224. end
  225.  
  226.  
  227. function drawMainScreen(line)
  228.    
  229.     -- images list
  230.     line = line + 2
  231.     term.setCursorPos(1,line)
  232.     termWriteCenter("Images")
  233.     local lst = fs.list(imagesDir)
  234.     local timglst = ""
  235.     for i,fil in ipairs(lst) do
  236.         if fs.isDir(imagesDir.."/"..fil) then
  237.             if timglst ~= "" then
  238.                 timglst = timglst.."  "
  239.             end
  240.             timglst = timglst..fil
  241.         end
  242.     end
  243.     line = line + 2
  244.     term.setCursorPos(1, line)
  245.     termWriteCenter(timglst)
  246.    
  247.     term.setCursorBlink(false)
  248.     return line
  249. end
  250.  
  251.  
  252. function gui()
  253.     drawGUI()
  254.     while running do
  255.         local evt, p1, p2, p3, p4 = os.pullEvent()
  256.         if evt == "key" then
  257.        
  258.             if screenState == "main" then
  259.                 if p1 == keys["f6"] and not locked then
  260.                     term.clear()
  261.                     term.setCursorPos(1,1)
  262.                     return true
  263.                 end
  264.             elseif screenState == "push" then
  265.                 if (p1 == keys["leftArrow"] or p1 == keys["rightArrow"]) and not locked then
  266.                     dialogYes = not dialogYes
  267.                 end
  268.                 if p1 == keys["enter"] and not locked then
  269.                     if dialogYes then
  270.                         acceptPushRequest(curImage)
  271.                         screenState = "pushWait"
  272.                     else
  273.                         declinePushRequest(curImage)
  274.                         screenState = "main"
  275.                     end
  276.                 end
  277.             end
  278.            
  279.         end
  280.         drawGUI()
  281.     end
  282. end
  283.  
  284.  
  285. function termWriteCenter(msg)
  286.     local center = (52 - string.len(msg)) / 2
  287.     local oldX, oldY = term.getCursorPos()
  288.     term.setCursorPos(center, oldY)
  289.     term.write(msg)
  290. end
  291.  
  292.  
  293. function termWriteRight(msg)
  294.     local right = 52 - string.len(msg)
  295.     local oldX, oldY = term.getCursorPos()
  296.     term.setCursorPos(right, oldY)
  297.     term.write(msg)
  298. end
  299.  
  300.  
  301. function changeChannel(newChan)
  302.     if not modem then
  303.         return
  304.     end
  305.     newChan = tonumber(newChan)
  306.     if newChan >= 1 and newChan <= 65535 then
  307.         modem.close(channel)
  308.         channel = tonumber(newChan)
  309.         modem.open(channel)
  310.     end
  311. end
  312.  
  313.  
  314. function modemListen()
  315.    
  316.     while running do
  317.         local event, modemSide, senderChannel, replyChannel, message, senderDistance = os.pullEvent("modem_message")
  318.        
  319.         -- No incoming files. Continue with normal protocol.
  320.         local command = getHeader(message, 1)
  321.         local image = getHeader(message, 2)
  322.        
  323.         --- CLIENT/UPDATING SERVER COMMANDS
  324.         if isClient or curImage ~= "" then
  325.        
  326.             local opDir = "/"
  327.             if isClient == false then
  328.                 opDir = imagesDir.."/"..curImage
  329.             end
  330.        
  331.             if command == "PRG" and image == curImage then
  332.                 if not hasPurged then
  333.                     if not keepFiles or not isClient then
  334.                         purgeSystem(opDir)
  335.                     end
  336.                     hasPurged = true
  337.                 end
  338.                 if isClient == false then
  339.                     if not fs.exists(opDir) then
  340.                         makeDir(opDir)              --create the image
  341.                     end
  342.                 end
  343.             end
  344.            
  345.             if command == "FIL" and image == curImage then
  346.                 if hasPurged then
  347.                     lastFile = getHeader(message, 3)
  348.                     transferID = getHeader(message, 4)
  349.                     if not isClient then
  350.                         lastFile = opDir..lastFile
  351.                     end
  352.                 else
  353.                     print ("File header sent before purge. Aborted.")
  354.                     error()
  355.                 end
  356.             end
  357.            
  358.             if command == "MDR" and image == curImage then
  359.                 if hasPurged then
  360.                     local newDir = getHeader(message, 3)
  361.                     if not isClient then
  362.                         newDir = opDir..newDir
  363.                     end
  364.                     handleMakeDir(newDir, curImage)
  365.                 else
  366.                     print ("mkdir command sent before purge. Aborted.")
  367.                     error()
  368.                 end
  369.             end
  370.            
  371.             if string.sub(command, 1, 3) == "RAW" then
  372.                 if not isConflictingFilenameWithDir(lastFile) then
  373.                     local tID = string.sub(command, 4, string.len(command))
  374.                     if tID == transferID then
  375.                         local fileData, f2 = string.sub(message, string.len(command) + 3, string.len(message))
  376.                         receiveFile(lastFile, fileData)
  377.                         fileCount = fileCount + 1
  378.                         lastFile = ""
  379.                         transferID = 0
  380.                     end
  381.                 else
  382.                     conflictedFileCount = conflictedFileCount + 1
  383.                 end
  384.             end
  385.            
  386.             if command == "FIN" and image == curImage then
  387.                 if isClient then
  388.                     printImageInstallResults()
  389.                     break
  390.                 else
  391.                     curImage = ""
  392.                     screenState = "main"
  393.                     hasPurged = false
  394.                     drawGUI()
  395.                 end
  396.             end
  397.            
  398.         end
  399.        
  400.         --- SERVER COMMANDS
  401.         if not isClient then
  402.        
  403.             if command == "UPD" then
  404.                 handleUpdate(image)
  405.             end
  406.            
  407.             if command == "CVR" then
  408.                 if fs.exists(imagesDir.."/"..image) then
  409.                     if fs.isDir(imagesDir.."/"..image) then
  410.                         modem.transmit(channel, channel, "<CVR><"..image.."><"..compatibility..">")
  411.                     end
  412.                 end
  413.             end
  414.            
  415.             if command == "PSH" then
  416.                 handlePushRequest(image, getHeader(message, 3))
  417.             end
  418.            
  419.         --- CLIENT COMMANDS
  420.         else
  421.        
  422.             if command == "CVR" and image == curImage then
  423.                 if not hasPurged then
  424.                     local compVer = getHeader(message, 3)
  425.                     if isCompatibleVersions(version, compVer) then
  426.                         requestUpdate(curImage)
  427.                     else
  428.                         print ("Incompatible GHOSTY versions.")
  429.                         print ("Client version:               "..version)
  430.                         print ("Server compatibility version: "..compVer)
  431.                         break
  432.                     end
  433.                 end
  434.             end
  435.            
  436.             if command == "NAM" and image == curImage then
  437.                 serverName = getHeader(message, 3)
  438.                 print("Server name: "..serverName)
  439.             end
  440.            
  441.             if command == "PNK" and image == curImage then
  442.                 print("Image update was declined by server.")
  443.                 break
  444.             end
  445.            
  446.             if command == "POK" and image == curImage then
  447.                 print("Image update was accepted by server. Updating...")
  448.                 modem.transmit(channel, channel, "<PRG><"..curImage..">")
  449.                 sendFile("/", curImage)
  450.                 modem.transmit(channel, channel, "<FIN><"..curImage..">")
  451.                 print("Update sent.")
  452.                 break
  453.             end
  454.            
  455.         end
  456.        
  457.     end
  458. end
  459.  
  460.  
  461. --Extracts ghosty file data and writes its content to the computer's hard disk.
  462. function extractGhostyFileDataToDisk(fileData, purgeDir)
  463.     fVer, dat = getNextLine(fileData)
  464.     fVer = string.gsub(fVer, "File version:","")
  465.     if not isCompatibleVersions(fileVersion, fVer) then
  466.         print ("Incompatible file version.")
  467.         print ("Image file version:  "..fVer)
  468.         print ("Client file version: "..fileVersion)
  469.     end
  470.     fName, dat = getNextLine(dat)
  471.     fName = string.gsub(fName,"Image name:","")
  472.     fSizeTotal, dat = getNextLine(dat)
  473.     fSizeTotal = string.gsub(fSizeTotal, "File data size:", " ")
  474.     fSizeTotal = tonumber(fSizeTotal)
  475.     fCount, dat = getNextLine(dat)
  476.     fCount = string.gsub(fCount, "File count:", "")
  477.     fCount = tonumber(fCount)
  478.     dCount, dat = getNextLine(dat)
  479.     dCount = string.gsub(dCount, "Dir count:", "")
  480.     dCount = tonumber(dCount)
  481.     files = {}
  482.     dirs = {}
  483.     for i = 1, dCount, 1 do
  484.         dName, dat = getNextLine(dat)
  485.         dirs[i] = dName
  486.     end
  487.     for i = 1, fCount, 1 do
  488.         fName, dat = getNextLine(dat)
  489.         fSize, dat = getNextLine(dat)
  490.         fSize = tonumber(fSize)
  491.         files[i] = {fName, fSize}
  492.     end
  493.     --if fSizeTotal ~= string.len(dat) then
  494.     --  print("Image data size is corrupted; does image contain binary files?")
  495.     --  print("Reported data size: "..fSizeTotal)
  496.     --  print("Actual data size:   "..string.len(dat))
  497.     --  return false
  498.     --end
  499.     if purgeDir ~= nil then
  500.         purgeSystem(purgeDir)
  501.         hasPurged = true
  502.     end
  503.     for i = 1, dCount, 1 do
  504.         handleMakeDir(dirs[i])
  505.     end
  506.     datLoc = 1
  507.     for i = 1, fCount, 1 do
  508.         if not isConflictingFilenameWithDir(files[i][1]) then
  509.             if not isIgnoredFile(file) then
  510.                 fDat = string.sub(dat, datLoc, datLoc + tonumber(files[i][2]))
  511.                 receiveFile(files[i][1], fDat)
  512.                 fileCount = fileCount + 1
  513.             end
  514.         end
  515.         datLoc = datLoc + tonumber(files[i][2]) + 1
  516.     end
  517.     return true
  518. end
  519.  
  520.  
  521. --Creates ghosty file data from the hard disk.
  522. function makeGhostyFileDataFromDisk(ghostImageName)
  523.     local fList = fs.list("")
  524.     files=""
  525.     filesData=""
  526.     dirs=""
  527.     dCount = 0
  528.     fCount = 0
  529.     for i, file in ipairs(fList) do
  530.         file="/"..file
  531.         if not isIgnoredFile(file) then
  532.             if not fs.isDir(file) then
  533.                 local fdata = getFileData(file)
  534.                 files=files.."--"..file.."\n--"..string.len(fdata).."\n"
  535.                 filesData=filesData..fdata.."\n"
  536.                 fCount = fCount + 1
  537.             else
  538.                 dirs=dirs.."--"..file.."\n"
  539.                 dCount = dCount + 1
  540.             end
  541.         end
  542.     end
  543.     rtn =   "-->-----------------------------------<\n"
  544.           .."-->         GHOSTY image file         <\n"
  545.           .."-->-----------------------------------<\n"
  546.           .."--> This file was generated using     <\n"
  547.           .."--> GHOSTY for ComputerCraft          <\n"
  548.           .."-->                                   <\n"
  549.           .."--> WARNING: Do not edit these values <\n"
  550.           .."-->          unless you know what     <\n"
  551.           .."-->          you're doing!            <\n"
  552.           .."-->-----------------------------------<\n"
  553.           .."--File version:    "..fileCompatibility.."\n"
  554.           .."--Image name:      "..ghostImageName.."\n"
  555.           .."--File data size:  "..string.len(filesData).."\n"
  556.           .."--File count:      "..fCount.."\n"
  557.           .."--Dir count:       "..dCount.."\n"
  558.           .."--> \n"
  559.           .."-->---------------<\n"
  560.           .."-->  Directories  <\n"
  561.           .."-->---------------<\n"
  562.           .."--> \n"
  563.           ..dirs
  564.           .."--> \n"
  565.           .."-->---------<\n"
  566.           .."-->  Files  <\n"
  567.           .."-->---------<\n"
  568.           .."--> \n"
  569.           ..files
  570.           ..filesData.."\n"
  571.     return rtn
  572. end
  573.  
  574.  
  575. --Makes an image of the local disk and uploads it to pastebin.
  576. function uploadDiskToPastebin(ghostImageName)
  577.     if not http then
  578.         print ("To upload to Pastebin, the http API is required.")
  579.         print ("Contact your server administrator or set enableAPI_http to true in ComputerCraft.cfg")
  580.         return
  581.     end
  582.     fileData = makeGhostyFileDataFromDisk(ghostImageName)
  583.     print ("Attempting to connect to Pastebin...")
  584.     respAttempt = http.post(pastebinUploadAddress, "api_option=paste&api_dev_key=401a70bae40b60c47071e89a2d4856d6&api_paste_name="..textutils.urlEncode(ghostImageName).."&".."api_paste_code="..textutils.urlEncode(fileData))
  585.     if respAttempt then
  586.         resp = respAttempt.readAll()
  587.         respAttempt.close()
  588.         code = string.match( resp, "[^/]+$" )
  589.         if string.len(code) > 10 then
  590.             print ("Pastebin response: ")
  591.             print (code)
  592.             return
  593.         end
  594.         print ("Uploaded.")
  595.         print ("URL: "..resp)
  596.         print ("To install this image, run 'ghosty -ipb:"..code.."'")
  597.     else
  598.         print ("Failed to connect to Pastebin.")
  599.     end
  600. end
  601.  
  602.  
  603. --Downloads and installs a ghosty image from pastebin.
  604. function installImageFromPastebin(pastebinCode)
  605.     print ("Attempting to connect to Pastebin...")
  606.     respAttempt = http.get(pastebinDownloadAddress.."?i="..textutils.urlEncode(pastebinCode))
  607.     if respAttempt then
  608.         resp = respAttempt.readAll()
  609.         respAttempt.close()
  610.         if keepFiles then
  611.             extractGhostyFileDataToDisk(resp)
  612.         else
  613.             extractGhostyFileDataToDisk(resp, "/")
  614.         end
  615.         printImageInstallResults()
  616.     else
  617.         print ("Failed to connect to Pastebin.")
  618.     end
  619. end
  620.  
  621.  
  622. --Displays image installation results on terminal screen.
  623. function printImageInstallResults()
  624.     print(fileCount.." files copied.")
  625.     print(dirCount.." directories copied.")
  626.     if conflictedFileCount > 0 then
  627.         print (conflictedFileCount.." files failed to copy. A file name is conflicting with a directory name from the image.")
  628.     end
  629.     print("File system updated.")
  630. end
  631.  
  632.  
  633. --Returns the next line in a given string, followed by the remainder of the string (minus the line break)
  634. function getNextLine(strLines)
  635.     local loc
  636.     while string.sub(strLines,1,3) == "-->" do
  637.         loc = string.find(strLines, "\n")
  638.         if loc then
  639.             strLines = string.sub(strLines, loc+1)..""
  640.         else --nothing left, and last line is commented
  641.             return "", ""
  642.         end
  643.     end
  644.     loc = string.find(strLines, "\n")
  645.     if string.sub(strLines,1,2) == "--" then
  646.         strLines = string.sub(strLines,3)..""
  647.         if loc then
  648.             loc = loc - 2
  649.         end
  650.     end
  651.     if loc then
  652.         --NOTE: had to concat empty strings to prevent strange behaviour from returning incorrect results
  653.         return string.sub(strLines,1,loc-1).."", string.sub(strLines, loc+1)..""
  654.     end
  655.     return strLines.."", ""
  656. end
  657.  
  658.  
  659. ---Sends a file (or entire directory) over the channel.
  660. function sendFile(fileName, masterImage)
  661.     if not fs.exists(fileName) then
  662.         return
  663.     end
  664.     local cFile = fileName
  665.     if not isClient then
  666.         cFile = getClientFilename(fileName, masterImage)
  667.     end
  668.     if string.len(cFile) == 0 then
  669.         cFile = "/"
  670.     end
  671.     if isIgnoredFile(cFile) then
  672.         return
  673.     end
  674.     if fs.isDir(fileName) then
  675.         if cFile ~= "/" then
  676.             makeDirRemote(cFile, masterImage)
  677.         end
  678.         local dirList = fs.list(fileName)
  679.         for i,fil in ipairs(dirList) do
  680.             if fileName == "/" then
  681.                 sendFile(fileName..fil, masterImage)
  682.             else
  683.                 sendFile(fileName.."/"..fil, masterImage)
  684.             end
  685.         end
  686.     else
  687.         tIDCount = tIDCount + 1
  688.         local tMsg = "<FIL><"..masterImage.."><"..cFile.."><"..tIDCount..masterImage..">"
  689.         modem.transmit(channel, channel, tMsg)
  690.         local fileData = getFileData(fileName)
  691.         modem.transmit(channel, channel, "<RAW"..tIDCount..masterImage..">"..fileData)
  692.     end
  693.  
  694. end
  695.  
  696.  
  697. --Attempts to receive a file and write it to the local disk.
  698. function receiveFile(fileName, fileData)
  699.     if fs.exists(fileName) then
  700.         if not keepFiles then
  701.             print("File already sent; "..fileName)
  702.             return
  703.         end
  704.     end
  705.     writeFileData(fileName, fileData)
  706. end
  707.  
  708.  
  709. --Retrieves file data from a local file.
  710. --function getFileData(fileName)
  711. --  local fileHandle = io.open(fileName, "rb")
  712. --  local clock = os.clock() + 4
  713.     -- local fileData = {}
  714.     -- i = 1
  715.     -- for b in fileHandle.read do
  716.         -- fileData[i] = string.char(b)
  717.         -- if os.clock() >= clock then
  718.                 -- os.queueEvent("")
  719.                 -- coroutine.yield()
  720.             -- clock = os.clock() + 4
  721.         -- end
  722.         -- i = i + 1
  723.     -- end
  724.     -- fileHandle:close()
  725.     -- return table.concat(fileData)
  726. -- end
  727.  
  728.  
  729.  
  730. -- function writeFileData(fileName, fileDat)
  731.     -- local fileHandle = io.open(fileName, "wb")
  732.     -- if fileHandle == nil then
  733.         -- print("Error creating file: "..fileName)
  734.         -- error()
  735.     -- end
  736.     -- local clock = os.clock() + 4
  737.     -- for i=1,string.len(fileDat),1 do
  738.         -- b = string.byte(fileDat, i)
  739.         -- fileHandle:write(b)
  740.         -- if os.clock() >= clock then
  741.                 -- os.queueEvent("")
  742.                 -- coroutine.yield()
  743.             -- clock = os.clock() + 4
  744.         -- end
  745.     -- end
  746.     -- fileHandle.close()
  747. -- end
  748.  
  749.  
  750. function getFileData(fileName)
  751.     local fileHandle = io.open(fileName, "r")
  752.     local fileData = fileHandle:read("*a")
  753.     fileHandle:close()
  754.     return fileData
  755. end
  756.  
  757.  
  758. function writeFileData(fileName, fileDat)
  759.     local fileHandle = io.open(fileName, "w")
  760.     if fileHandle == nil then
  761.         print("Error creating file: "..fileName)
  762.         error()
  763.     end
  764.     fileHandle:write(fileDat)
  765.     fileHandle:close()
  766. end
  767.  
  768.  
  769. --Sends a remote signal to create a directory for the given image.
  770. function makeDirRemote(dirName, masterImage)
  771.     modem.transmit(channel, channel, "<MDR><"..masterImage.."><"..dirName..">")
  772. end
  773.  
  774.  
  775. --Attempts to make a given directory.
  776. function makeDir(dirName)
  777.     local mkOk, errorMsg = pcall(fs.makeDir,dirName)
  778.     if not mkOk then
  779.         print ("Error making directory: "..dirName)
  780.         print (errorMsg)
  781.         return false
  782.     end
  783.     return true
  784. end
  785.  
  786.  
  787. --Handles a directory creation request for the given image.
  788. function handleMakeDir(dirName, masterImage)
  789.     if isClient and keepFiles then
  790.         if fs.exists(dirName) then
  791.             dirName = ""
  792.         end
  793.     end
  794.     if dirName ~= "" and makeDir(dirName) then
  795.         dirCount = dirCount + 1
  796.     end
  797. end
  798.  
  799.  
  800. ---Handles an update request from a client.
  801. function handleUpdate(masterImage)
  802.     tDir = imagesDir.."/"..masterImage
  803.     if not fs.exists(tDir) then
  804.         return
  805.     end
  806.     if not fs.isDir(tDir) then
  807.         return
  808.     end
  809.     modem.transmit(channel, channel, "<PRG><"..masterImage..">")
  810.     if serverName ~= "" then
  811.         modem.transmit(channel, channel, "<NAM><"..masterImage.."><"..serverName..">")
  812.     end
  813.     sendFile(tDir, masterImage)
  814.     modem.transmit(channel, channel, "<FIN><"..masterImage..">")
  815. end
  816.  
  817.  
  818. --Handles a push request from a client.
  819. function handlePushRequest(masterImage, clientVer)
  820.     if curImage ~= "" then
  821.         --we're currently receiving another image; decline request
  822.         declinePushRequest(masterImage)
  823.     end
  824.     if not isCompatibleVersions(clientVer, compatibility) then
  825.         modem.transmit(channel, channel, "<CVR><"..masterImage.."><"..compatibility..">")
  826.         return
  827.     end
  828.     curImage = masterImage
  829.     if autoAccept then
  830.         screenState = "pushWait"
  831.         acceptPushRequest(masterImage)
  832.     else
  833.         dialogYes = false
  834.         screenState = "push"
  835.     end
  836.     drawGUI()
  837. end
  838.  
  839.  
  840. --Transmits a message, declining a client's push request.
  841. function declinePushRequest(masterImage)
  842.     modem.transmit(channel, channel, "<PNK><"..masterImage..">")
  843. end
  844.  
  845.  
  846. --Transmits a message, accepting a client's push request.
  847. function acceptPushRequest(masterImage)
  848.     if not fs.exists(imagesDir.."/"..masterImage) then
  849.         makeDir(imagesDir.."/"..masterImage)
  850.     end
  851.     if not fs.isDir(imagesDir.."/"..masterImage) then
  852.         print("Server images must be a directory.")
  853.         error()
  854.     end
  855.     modem.transmit(channel, channel, "<POK><"..masterImage..">")
  856. end
  857.  
  858.  
  859. --Sends an image push request to the server.
  860. function sendPushRequest(masterImage)
  861.     modem.transmit(channel, channel, "<PSH><"..masterImage.."><"..version..">")
  862. end
  863.  
  864.  
  865. ---Purges all files from the directory. WARNING - use with extreme caution.
  866. function purgeSystem(purgeDir)
  867.     if purgeDir == "/" then
  868.         purgeDir=""
  869.     end
  870.     local dirList = fs.list(purgeDir)
  871.     for i,fil in ipairs(dirList) do
  872.         if isIgnoredFile(purgeDir.."/"..fil) == false then
  873.             local delOk, errorMsg = pcall(fs.delete,purgeDir.."/"..fil)
  874.             if not delOk then
  875.                 print ("Error purging file: "..purgeDir.."/"..fil)
  876.                 print (errorMsg)
  877.                 error()
  878.             end
  879.         end
  880.     end
  881. end
  882.  
  883.  
  884. function isIgnoredFile(fileName)
  885.     for j,ignore in ipairs(ignoreFiles) do
  886.         if ignore == fileName then
  887.             return true
  888.         end
  889.     end
  890.     return false
  891. end
  892.  
  893.  
  894. function requestUpdate(masterImage)
  895.     modem.transmit(channel, channel, "<UPD><"..masterImage.."><"..version..">")
  896. end
  897.  
  898.  
  899. ---Gets a header surrounded in <> of a given position, starting at 1.
  900. function getHeader(cmdStr, cmdPos)
  901.     local curPos = 1
  902.     for cmd in string.gmatch(cmdStr, "<(.-)>") do
  903.         if curPos == cmdPos then
  904.             return cmd
  905.         end
  906.         curPos = curPos + 1
  907.     end
  908.     return ""
  909. end
  910.  
  911.  
  912. function getClientFilename(serverFilename, imageName)
  913.     local rtn = string.gsub(serverFilename, imagesDir, "", 1)
  914.     rtn = string.gsub(rtn, "/"..imageName, "", 1)
  915.     return rtn
  916. end
  917.  
  918. --Gets a directory name, stripping off the file name and trailing "/". If the root directory is returned, "/" is returned.
  919. function getDirFromFilename(fileName)
  920.     rtn = string.sub(fileName, 1, string.len(fileName)-string.find(string.reverse(fileName),"/",1))
  921.     if rtn == "" then
  922.         return "/"
  923.     end
  924.     return rtn
  925. end
  926.  
  927.  
  928. --Checks the current file system to see if a hypothetical absolute file location would
  929. --conflict with the file system. This would occur if a file exists that is the name of
  930. --one of the directories in the hypothetical file.
  931. function isConflictingFilenameWithDir(fileName)
  932.     dirName = fileName
  933.     while true do
  934.         dirName = getDirFromFilename(dirName)
  935.         if dirName == "/" then
  936.             return false
  937.         end
  938.         if fs.exists(dirName) then
  939.             if not fs.isDir(dirName) then
  940.                 return true
  941.             end
  942.         end
  943.     end
  944.     error() --shouldn't reach here
  945. end
  946.  
  947.  
  948. --Requests the server for its compatibility version.
  949. function requestCompatibilityVersion(masterImage)
  950.     modem.transmit(channel, channel, "<CVR><"..masterImage..">")
  951. end
  952.  
  953.  
  954. --Checks if a version of something meets compatibility requirements.
  955. --Format: major.minor.revision
  956. function isCompatibleVersions(vers, comp)
  957.     versList = getVersionAsList(vers)
  958.     compList = getVersionAsList(comp)
  959.     for i=0,2,1 do
  960.         if versList[i] < compList[i] then
  961.             return false
  962.         end
  963.         if versList[i] > compList[i] then
  964.             return true
  965.         end
  966.     end
  967.     return true
  968. end
  969.  
  970.  
  971. --Converts a string containing a version number into a list of numbers, with the major version as the first index.
  972. function getVersionAsList(vers)
  973.     --had to use a round-about way to split this string since gmatch was giving some problems
  974.     verList = {0,0,0}
  975.     index = 0
  976.     s = ""
  977.     for i = 1,string.len(vers),1 do
  978.         local ts = string.sub(vers, i, i)
  979.         if ts ~= "." then
  980.             s = s..ts
  981.         end
  982.         if  ts == "." or i == string.len(vers) then
  983.             verList[index] = tonumber(s)
  984.             index = index + 1
  985.             s = ""
  986.         end
  987.     end
  988.     if (index ~= 3) then
  989.         return {0, 0, 0}
  990.     end
  991.     return verList
  992. end
  993.  
  994.  
  995. --Setup used by both client and server.
  996. function setup()
  997.     -- prevent user from terminating
  998.     os.pullEvent = os.pullEventRaw
  999.  
  1000.     for i,side in ipairs(sides) do
  1001.         if peripheral.getType(side) == "modem" then
  1002.             modem = peripheral.wrap(side)
  1003.             modem.open(channel)
  1004.             running = true
  1005.         end
  1006.     end
  1007.     if not modem then
  1008.         print("No modem attached.")
  1009.         error()
  1010.     end
  1011. end
  1012.  
  1013.  
  1014. --Prepares the server's images directory, and also checks for validity.
  1015. function prepareImagesDir()
  1016.     if not fs.exists(imagesDir) then
  1017.         fs.makeDir(imagesDir)
  1018.     elseif not fs.isDir(imagesDir) then
  1019.         print (imagesDir.." is not a directory! Aborted.")
  1020.         error()
  1021.     end
  1022. end
  1023.  
  1024.  
  1025. --Shutdown performed by both client and server.
  1026. function shutdown()
  1027.     if modem then
  1028.         modem.close(channel)
  1029.     end
  1030. end
  1031.  
  1032.  
  1033. --Timeout function that sleeps for so long before returning.
  1034. function waitForTimeout()
  1035.     os.sleep(clientTimeout)
  1036.     timedOut = true
  1037. end
  1038.  
  1039.  
  1040. --Displays the utility help.
  1041. function showHelp()
  1042.     print ("GHOSTY v"..version.." disk cloning utility")
  1043.     print ("")
  1044.     print ("Arguments: ")
  1045.     print ("-i:<image>       Requests an image from server. If")
  1046.     print ("                 found, the image is installed.")
  1047.     print ("-update:<image>  Updates server's image with this")
  1048.     print ("                 client's hard disk.")
  1049.     print ("-s               Hosts a GHOSTY server.")
  1050.     print ("-images:<dir>    Sets the root images directory")
  1051.     print ("                 for the server.")
  1052.     print ("-a               Auto-accept images from clients.")
  1053.     print ("-channel:<#>     Sets the modem channel.")
  1054.     print ("-mkimage:<file>  Creates an image file of the hard")
  1055.     print ("                 disk.")
  1056.     print ("-ifile:<file>    Installs an image from a file.")
  1057.     print ("-name:<name>     Displays a name on the server.")
  1058.     print ("-nopurge         Keeps current files on client.")
  1059.     print ("                 Image files replace client files.")
  1060.     print ("-pbupdate        Uploads hard disk's image to")
  1061.     print ("                 Pastebin.")
  1062.     print ("-ipb:<code>      Installs image from Pastebin.")
  1063. end
  1064.  
  1065.  
  1066. function main()
  1067.    
  1068.     local clientPush = false
  1069.  
  1070.     --handle program arguments
  1071.     if table.getn(args) > 0 then
  1072.    
  1073.         local majorActions = 0
  1074.    
  1075.         --Primary arguments
  1076.         for i,arg in ipairs(args) do
  1077.             if arg == "-server" or arg == "-s" then
  1078.                 isClient = false
  1079.                 majorActions = majorActions + 1
  1080.             end
  1081.             if arg == "-a" then
  1082.                 autoAccept = true
  1083.             end
  1084.             if string.sub(arg, 1, 5) == "-ipb:" then
  1085.                 local tpbcode = string.sub(arg,6,string.len(arg))
  1086.                 installImageFromPastebin(tpbcode)
  1087.                 return
  1088.             end
  1089.             if arg == "-pbupdate" or arg == "-pbupload" then
  1090.                 uploadDiskToPastebin("GHOSTY_image")
  1091.                 return
  1092.             end
  1093.             if string.sub(arg, 1, 3) == "-i:" then
  1094.                 curImage = string.sub(arg,4,string.len(arg))
  1095.                 majorActions = majorActions + 1
  1096.             end
  1097.             if string.sub(arg, 1, 8) == "-images:" then
  1098.                 local tdir = string.sub(arg,9,string.len(arg))
  1099.                 if not fs.exists(tdir) then
  1100.                     print ("Cannot set images directory: no such directory!")
  1101.                     return
  1102.                 end
  1103.                 if not fs.isDir(tdir) then
  1104.                     print ("Cannot set images directory: not a directory!")
  1105.                     return
  1106.                 end
  1107.                 imagesDir = tdir
  1108.             end
  1109.             if string.sub(arg, 1, 8) == "-update:" or string.sub(arg, 1, 8) == "-upload:" then
  1110.                 curImage = string.sub(arg,9,string.len(arg))
  1111.                 if curImage == "" then
  1112.                     showHelp()
  1113.                     return
  1114.                 end
  1115.                 clientPush = true
  1116.                 majorActions = majorActions + 1
  1117.             end
  1118.             if string.sub(arg, 1, 9) == "-channel:" then
  1119.                 local tnum = tonumber(string.sub(arg,10,string.len(arg)))
  1120.                 if tnum == nil then
  1121.                     print ("Not a valid channel number.")
  1122.                     return
  1123.                 end
  1124.                 if tnum < 1 or tnum > 65535 then
  1125.                     print ("Out-of-range channel number.")
  1126.                     return
  1127.                 end
  1128.                 channel = tnum
  1129.             end
  1130.             if string.sub(arg, 1, 6) == "-name:" then
  1131.                 serverName = string.sub(arg,7,string.len(arg))
  1132.             end
  1133.             if arg == "help" or arg == "-help" or arg == "/help" or arg == "?" or arg == "/?" or arg == "-?" then
  1134.                 showHelp()
  1135.                 return
  1136.             end
  1137.         end
  1138.        
  1139.         --Secondary arguments (reliant on primary)
  1140.         for i,arg in ipairs(args) do
  1141.             if string.sub(arg, 1, 8) == "-nopurge" and isClient then
  1142.                 keepFiles = true
  1143.             end
  1144.         end
  1145.        
  1146.         --Tertiary arguments (reliant on secondary)
  1147.         for i,arg in ipairs(args) do
  1148.             if string.sub(arg, 1, 9) == "-mkimage:" then
  1149.                 local timgfile = string.sub(arg, 10)
  1150.                 if timgfile == "" then
  1151.                     print("Usage: -mkimage:<image-filename>")
  1152.                     print("Generates an image file of the local hard disk.")
  1153.                     return
  1154.                 end
  1155.                 local fildat = makeGhostyFileDataFromDisk("GHOSTY_image")
  1156.                 local fh = io.open(timgfile,"w")
  1157.                 fh:write(fildat)
  1158.                 fh:close()
  1159.                 print ("Image created as file "..timgfile)
  1160.                 return
  1161.             end
  1162.             if string.sub(arg, 1, 7) == "-ifile:" then
  1163.                 local timgfile = string.sub(arg, 8)
  1164.                 local fh = io.open(timgfile,"r")
  1165.                 if not fh then
  1166.                     print ("Cannot open image file: "..timgfile)
  1167.                     return
  1168.                 end
  1169.                 local fildat = fh:read("*a")
  1170.                 fh:close()
  1171.                 if keepFiles then
  1172.                     extractGhostyFileDataToDisk(fildat)
  1173.                 else
  1174.                     extractGhostyFileDataToDisk(fildat, "/")
  1175.                 end
  1176.                 print ("Image installed.")
  1177.                 return
  1178.             end
  1179.         end
  1180.        
  1181.         if majorActions > 1 then
  1182.             print("Too many arguments provided.")
  1183.             return
  1184.         end
  1185.        
  1186.         if majorActions < 1 then
  1187.             print("Not enough arguments provided.")
  1188.             return
  1189.         end
  1190.        
  1191.     else
  1192.         showHelp()
  1193.         return
  1194.     end
  1195.     if curImage == "" and isClient then
  1196.         showHelp()
  1197.         return
  1198.     end
  1199.    
  1200.     setup()
  1201.    
  1202.     if not isClient then
  1203.         prepareImagesDir()
  1204.         parallel.waitForAny(gui, modemListen)
  1205.     else
  1206.         print("GHOSTY v"..version.." client")
  1207.         print("Image: "..curImage)
  1208.         if clientPush then
  1209.             print("Requesting to update image on channel "..channel.."...")
  1210.             sendPushRequest(curImage)
  1211.         else
  1212.             print("Requesting image on channel "..channel.."...")
  1213.             requestCompatibilityVersion(curImage)
  1214.         end
  1215.         parallel.waitForAny(waitForTimeout, modemListen)
  1216.         if timedOut then
  1217.             print ("Timed out waiting for server.")
  1218.         end
  1219.     end
  1220.    
  1221.     shutdown()
  1222.    
  1223. end
  1224.  
  1225.  
  1226.  
  1227.  
  1228. main()
  1229. sleep(0)
Advertisement
Add Comment
Please, Sign In to add comment