Created
April 28, 2013 02:48
-
-
Save dermotbalson/5475664 to your computer and use it in GitHub Desktop.
Backup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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