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 |