0

Now writes images to temporary files and atomically moves in place*

* on systems with an atomic os.rename
This commit is contained in:
Andrew Brown
2012-02-26 00:14:58 -05:00
parent c759b20f8e
commit 19f6f136e4
3 changed files with 102 additions and 40 deletions

View File

@@ -144,11 +144,10 @@ directory.
jsondump = json.dumps(dump, indent=4)
with codecs.open(os.path.join(self.outputdir, 'overviewerConfig.js'), 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n")
with util.FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js")) as tmpfile:
with codecs.open(tmpfile, 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n")
# copy web assets into destdir:
global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets")
if not os.path.isdir(global_assets):
@@ -159,23 +158,16 @@ directory.
js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src")
if not os.path.isdir(js_src):
js_src = os.path.join(util.get_program_path(), "js_src")
with open(os.path.join(self.outputdir, "overviewer.js"), "w") as fout:
# first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js")) as f:
fout.write(f.read())
# now copy in the rest
for js in os.listdir(js_src):
if not js.endswith("overviewer.js") and js.endswith(".js"):
with open(os.path.join(js_src,js)) as f:
fout.write(f.read())
# do the same with the local copy, if we have it
# TODO
# if self.web_assets_path:
# util.mirror_dir(self.web_assets_path, self.outputdir)
with util.FileReplacer(os.path.join(self.outputdir, "overviewer.js")) as tmpfile:
with open(tmpfile, "w") as fout:
# first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js"), 'r') as f:
fout.write(f.read())
# now copy in the rest
for js in os.listdir(js_src):
if not js.endswith("overviewer.js") and js.endswith(".js"):
with open(os.path.join(js_src,js)) as f:
fout.write(f.read())
# helper function to get a label for the given rendermode
def get_render_mode_label(rendermode):
info = get_render_mode_info(rendermode)
@@ -193,7 +185,6 @@ directory.
versionstr = "%s (%s)" % (overviewer_version.VERSION, overviewer_version.HASH[:7])
index = index.replace("{version}", versionstr)
with codecs.open(os.path.join(self.outputdir, "index.html"), 'w', encoding='UTF-8') as output:
output.write(index)
with util.FileReplacer(indexpath) as indexpath:
with codecs.open(indexpath, 'w', encoding='UTF-8') as output:
output.write(index)

View File

@@ -28,6 +28,7 @@ from collections import namedtuple
from PIL import Image
from .util import iterate_base4, convert_coords, unconvert_coords, get_tiles_by_chunk
from .util import FileReplacer
from .optimizeimages import optimize_image
import c_overviewer
@@ -784,15 +785,16 @@ class TileSet(object):
logging.error("While attempting to delete corrupt image %s, an error was encountered. You will need to delete it yourself. Error was '%s'", path[1], e)
# Save it
if imgformat == 'jpg':
img.save(imgpath, quality=self.options['imgquality'], subsampling=0)
else: # png
img.save(imgpath)
if self.options['optimizeimg']:
optimize_image(imgpath, imgformat, self.options['optimizeimg'])
with FileReplacer(imgpath) as tmppath:
if imgformat == 'jpg':
img.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png
img.save(tmppath, "png")
if self.options['optimizeimg']:
optimize_image(tmppath, imgformat, self.options['optimizeimg'])
os.utime(imgpath, (max_mtime, max_mtime))
os.utime(tmppath, (max_mtime, max_mtime))
def _render_rendertile(self, tile):
"""Renders the given render-tile.
@@ -877,15 +879,16 @@ class TileSet(object):
draw.text((96,96), "c,r: %s,%s" % (col, row), fill='red')
# Save them
if self.imgextension == 'jpg':
tileimg.save(imgpath, quality=self.options['imgquality'], subsampling=0)
else: # png
tileimg.save(imgpath)
with FileReplacer(imgpath) as tmppath:
if self.imgextension == 'jpg':
tileimg.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png
tileimg.save(tmppath, "png")
if self.options['optimizeimg']:
optimize_image(imgpath, self.imgextension, self.options['optimizeimg'])
if self.options['optimizeimg']:
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
os.utime(imgpath, (max_chunk_mtime, max_chunk_mtime))
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))
def _get_chunks_for_tile(self, tile):
"""Get chunks that are relevant to the given render-tile

View File

@@ -178,6 +178,74 @@ def get_tiles_by_chunk(chunkcol, chunkrow):
return product(colrange, rowrange)
# Define a context manager to handle atomic renaming or "just forget it write
# straight to the file" depending on whether os.rename provides atomic
# overwrites.
# Detect whether os.rename will overwrite files
import tempfile
with tempfile.NamedTemporaryFile() as f1:
with tempfile.NamedTemporaryFile() as f2:
try:
os.rename(f1.name,f2.name)
except OSError:
renameworks = False
else:
renameworks = True
# re-make this file so it can be deleted without error
open(f1.name, 'w').close()
del tempfile,f1,f2
doc = """This class acts as a context manager for files that are to be written
out overwriting an existing file.
The parameter is the destination filename. The value returned into the context
is the filename that should be used. On systems that support an atomic
os.rename(), the filename will actually be a temporary file, and it will be
atomically replaced over the destination file on exit.
On systems that don't support an atomic rename, the filename returned is the
filename given.
If an error is encountered, the file is attempted to be removed, and the error
is propagated.
Example:
with FileReplacer("config") as configname:
with open(configout, 'w') as configout:
configout.write(newconfig)
"""
if renameworks:
class FileReplacer(object):
__doc__ = doc
def __init__(self, destname):
self.destname = destname
self.tmpname = destname + ".tmp"
def __enter__(self):
# rename works here. Return a temporary filename
return self.tmpname
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
# error
try:
os.remove(self.tmpname)
except Exception, e:
logging.warning("An error was raised, so I was doing "
"some cleanup first, but I couldn't remove "
"'%s'!", self.tmpname)
else:
# atomic rename into place
os.rename(self.tmpname, self.destname)
else:
class FileReplacer(object):
__doc__ = doc
def __init__(self, destname):
self.destname = destname
def __enter__(self):
return self.destname
def __exit__(self, exc_type, exc_val, exc_tb):
return
del renameworks
# Logging related classes are below
# Some cool code for colored logging: