SHOW:
|
|
- or go back to the newest paste.
| 1 | ||
| 2 | --# Notes | |
| 3 | --# Notes | |
| 4 | --[[ | |
| 5 | Version 1.2 March 2013 | |
| 6 | by Ignatz and Codeslinger | |
| 7 | ||
| 8 | This utility backs up and restores your projects for you, by storing them as images in your Dropbox folder. | |
| 9 | Its features are as follows: | |
| 10 | 1. backs up up behind the scenes every time you change the version number | |
| 11 | 2. tells you how long it is since the last backup | |
| 12 | 3. restores your backup into a new project, tabs and all | |
| 13 | ||
| 14 | INSTALLING IT | |
| 15 | Copy this project to your Codea, named as Backup | |
| 16 | ||
| 17 | BACKING UP | |
| 18 | ||
| 19 | Include a line like this in your function setup() | |
| 20 | - | b=Backup("MyProject Ver 1.00")
|
| 20 | + | b=Backup("MyProject Ver 100") -- NB no full stops or other "illegal" filename chars!
|
| 21 | ||
| 22 | Also create a dependency to the Backup project | |
| 23 | (click the + at upper right of screen, find Backup on the project list, and press it) | |
| 24 | ||
| 25 | The utility will only back up when the string in brackets changes. It will create a new image with that name | |
| 26 | 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. | |
| 27 | ||
| 28 | 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. | |
| 29 | ||
| 30 | RESTORING | |
| 31 | ||
| 32 | Create a new project to hold your restored project, and put this in function setup() | |
| 33 | img=readImage("Dropbox:AAA")
| |
| 34 | r=Restore(img) | |
| 35 | ||
| 36 | Now change the image name to the Dropbox file you want to restore | |
| 37 | ||
| 38 | BEFORE YOU RUN, create a dependency to the Backup project | |
| 39 | (click the + at upper right of screen, find Backup on the project list, and press it) | |
| 40 | ||
| 41 | Now run, and when it's done, go back to the code and it should all be there. | |
| 42 | ||
| 43 | --]] | |
| 44 | ||
| 45 | --# Main | |
| 46 | ||
| 47 | -- NEVER restore in this function, even for testing, because it may overwrite code | |
| 48 | function setup() | |
| 49 | --b=Backup("test302a",false,"Jeopardy")
| |
| 50 | end | |
| 51 | ||
| 52 | --[[ | |
| 53 | ||
| 54 | Backup file format - version 1 | |
| 55 | ||
| 56 | 3 bytes version number (currently = 001) | |
| 57 | ||
| 58 | then for each tab... | |
| 59 | char 1 | |
| 60 | tab name | |
| 61 | char 2 | |
| 62 | tab text | |
| 63 | char 3 | |
| 64 | ||
| 65 | final char is 0 | |
| 66 | ||
| 67 | --]] | |
| 68 | ||
| 69 | ||
| 70 | --# Backup | |
| 71 | Backup = class() | |
| 72 | ||
| 73 | function Backup:init(title,remind,testproject) | |
| 74 | local version="001" | |
| 75 | if version=="001" then | |
| 76 | local b=Backup001(title,remind,testproject) | |
| 77 | end | |
| 78 | end | |
| 79 | ||
| 80 | ||
| 81 | ||
| 82 | --# Backup001 | |
| 83 | Backup001 = class() | |
| 84 | ||
| 85 | --title is name of backup, backup only occurs when this changes | |
| 86 | --remind parameter if set to false turns off the reminder of how long it has been since last backup | |
| 87 | --testproject allows us to backup a named project instead of the open project, for use in debugging | |
| 88 | function Backup001:init(title,remind,testproject) | |
| 89 | self.version="001" | |
| 90 | local t=readProjectData(title) | |
| 91 | if t~=nil then --print reminder if backup exists and reminder not disabled | |
| 92 | if remind~=false then | |
| 93 | print("Last backup was",string.format("%.0f",os.difftime(os.time(),t)/60),"minutes ago")
| |
| 94 | end | |
| 95 | else | |
| 96 | self:Store(title,testproject) --do backup | |
| 97 | saveProjectData(title,os.time()) --store date/time of backup | |
| 98 | end | |
| 99 | end | |
| 100 | ||
| 101 | function Backup001:Store(title,testproject) | |
| 102 | local txt=self:EncodeTabs(testproject) | |
| 103 | --setup image | |
| 104 | local n=string.len(txt) | |
| 105 | local s=math.floor((n/3)^.5)+1 | |
| 106 | local img=image(s,s) | |
| 107 | local row,col=0,1 | |
| 108 | local e={}
| |
| 109 | for i=0,n-1,3 do | |
| 110 | for j=1,3 do | |
| 111 | e[j]=string.byte(string.sub(txt,i+j,i+j)) or 0 | |
| 112 | end | |
| 113 | row = row + 1 | |
| 114 | if row>s then row=1 col = col + 1 end | |
| 115 | img:set(col,row,e[1],e[2],e[3],255) | |
| 116 | end | |
| 117 | local docName="Dropbox:"..title | |
| 118 | saveImage(docName,nil) | |
| 119 | saveImage(docName,img) | |
| 120 | print(title.." - backup made") | |
| 121 | --verify image by restoring and comparing result with original | |
| 122 | img2=readImage(docName) | |
| 123 | local r=Restore(img2,true) | |
| 124 | if txt==r.RecoveredText then | |
| 125 | print("Backup verified")
| |
| 126 | else | |
| 127 | print("ERROR - backup faulty")
| |
| 128 | end | |
| 129 | end | |
| 130 | ||
| 131 | -- Private: Encode all tabs, including header. | |
| 132 | -- Returns encoded string. | |
| 133 | function Backup001:EncodeTabs(testproject) | |
| 134 | local SOH, STX, ETX = "\001", "\002", "\003" | |
| 135 | local encoded = self.version | |
| 136 | local tabs | |
| 137 | if testproject==nil then tabs = listProjectTabs() else tabs = listProjectTabs(testproject) end | |
| 138 | for i,t in pairs(tabs) do | |
| 139 | local tName=t if testproject~=nil then tName=testproject..":"..t end | |
| 140 | encoded = encoded..SOH..t..STX..readProjectTab(tName)..ETX | |
| 141 | end | |
| 142 | encoded = encoded.."\000" | |
| 143 | return encoded | |
| 144 | end | |
| 145 | --# Restore | |
| 146 | Restore = class() | |
| 147 | ||
| 148 | function Restore:init(img,test) | |
| 149 | local r,g,b,a=img:get(1,1) | |
| 150 | if string.char(r)..string.char(g)..string.char(b)=="001" then | |
| 151 | local b=Restore001(img,test) | |
| 152 | self.RecoveredText=b.RecoveredText | |
| 153 | else | |
| 154 | print("ERROR - Unknown version "..r)
| |
| 155 | end | |
| 156 | end | |
| 157 | ||
| 158 | ||
| 159 | --# Restore001 | |
| 160 | Restore001 = class() | |
| 161 | ||
| 162 | --test=true if verifying a backup, we need to return the decoded string and not save any tabs | |
| 163 | --if test is false or missing, this is a real restore, save the tabs | |
| 164 | function Restore001:init(img,test) | |
| 165 | self.version="001" | |
| 166 | local txt=self:ReadImage(img) | |
| 167 | if txt=="ERROR" then print("ERROR: I couldn't read the backup") return end
| |
| 168 | if test then self.RecoveredText=txt return end --if testing, return the string | |
| 169 | print(self:DecodeAndSave(txt)) | |
| 170 | end | |
| 171 | ||
| 172 | function Restore001:ReadImage(img) | |
| 173 | if type(img)=="string" then img=readImage(img) end | |
| 174 | rows,cols=img.height,img.width | |
| 175 | t={}
| |
| 176 | local n=0 | |
| 177 | local r,g,b | |
| 178 | for col=1,cols do | |
| 179 | for row=1,rows do | |
| 180 | r,g,b=img:get(col,row) | |
| 181 | table.insert(t,string.char(r)) table.insert(t,string.char(g)) table.insert(t,string.char(b)) | |
| 182 | n = n + 3 | |
| 183 | end | |
| 184 | end | |
| 185 | --truncate at end of text | |
| 186 | txt=table.concat(t) | |
| 187 | local u=string.find(txt,"\000",nil,true) | |
| 188 | if u>0 then txt=string.sub(txt,1,u) end | |
| 189 | return txt | |
| 190 | end | |
| 191 | ||
| 192 | -- Private: Decode one tab of an encoded string. | |
| 193 | -- | |
| 194 | -- encoded - encoded string. | |
| 195 | -- start - start index inside |encoded|. | |
| 196 | -- | |
| 197 | -- Returns tab name, tab contents, next index on success. | |
| 198 | -- Returns nil on failure. | |
| 199 | function Restore001:DecodeTab(encoded, start) | |
| 200 | local SOH, STX, ETX = "\001", "\002", "\003" | |
| 201 | local SOHidx = string.find(encoded, SOH, start) | |
| 202 | local STXidx = string.find(encoded, STX, start) | |
| 203 | local ETXidx = string.find(encoded, ETX, start) | |
| 204 | -- assert some basic format properties | |
| 205 | if not SOHidx or not STXidx or not ETXidx then | |
| 206 | return nil | |
| 207 | end | |
| 208 | if SOHidx ~= start or ETXidx < STXidx then | |
| 209 | return nil | |
| 210 | end | |
| 211 | -- decode | |
| 212 | local tabname = string.sub(encoded, SOHidx + 1, STXidx - 1) | |
| 213 | local contents = string.sub(encoded, STXidx + 1, ETXidx - 1) | |
| 214 | local nextidx = ETXidx + 1 | |
| 215 | return tabname, contents, nextidx | |
| 216 | end | |
| 217 | ||
| 218 | -- Private: Decode all tabs of an encoded string and write them | |
| 219 | -- into the project. | |
| 220 | -- | |
| 221 | -- encoded - encoded string, including header. | |
| 222 | -- Returns success or error message on failure. | |
| 223 | -- Tabs may have been saved until that point. | |
| 224 | function Restore001:DecodeAndSave(encoded) | |
| 225 | local tabidx = string.len(self.version)+1 | |
| 226 | local result = "All done!" | |
| 227 | repeat | |
| 228 | -- check for end of data | |
| 229 | if string.sub(encoded, tabidx, tabidx) == "\000" then | |
| 230 | tabidx = nil | |
| 231 | else | |
| 232 | local tabname, contents | |
| 233 | tabname, contents, tabidx = self:DecodeTab(encoded, tabidx) | |
| 234 | if tabname ~= nil then | |
| 235 | saveProjectTab(tabname, contents) | |
| 236 | else | |
| 237 | result = "ERROR - Decoding error, recovery incomplete" | |
| 238 | end | |
| 239 | end | |
| 240 | until tabidx == nil | |
| 241 | return result | |
| 242 | end |