0

Merge pull request #1064 from overviewer/fscaps

Try to track the capabilities of our outputdir filesystem.
This commit is contained in:
Andrew Chin
2014-02-16 22:38:41 -05:00
3 changed files with 78 additions and 44 deletions

View File

@@ -25,7 +25,7 @@ from PIL import Image
import world import world
import util import util
from files import FileReplacer, mirror_dir from files import FileReplacer, mirror_dir, get_fs_caps
class AssetManager(object): class AssetManager(object):
"""\ """\
@@ -44,6 +44,8 @@ directory.
self.custom_assets_dir = custom_assets_dir self.custom_assets_dir = custom_assets_dir
self.renders = dict() self.renders = dict()
self.fs_caps = get_fs_caps(self.outputdir)
# look for overviewerConfig in self.outputdir # look for overviewerConfig in self.outputdir
try: try:
with open(os.path.join(self.outputdir, "overviewerConfig.js")) as c: with open(os.path.join(self.outputdir, "overviewerConfig.js")) as c:
@@ -148,7 +150,7 @@ directory.
# write out config # write out config
jsondump = json.dumps(dump, indent=4) jsondump = json.dumps(dump, indent=4)
with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js")) as tmpfile: with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js"), capabilities=self.fs_caps) as tmpfile:
with codecs.open(tmpfile, 'w', encoding='UTF-8') as f: with codecs.open(tmpfile, 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n") f.write("var overviewerConfig = " + jsondump + ";\n")
@@ -162,12 +164,12 @@ directory.
global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets") global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets")
if not os.path.isdir(global_assets): if not os.path.isdir(global_assets):
global_assets = os.path.join(util.get_program_path(), "web_assets") global_assets = os.path.join(util.get_program_path(), "web_assets")
mirror_dir(global_assets, self.outputdir) mirror_dir(global_assets, self.outputdir, capabilities=self.fs_caps)
if self.custom_assets_dir: if self.custom_assets_dir:
# Could have done something fancy here rather than just overwriting # Could have done something fancy here rather than just overwriting
# the global files, but apparently this what we used to do pre-rewrite. # the global files, but apparently this what we used to do pre-rewrite.
mirror_dir(self.custom_assets_dir, self.outputdir) mirror_dir(self.custom_assets_dir, self.outputdir, capabilities=self.fs_caps)
# write a dummy baseMarkers.js if none exists # write a dummy baseMarkers.js if none exists
if not os.path.exists(os.path.join(self.outputdir, "baseMarkers.js")): if not os.path.exists(os.path.join(self.outputdir, "baseMarkers.js")):
@@ -179,7 +181,7 @@ directory.
js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src") js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src")
if not os.path.isdir(js_src): if not os.path.isdir(js_src):
js_src = os.path.join(util.get_program_path(), "js_src") js_src = os.path.join(util.get_program_path(), "js_src")
with FileReplacer(os.path.join(self.outputdir, "overviewer.js")) as tmpfile: with FileReplacer(os.path.join(self.outputdir, "overviewer.js"), capabilities=self.fs_caps) as tmpfile:
with open(tmpfile, "w") as fout: with open(tmpfile, "w") as fout:
# first copy in js_src/overviewer.js # first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js"), 'r') as f: with open(os.path.join(js_src, "overviewer.js"), 'r') as f:
@@ -199,6 +201,6 @@ directory.
versionstr = "%s (%s)" % (util.findGitVersion(), util.findGitHash()[:7]) versionstr = "%s (%s)" % (util.findGitVersion(), util.findGitHash()[:7])
index = index.replace("{version}", versionstr) index = index.replace("{version}", versionstr)
with FileReplacer(indexpath) as indexpath: with FileReplacer(indexpath, capabilities=self.fs_caps) as indexpath:
with codecs.open(indexpath, 'w', encoding='UTF-8') as output: with codecs.open(indexpath, 'w', encoding='UTF-8') as output:
output.write(index) output.write(index)

View File

@@ -20,9 +20,50 @@ import shutil
import logging import logging
import stat import stat
default_caps = {"chmod_works": True, "rename_works": True}
def get_fs_caps(dir_to_test):
return {"chmod_works": does_chmod_work(dir_to_test),
"rename_works": does_rename_work(dir_to_test)
}
def does_chmod_work(dir_to_test):
"Detects if chmod works in a given directory"
# a CIFS mounted FS is the only thing known to reliably not provide chmod
if not os.path.isdir(dir_to_test):
return True
f1 = tempfile.NamedTemporaryFile(dir=dir_to_test)
try:
f1_stat = os.stat(f1.name)
os.chmod(f1.name, f1_stat.st_mode | stat.S_IRUSR)
chmod_works = True
logging.debug("Detected that chmods work in %r" % dir_to_test)
except OSError:
chmod_works = False
logging.debug("Detected that chmods do NOT work in %r" % dir_to_test)
return chmod_works
def does_rename_work(dir_to_test):
with tempfile.NamedTemporaryFile(dir=dir_to_test) as f1:
with tempfile.NamedTemporaryFile(dir=dir_to_test) as f2:
try:
os.rename(f1.name,f2.name)
except OSError:
renameworks = False
logging.debug("Detected that overwriting renames do NOT work in %r" % dir_to_test)
else:
renameworks = True
logging.debug("Detected that overwriting renames work in %r" % dir_to_test)
# re-make this file so it can be deleted without error
open(f1.name, 'w').close()
return renameworks
## useful recursive copy, that ignores common OS cruft ## useful recursive copy, that ignores common OS cruft
def mirror_dir(src, dst, entities=None): def mirror_dir(src, dst, entities=None, capabilities=default_caps):
'''copies all of the entities from src to dst''' '''copies all of the entities from src to dst'''
chmod_works = capabilities.get("chmod_works")
if not os.path.exists(dst): if not os.path.exists(dst):
os.mkdir(dst) os.mkdir(dst)
if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities))
@@ -38,10 +79,13 @@ def mirror_dir(src, dst, entities=None):
continue continue
if os.path.isdir(os.path.join(src,entry)): if os.path.isdir(os.path.join(src,entry)):
mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) mirror_dir(os.path.join(src, entry), os.path.join(dst, entry), capabilities=capabilities)
elif os.path.isfile(os.path.join(src,entry)): elif os.path.isfile(os.path.join(src,entry)):
try: try:
shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) if chmod_works:
shutil.copy(os.path.join(src, entry), os.path.join(dst, entry))
else:
shutil.copyfile(os.path.join(src, entry), os.path.join(dst, entry))
except IOError as outer: except IOError as outer:
try: try:
# maybe permission problems? # maybe permission problems?
@@ -51,24 +95,16 @@ def mirror_dir(src, dst, entities=None):
os.chmod(os.path.join(dst, entry), dst_stat.st_mode | stat.S_IWUSR) os.chmod(os.path.join(dst, entry), dst_stat.st_mode | stat.S_IWUSR)
except OSError: # we don't care if this fails except OSError: # we don't care if this fails
pass pass
shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) # try again; if this stills throws an error, let it propagate up
# if this stills throws an error, let it propagate up if chmod_works:
shutil.copy(os.path.join(src, entry), os.path.join(dst, entry))
else:
shutil.copyfile(os.path.join(src, entry), os.path.join(dst, entry))
# Define a context manager to handle atomic renaming or "just forget it write # Define a context manager to handle atomic renaming or "just forget it write
# straight to the file" depending on whether os.rename provides atomic # straight to the file" depending on whether os.rename provides atomic
# overwrites. # overwrites.
# Detect whether os.rename will overwrite files # Detect whether os.rename will overwrite files
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 doc = """This class acts as a context manager for files that are to be written
out overwriting an existing file. out overwriting an existing file.
@@ -89,16 +125,20 @@ with FileReplacer("config") as configname:
with open(configout, 'w') as configout: with open(configout, 'w') as configout:
configout.write(newconfig) configout.write(newconfig)
""" """
if renameworks: class FileReplacer(object):
class FileReplacer(object): __doc__ = doc
__doc__ = doc def __init__(self, destname, capabilities=default_caps):
def __init__(self, destname): self.caps = capabilities
self.destname = destname self.destname = destname
if self.caps.get("rename_works"):
self.tmpname = destname + ".tmp" self.tmpname = destname + ".tmp"
def __enter__(self): def __enter__(self):
if self.caps.get("rename_works"):
# rename works here. Return a temporary filename # rename works here. Return a temporary filename
return self.tmpname return self.tmpname
def __exit__(self, exc_type, exc_val, exc_tb): return self.destname
def __exit__(self, exc_type, exc_val, exc_tb):
if self.caps.get("rename_works"):
if exc_type: if exc_type:
# error # error
try: try:
@@ -109,18 +149,7 @@ if renameworks:
"'%s'!", self.tmpname) "'%s'!", self.tmpname)
else: else:
# copy permission bits, if needed # copy permission bits, if needed
if os.path.exists(self.destname): if self.caps.get("chmod_works") and os.path.exists(self.destname):
shutil.copymode(self.destname, self.tmpname) shutil.copymode(self.destname, self.tmpname)
# atomic rename into place # atomic rename into place
os.rename(self.tmpname, self.destname) 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

View File

@@ -31,7 +31,7 @@ from PIL import Image
from .util import roundrobin from .util import roundrobin
from . import nbt from . import nbt
from .files import FileReplacer from .files import FileReplacer, get_fs_caps
from .optimizeimages import optimize_image from .optimizeimages import optimize_image
import rendermodes import rendermodes
import c_overviewer import c_overviewer
@@ -357,6 +357,9 @@ class TileSet(object):
self.options['renderchecks'] = 2 self.options['renderchecks'] = 2
os.mkdir(self.outputdir) os.mkdir(self.outputdir)
# must wait until outputdir exists
self.fs_caps = get_fs_caps(self.outputdir)
if self.options['renderchecks'] == 2: if self.options['renderchecks'] == 2:
# Set forcerendertime so that upon an interruption the next render # Set forcerendertime so that upon an interruption the next render
# will continue where we left off. # will continue where we left off.
@@ -902,7 +905,7 @@ 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) 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 # Save it
with FileReplacer(imgpath) as tmppath: with FileReplacer(imgpath, capabilities=self.fs_caps) as tmppath:
if imgformat == 'jpg': if imgformat == 'jpg':
img.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0) img.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png else: # png
@@ -1006,7 +1009,7 @@ class TileSet(object):
#draw.text((96,96), "c,r: %s,%s" % (col, row), fill='red') #draw.text((96,96), "c,r: %s,%s" % (col, row), fill='red')
# Save them # Save them
with FileReplacer(imgpath) as tmppath: with FileReplacer(imgpath, capabilities=self.fs_caps) as tmppath:
if self.imgextension == 'jpg': if self.imgextension == 'jpg':
tileimg.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0) tileimg.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png else: # png