Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created April 28, 2013 02:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/5475664 to your computer and use it in GitHub Desktop.
Save dermotbalson/5475664 to your computer and use it in GitHub Desktop.
Backup
--# Notes
--# Notes
--[[
Version 1.2 March 2013
by Ignatz and Codeslinger
This utility backs up and restores your projects for you, by storing them as images in your Dropbox folder.
Its features are as follows:
1. backs up up behind the scenes every time you change the version number
2. tells you how long it is since the last backup
3. restores your backup into a new project, tabs and all
INSTALLING IT
Copy this project to your Codea, named as Backup
BACKING UP
Include a line like this in your function setup()
b=Backup("MyProject Ver 100") -- NB no full stops or other "illegal" filename chars!
Also create a dependency to the Backup project
(click the + at upper right of screen, find Backup on the project list, and press it)
The utility will only back up when the string in brackets changes. It will create a new image with that name
in your Dropbox folder (you may need to sync to see it). The reason for not backing up every time you run, is that when you are developing, you will try a lot of stuff, and you may get into a mess. You will then want to go back to your last stable version. So the idea is that whenever you are at a checkpoint, make a new version name.
NB each time you run, the utility will tell you how long it is since you last backed up. You can turn this off by adding a second parameter, false, when calling Backup.
RESTORING
Create a new project to hold your restored project, and put this in function setup()
img=readImage("Dropbox:AAA")
r=Restore(img)
Now change the image name to the Dropbox file you want to restore
BEFORE YOU RUN, create a dependency to the Backup project
(click the + at upper right of screen, find Backup on the project list, and press it)
Now run, and when it's done, go back to the code and it should all be there.
--]]
--# Main
-- NEVER restore in this function, even for testing, because it may overwrite code
function setup()
--b=Backup("test302a",false,"Jeopardy")
end
--[[
Backup file format - version 1
3 bytes version number (currently = 001)
then for each tab...
char 1
tab name
char 2
tab text
char 3
final char is 0
--]]
--# Backup
Backup = class()
function Backup:init(title,remind,testproject)
local version="001"
if version=="001" then
local b=Backup001(title,remind,testproject)
end
end
--# Backup001
Backup001 = class()
--title is name of backup, backup only occurs when this changes
--remind parameter if set to false turns off the reminder of how long it has been since last backup
--testproject allows us to backup a named project instead of the open project, for use in debugging
function Backup001:init(title,remind,testproject)
self.version="001"
local t=readProjectData(title)
if t~=nil then --print reminder if backup exists and reminder not disabled
if remind~=false then
print("Last backup was",string.format("%.0f",os.difftime(os.time(),t)/60),"minutes ago")
end
else
self:Store(title,testproject) --do backup
saveProjectData(title,os.time()) --store date/time of backup
end
end
function Backup001:Store(title,testproject)
local txt=self:EncodeTabs(testproject)
--setup image
local n=string.len(txt)
local s=math.floor((n/3)^.5)+1
local img=image(s,s)
local row,col=0,1
local e={}
for i=0,n-1,3 do
for j=1,3 do
e[j]=string.byte(string.sub(txt,i+j,i+j)) or 0
end
row = row + 1
if row>s then row=1 col = col + 1 end
img:set(col,row,e[1],e[2],e[3],255)
end
local docName="Dropbox:"..title
saveImage(docName,nil)
saveImage(docName,img)
print(title.." - backup made")
--verify image by restoring and comparing result with original
img2=readImage(docName)
local r=Restore(img2,true)
if txt==r.RecoveredText then
print("Backup verified")
else
print("ERROR - backup faulty")
end
end
-- Private: Encode all tabs, including header.
-- Returns encoded string.
function Backup001:EncodeTabs(testproject)
local SOH, STX, ETX = "\001", "\002", "\003"
local encoded = self.version
local tabs
if testproject==nil then tabs = listProjectTabs() else tabs = listProjectTabs(testproject) end
for i,t in pairs(tabs) do
local tName=t if testproject~=nil then tName=testproject..":"..t end
encoded = encoded..SOH..t..STX..readProjectTab(tName)..ETX
end
encoded = encoded.."\000"
return encoded
end
--# Restore
Restore = class()
function Restore:init(img,test)
local r,g,b,a=img:get(1,1)
if string.char(r)..string.char(g)..string.char(b)=="001" then
local b=Restore001(img,test)
self.RecoveredText=b.RecoveredText
else
print("ERROR - Unknown version "..r)
end
end
--# Restore001
Restore001 = class()
--test=true if verifying a backup, we need to return the decoded string and not save any tabs
--if test is false or missing, this is a real restore, save the tabs
function Restore001:init(img,test)
self.version="001"
local txt=self:ReadImage(img)
if txt=="ERROR" then print("ERROR: I couldn't read the backup") return end
if test then self.RecoveredText=txt return end --if testing, return the string
print(self:DecodeAndSave(txt))
end
function Restore001:ReadImage(img)
if type(img)=="string" then img=readImage(img) end
rows,cols=img.height,img.width
t={}
local n=0
local r,g,b
for col=1,cols do
for row=1,rows do
r,g,b=img:get(col,row)
table.insert(t,string.char(r)) table.insert(t,string.char(g)) table.insert(t,string.char(b))
n = n + 3
end
end
--truncate at end of text
txt=table.concat(t)
local u=string.find(txt,"\000",nil,true)
if u>0 then txt=string.sub(txt,1,u) end
return txt
end
-- Private: Decode one tab of an encoded string.
--
-- encoded - encoded string.
-- start - start index inside |encoded|.
--
-- Returns tab name, tab contents, next index on success.
-- Returns nil on failure.
function Restore001:DecodeTab(encoded, start)
local SOH, STX, ETX = "\001", "\002", "\003"
local SOHidx = string.find(encoded, SOH, start)
local STXidx = string.find(encoded, STX, start)
local ETXidx = string.find(encoded, ETX, start)
-- assert some basic format properties
if not SOHidx or not STXidx or not ETXidx then
return nil
end
if SOHidx ~= start or ETXidx < STXidx then
return nil
end
-- decode
local tabname = string.sub(encoded, SOHidx + 1, STXidx - 1)
local contents = string.sub(encoded, STXidx + 1, ETXidx - 1)
local nextidx = ETXidx + 1
return tabname, contents, nextidx
end
-- Private: Decode all tabs of an encoded string and write them
-- into the project.
--
-- encoded - encoded string, including header.
-- Returns success or error message on failure.
-- Tabs may have been saved until that point.
function Restore001:DecodeAndSave(encoded)
local tabidx = string.len(self.version)+1
local result = "All done!"
repeat
-- check for end of data
if string.sub(encoded, tabidx, tabidx) == "\000" then
tabidx = nil
else
local tabname, contents
tabname, contents, tabidx = self:DecodeTab(encoded, tabidx)
if tabname ~= nil then
saveProjectTab(tabname, contents)
else
result = "ERROR - Decoding error, recovery incomplete"
end
end
until tabidx == nil
return result
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment