pazzesco

GHOSTY v0.9.3

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