From 344b111b80ee48e08576bdbedb0a076248153335 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 17 Apr 2011 01:07:05 +0900 Subject: [PATCH] centralize all tmp dir access --- anki/latex.py | 20 +++++++++----------- anki/media.py | 9 ++++----- anki/sound.py | 27 +++++++++------------------ anki/sync.py | 11 ++++------- anki/utils.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 72 insertions(+), 44 deletions(-) diff --git a/anki/latex.py b/anki/latex.py index 82c2e0e85..557a72295 100644 --- a/anki/latex.py +++ b/anki/latex.py @@ -2,8 +2,8 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import re, tempfile, os, sys, shutil, cgi, subprocess -from anki.utils import checksum, call +import re, os, sys, shutil, cgi, subprocess +from anki.utils import checksum, call, namedtmp, tmpdir, isMac from anki.hooks import addHook from htmlentitydefs import entitydefs from anki.lang import _ @@ -17,10 +17,8 @@ regexps = { "math": re.compile(r"\[\$\$\](.+?)\[/\$\$\]", re.DOTALL | re.IGNORECASE), } -tmpdir = tempfile.mkdtemp(prefix="anki") - # add standard tex install location to osx -if sys.platform == "darwin": +if isMac: os.environ['PATH'] += ":/usr/texbin" def stripLatex(text): @@ -76,17 +74,17 @@ def _buildImg(deck, latex, fname): latex + "\n" + deck.conf["latexPost"]) # write into a temp file - log = open(os.path.join(tmpdir, "latex_log.txt"), "w+") - texpath = os.path.join(tmpdir, "tmp.tex") - texfile = file(texpath, "w") + log = open(namedtmp("latex_log.txt"), "w") + texfile = file(namedtmp("tmp.tex"), "w") texfile.write(latex) texfile.close() # make sure we have a valid mediaDir mdir = deck.media.dir(create=True) oldcwd = os.getcwd() + png = namedtmp("tmp.png") try: # generate dvi - os.chdir(tmpdir) + os.chdir(tmpdir()) if call(latexCmd + ["tmp.tex"], stdout=log, stderr=log): return _errMsg("latex") # and png @@ -94,7 +92,7 @@ def _buildImg(deck, latex, fname): stdout=log, stderr=log): return _errMsg("dvipng") # add to media - shutil.copy2(os.path.join(tmpdir, "tmp.png"), + shutil.copy2(png, os.path.join(mdir, fname)) return finally: @@ -103,7 +101,7 @@ def _buildImg(deck, latex, fname): def _errMsg(type): msg = (_("Error executing %s.") % type) + "
" try: - log = open(os.path.join(tmpdir, "latex_log.txt")).read() + log = open(namedtmp("latex_log.txt")).read() if not log: raise Exception() msg += "
" + cgi.escape(log) + "
" diff --git a/anki/media.py b/anki/media.py index 40ff64f34..c32b19860 100644 --- a/anki/media.py +++ b/anki/media.py @@ -2,9 +2,9 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import os, shutil, re, urllib, urllib2, time, tempfile, unicodedata, \ +import os, shutil, re, urllib, urllib2, time, unicodedata, \ urllib, sys -from anki.utils import checksum, intTime +from anki.utils import checksum, intTime, namedtmp, isWin from anki.lang import _ class MediaRegistry(object): @@ -111,7 +111,7 @@ If the same name exists, compare checksums.""" # problem is more complicated - if we percent-escape as utf8 it fixes # some images but breaks others. When filenames are normalized by # dropbox they become unreadable if we escape them. - if sys.platform.startswith("win32"): + if isWin: return string def repl(match): tag = match.group(1) @@ -212,12 +212,11 @@ If the same name exists, compare checksums.""" for f in mediaFiles(txt, remote=True): refs[f] = True - tmpdir = tempfile.mkdtemp(prefix="anki") failed = [] passed = [] for c, link in enumerate(refs.keys()): try: - path = os.path.join(tmpdir, os.path.basename(link)) + path = namedtmp(os.path.basename(link)) url = urllib2.urlopen(link) open(path, "wb").write(url.read()) newpath = copyToMedia(self.deck, path) diff --git a/anki/sound.py b/anki/sound.py index fd942e9b6..d0523de82 100644 --- a/anki/sound.py +++ b/anki/sound.py @@ -3,8 +3,9 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import re, sys, threading, time, subprocess, os, signal, errno, atexit -import tempfile, shutil +import shutil from anki.hooks import addHook, runHook +from anki.utils import namedtmp, tmpdir, isWin, isMac # Shared utils ########################################################################## @@ -30,23 +31,18 @@ processingChain = [ ["lame", "rec.wav", processingDst, "--noreplaygain", "--quiet"], ] -tmpdir = None - # don't show box on windows -if sys.platform == "win32": +if isWin: si = subprocess.STARTUPINFO() try: si.dwFlags |= subprocess.STARTF_USESHOWWINDOW except: # python2.7+ si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW - # tmp dir for non-hashed media - tmpdir = unicode( - tempfile.mkdtemp(prefix="anki"), sys.getfilesystemencoding()) else: si = None -if sys.platform.startswith("darwin"): +if isMac: # make sure lame, which is installed in /usr/local/bin, is in the path os.environ['PATH'] += ":" + "/usr/local/bin" dir = os.path.dirname(os.path.abspath(__file__)) @@ -64,7 +60,7 @@ def retryWait(proc): # Mplayer settings ########################################################################## -if sys.platform.startswith("win32"): +if isWin: mplayerCmd = ["mplayer.exe", "-ao", "win32"] dir = os.path.dirname(os.path.abspath(sys.argv[0])) os.environ['PATH'] += ";" + dir @@ -157,13 +153,13 @@ def queueMplayer(path): ensureMplayerThreads() while mplayerEvt.isSet(): time.sleep(0.1) - if tmpdir and os.path.exists(path): + if isWin and os.path.exists(path): # mplayer on windows doesn't like the encoding, so we create a # temporary file instead. oddly, foreign characters in the dirname # don't seem to matter. - (fd, name) = tempfile.mkstemp(suffix=os.path.splitext(path)[1], - dir=tmpdir) - f = os.fdopen(fd, "wb") + dir = tmpdir().encode(sys.getfilesystemencoding()) + name = os.path.join(dir, "audio"+os.path.splitext(path)[1]) + f = open(name, "wb") f.write(open(path, "rb").read()) f.close() # it wants unix paths, too! @@ -194,12 +190,7 @@ def stopMplayer(*args): return mplayerManager.kill() -def onExit(): - if tmpdir: - shutil.rmtree(tmpdir) - addHook("deckClosed", stopMplayer) -atexit.register(onExit) # PyAudio recording ########################################################################## diff --git a/anki/sync.py b/anki/sync.py index bce136af0..953505a83 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -3,7 +3,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import zlib, re, urllib, urllib2, socket, simplejson, time, shutil -import os, base64, httplib, sys, tempfile, httplib, types +import os, base64, httplib, sys, httplib, types from datetime import date import anki, anki.deck, anki.cards from anki.errors import * @@ -851,8 +851,8 @@ and cards.id in %s""" % ids2str([c[0] for c in cards]))) try: # write into a temporary file, since POST needs content-length src = open(path, "rb") - (fd, name) = tempfile.mkstemp(prefix="anki") - tmp = open(name, "w+b") + name = namedtmp("fullsync.anki") + tmp = open(name, "wb") # post vars for (key, value) in fields.items(): tmp.write('--' + MIME_BOUNDARY + "\r\n") @@ -900,8 +900,6 @@ and cards.id in %s""" % ids2str([c[0] for c in cards]))) finally: sendProgressHook = None tmp.close() - os.close(fd) - os.unlink(name) finally: runHook("fullSyncFinished") @@ -910,8 +908,7 @@ and cards.id in %s""" % ids2str([c[0] for c in cards]))) runHook("fullSyncStarted", 0) fields = urllib.urlencode(fields) src = urllib.urlopen(SYNC_URL + "fulldown", fields) - (fd, tmpname) = tempfile.mkstemp(dir=os.path.dirname(path), - prefix="fullsync") + tmpname = namedtmp("fullsync.anki") tmp = open(tmpname, "wb") decomp = zlib.decompressobj() cnt = 0 diff --git a/anki/utils.py b/anki/utils.py index 611be1e9a..c66c33789 100644 --- a/anki/utils.py +++ b/anki/utils.py @@ -2,7 +2,8 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import re, os, random, time, types, math, htmlentitydefs, subprocess +import re, os, random, time, types, math, htmlentitydefs, subprocess, \ + tempfile, shutil try: import hashlib @@ -231,7 +232,7 @@ def joinFields(list): def splitFields(string): return string.split("\x1f") -# Misc +# Checksums ############################################################################## def checksum(data): @@ -241,10 +242,46 @@ def fieldChecksum(data): # 32 bit unsigned number from first 8 digits of md5 hash return int(checksum(data.encode("utf-8"))[:8], 16) +# Temp files +############################################################################## + +_tmpdir = None + +def tmpdir(): + "A reusable temp folder which we clean out on each program invocation." + global _tmpdir + if not _tmpdir: + def cleanup(): + shutil.rmtree(_tmpdir) + import atexit + atexit.register(cleanup) + _tmpdir = os.path.join(tempfile.gettempdir(), "anki_temp") + try: + shutil.rmtree(_tmpdir) + except (IOError, OSError): + pass + os.mkdir(_tmpdir) + return _tmpdir + +def tmpfile(prefix=None, suffix=None): + return tempfile.mkstemp(dir=tmpdir(), prefix=prefix, suffix=suffix) + +def namedtmp(name): + "Return tmpdir+name. Deletes any existing file." + path = os.path.join(tmpdir(), name) + try: + os.unlink(path) + except (OSError, IOError): + pass + return path + +# Cmd invocation +############################################################################## + def call(argv, wait=True, **kwargs): "Execute a command. If WAIT, return exit code." # ensure we don't open a separate window for forking process on windows - if sys.platform == "win32": + if isWin: si = subprocess.STARTUPINFO() try: si.dwFlags |= subprocess.STARTF_USESHOWWINDOW @@ -270,3 +307,9 @@ def call(argv, wait=True, **kwargs): else: ret = 0 return ret + +# OS helpers +############################################################################## + +isMac = sys.platform.startswith("darwin") +isWin = sys.platform.startswith("win32")