Advertisement
Vaktus_

PringleSentrix IRC Bot

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