Now writes images to temporary files and atomically moves in place*
* on systems with an atomic os.rename
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user