File: Storage.py

package info (click to toggle)
lodju 2.2-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 456 kB
  • ctags: 814
  • sloc: python: 4,698; ansic: 139; makefile: 64; sh: 21
file content (282 lines) | stat: -rw-r--r-- 9,050 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import errno
import os
import shutil


# The classes in this module implement on-disk storage for Lodju. A Lodju
# "document" consists of an index file, a bunch of untouched, original
# photos, and thumbnails. At some point in the future, there might be
# additional things stored in the document.
# 
# The document is implemented on disk as a directory. If the document
# is called "foo.lodju", then the disk structure looks like this:
# 
#     foo.lodju/
#     	lodju.xml   	    Index file
# 	orig/	    	    Original photo files
# 	    1.jpg   	    Photo with identifier 1
# 	    2.jpg   	    Photo with identifier 2
# 	thumb/	    	    Thumbnail files
# 	    1.jpg   	    Thumbnail for photo with id 1
# 	    2.jpg   	    Thumbnail for photo with id 2
# 
# When the user has "foo.lodju" open, they will make some changes to it:
# import photos, delete photos, change meta data, and so on. These changes
# need to actually happen on disk in the above directory structure only
# after "foo.lodju" is saved; in the mean while, they need to be stored
# in some other way. This way:
# 
#     foo.lodju/
#     	orig.new/   	    New imported files
# 	thumb.new/  	    New thumbnails
# 
# List of files to be deleted is kept in memory, as is the new set of meta
# data. When it comes time to save, Lodju will write a new lodju.xml,
# delete all files to be deleted from orig and thumb, and copy all new
# files from orig.new to orig, and from thumb.new to thumb.
# 
# This scheme does not allow multiple instances of Lodju making simultaneous
# modifications to foo.lodju. I doubt this matters. If this is wanted, the
# whole design of Lodju needs to be re-thought so that changes made in one
# instance are instantly visible in every other instance.  This probably
# indicates a server and database.
#
# When a new Storage object is created, it will create the corresponding
# directory on demand. This means that it may be created even before
# the user presses save. However, when the Storage.discard() method is
# called, if the directory didn't exist beforehand, it is deleted.


class Storage:

    known_dirs = [u"orig", u"orig.new", u"thumb", u"thumb.new"]
    known_files = [u"lodju.xml"]

    def __init__(self, filename):
    	assert type(filename) == type(u"")
    	if filename == u"":
	    self.filename = self.make_unnamed_dir()
	    self.dirs_should_exist = 0
	else:
	    self.filename = filename
	    self.dirs_should_exist = os.path.exists(self.filename)
    	self.filename = os.path.abspath(self.filename)
    	assert type(filename) == type(u"")
	self.remove_ids = []

    def make_unnamed_dir(self):
    	try:
	    os.mkdir(u"unnamed.lodju")
	except OSError:
	    pass
	else:
	    return u"unnamed.lodju"
    	for i in range(100):
	    name = u"unnamed-%d.lodju" % i
	    try:
	    	os.mkdir(name)
	    except OSError:
	    	pass
	    else:
	    	return name
    	raise OSError((errno.EACCES, os.strerror(errno.EACCES)))

    def mkdirs(self):
    	if not os.path.exists(self.filename):
	    os.makedirs(self.filename)
	for subdir in self.known_dirs:
	    name = os.path.join(self.filename, subdir)
	    if not os.path.exists(name):
		os.makedirs(name)

    def rename_new_files(self):
    	for basename in self.known_files:
	    fullname = os.path.join(self.filename, basename)
	    if os.path.isfile(fullname + u".new"):
	    	os.rename(fullname + u".new", fullname)

    def move_files_in_dotnew_dirs(self):
	for subdir in filter(lambda s: s[-4:] == u".new", self.known_dirs):
	    fromdir = os.path.join(self.filename, subdir)
	    todir = os.path.join(self.filename, subdir[:-4])
	    for basename in os.listdir(fromdir):
	    	os.rename(os.path.join(fromdir, basename),
		    	  os.path.join(todir, basename))

    def commit_removes(self):
    	for subdir in self.known_dirs:
	    subfull = os.path.join(self.filename, subdir)
	    idfulls = map(lambda s: os.path.join(subfull, s), self.remove_ids)
	    for name in idfulls:
	    	if os.path.exists(name):
		    os.remove(name)
    	self.remove_ids = []

    def save(self):
    	self.mkdirs()
	self.commit_removes()
	self.rename_new_files()
	self.move_files_in_dotnew_dirs()
	self.dirs_should_exist = 1

    def new_file(self, basename, contents):
    	assert basename in self.known_files
    	self.mkdirs()
    	f = open(os.path.join(self.filename, basename + u".new"), "w")
	f.write(contents.encode("utf-8"))
	f.close()

    def discard(self):
    	# Unconditionally remove all the unsaved stuff.
    	for known_file in self.known_files:
	    name = os.path.join(self.filename, known_file + u".new")
	    if os.path.isfile(name):
	    	os.remove(name)
    	for subdir in filter(lambda s: s[-4:] == u".new", self.known_dirs):
	    dirname = os.path.join(self.filename, subdir)
    	    if os.path.isdir(dirname):
		for basename in os.listdir(dirname):
		    name = os.path.join(dirname, basename)
		    if os.path.isfile(name):
			os.remove(name)

    	# Remove everything else, too, if there directory shouldn't exist.
	if not self.dirs_should_exist and os.path.isdir(self.filename):
	    for subdir in self.known_dirs:
	    	name = os.path.join(self.filename, subdir)
	    	if os.path.exists(name):
		    shutil.rmtree(name)
    	    for basename in self.known_files:
	    	fullname = os.path.join(self.filename, basename)
		if os.path.isfile(fullname):
		    os.remove(fullname)
		if os.path.isfile(fullname + u".new"):
		    os.remove(fullname + u".new")
	    os.rmdir(self.filename)

    def new_original(self, id):
    	self.mkdirs()
    	f = open(os.path.join(os.path.join(self.filename, u"orig.new"), 
	    	    	      id + u".new"),
	    	 "w")
    	return f

    def close_original(self, id, file):
    	file.close()
	dirname = os.path.join(self.filename, u"orig.new")
	os.rename(os.path.join(dirname, id + u".new"),
	    	  os.path.join(dirname, id))

    def new_thumbnail(self, id):
    	self.mkdirs()
    	f = open(os.path.join(os.path.join(self.filename, u"thumb.new"), 
	    	    	      id + u".new"),
	    	 "w")
    	return f

    def close_thumbnail(self, id, file):
    	file.close()
	dirname = os.path.join(self.filename, u"thumb.new")
	os.rename(os.path.join(dirname, id + u".new"),
	    	  os.path.join(dirname, id))

    def read_file(self, basename):
    	try:
	    f = open(os.path.join(self.filename, basename), "r")
	except IOError:
	    return u""
	data = f.read()
	f.close()
	return data

    def get_original_size(self, id):
    	filename = self.get_filename([u"orig", u"orig.new"], id)
	if filename:
	    try:
		ret = os.stat(filename)
	    except IOError:
	    	return 0
	    return ret.st_size
	else:
	    return 0

    def get_file(self, basename):
    	data = self.read_file(basename + ".new")
	if data == u"":
	    return self.read_file(basename)
	else:
	    return data

    def open_file(self, dirname, basename):
    	name = os.path.join(os.path.join(self.filename, dirname), basename)
    	try:
	    return open(name, "r")
	except IOError:
	    return None

    def open_image(self, dirname, id):
    	f = self.open_file(dirname + u".new", id)
	if f == None:
	    f = self.open_file(dirname, id)
	return f

    def get_filename(self, subdirs, id):
    	for subdir in subdirs:
	    name = os.path.join(os.path.join(self.filename, subdir), id)
	    if os.path.isfile(name):
		return name
    	return None

    def get_original_filename(self, id):
    	return self.get_filename([u"orig.new", u"orig"], id)

    def get_thumbnail_filename(self, id):
    	return self.get_filename([u"thumb.new", u"thumb"], id)

    def new_thumbnail_filename(self, id):
    	return os.path.join(os.path.join(self.filename, u"thumb.new"),
	    	    	    id + u".new")

    def new_thumbnail_commit(self, id):
    	new_name = self.new_thumbnail_filename(id)
	assert new_name[-4:] == u".new"
	os.rename(new_name, new_name[:-4])

    def get_original(self, id):
    	return self.open_image(u"orig", id)

    def get_thumbnail(self, id):
    	return self.open_image(u"thumb", id)

    def remove(self, id):
    	self.remove_ids.append(id)

    # XXX this needs to be done in a background task
    # Imagine the UI freezing while a hundred gigabytes of photos are
    # copied
    def save_as(self, new_filename):
    	assert type(new_filename) == type(u"")
    	st = Storage(new_filename)
	for file in self.known_files:
	    data = self.get_file(file)
	    if data:
	    	st.new_file(file, data)
    	for subdir in self.known_dirs:
	    fromdir = os.path.join(self.filename, subdir)
	    if subdir[-4:] == u".new":
	    	todir = os.path.join(new_filename, subdir[:-4])
	    else:
	    	todir = os.path.join(new_filename, subdir)
    	    if os.path.isdir(fromdir):
		for basename in os.listdir(fromdir):
		    fromfile = os.path.join(fromdir, basename)
		    tofile = os.path.join(todir, basename)
		    shutil.copyfile(fromfile, tofile)
    	st.save()
	self.filename = st.filename

    def rename(self, new_filename):
    	if os.path.isdir(self.filename):
	    os.rename(self.filename, new_filename)
    	self.filename = new_filename
	self.dirs_should_exist = 1