Use QSaveFile for saving files. Fixes #234.

This commit is contained in:
Florian Bruhin 2014-12-08 23:42:26 +01:00
parent d611a37d7d
commit bf24578dfd
5 changed files with 140 additions and 15 deletions

View File

@ -38,7 +38,7 @@ from PyQt5.QtWidgets import QMessageBox
from qutebrowser.config import (configdata, iniparsers, configtypes,
textwrapper, keyconfparser)
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, objreg, utils, standarddir, log
from qutebrowser.utils import message, objreg, utils, standarddir, log, qtutils
from qutebrowser.utils.usertypes import Completion
@ -579,7 +579,7 @@ class ConfigManager(QObject):
return
configfile = os.path.join(self._configdir, self._fname)
log.destroy.debug("Saving config to {}".format(configfile))
with open(configfile, 'w', encoding='utf-8') as f:
with qtutils.savefile_open(configfile) as f:
f.write(str(self))
def dump_userconfig(self):

View File

@ -23,7 +23,7 @@ import os
import os.path
import configparser
from qutebrowser.utils import log, utils
from qutebrowser.utils import log, utils, qtutils
class ReadConfigParser(configparser.ConfigParser):
@ -67,5 +67,5 @@ class ReadWriteConfigParser(ReadConfigParser):
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile))
with open(self._configfile, 'w', encoding='utf-8') as f:
with qtutils.savefile_open(self._configfile) as f:
self.write(f)

View File

@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.config import configdata, textwrapper
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import log, utils
from qutebrowser.utils import log, utils, qtutils
class KeyConfigError(Exception):
@ -126,8 +126,9 @@ class KeyConfigParser(QObject):
if self._configfile is None:
return
log.destroy.debug("Saving key config to {}".format(self._configfile))
with open(self._configfile, 'w', encoding='utf-8') as f:
f.write(str(self))
with qtutils.savefile_open(self._configfile, encoding='utf-8') as f:
data = str(self)
f.write(data)
@cmdutils.register(instance='key-config')
def bind(self, key, *command, mode=None):

View File

@ -25,7 +25,7 @@ import collections
from PyQt5.QtCore import pyqtSlot
from qutebrowser.utils import log, utils, objreg
from qutebrowser.utils import log, utils, objreg, qtutils
from qutebrowser.config import config
@ -104,12 +104,8 @@ class LineConfigParser(collections.UserList):
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile))
if self._binary:
with open(self._configfile, 'wb') as f:
self.write(f, limit)
else:
with open(self._configfile, 'w', encoding='utf-8') as f:
self.write(f, limit)
with qtutils.savefile_open(self._configfile, self._binary) as f:
self.write(f, limit)
@pyqtSlot(str, str)
def cleanup_file(self, section, option):

View File

@ -27,14 +27,16 @@ Module attributes:
"""
import io
import os
import sys
import operator
import distutils.version # pylint: disable=no-name-in-module,import-error
# https://bitbucket.org/logilab/pylint/issue/73/
import contextlib
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
QIODevice)
QIODevice, QSaveFile)
MAXVALS = {
@ -155,6 +157,132 @@ def deserialize(data, obj):
_check_qdatastream(stream)
@contextlib.contextmanager
def savefile_open(filename, binary=False, encoding='utf-8'):
"""Context manager to easily use a QSaveFile."""
f = QSaveFile(filename)
try:
if binary:
ok = f.open(QIODevice.WriteOnly)
new_f = PyQIODevice(f)
else:
ok = f.open(QIODevice.WriteOnly | QIODevice.Text)
new_f = io.TextIOWrapper(PyQIODevice(f), encoding=encoding)
if not ok: # pylint: disable=used-before-assignment
raise IOError(f.errorString())
yield new_f
except:
f.cancelWriting()
raise
finally:
new_f.flush()
ok = f.commit()
if not ok:
raise IOError(f.errorString())
class PyQIODevice(io.BufferedIOBase):
"""Wrapper for a QIODevice which provides a python interface.
Attributes:
_dev: The underlying QIODevice.
"""
# pylint: disable=missing-docstring
def __init__(self, dev):
self._dev = dev
def __len__(self):
return self._dev.size()
def _check_open(self):
"""Check if the device is open, raise IOError if not."""
if not self._dev.isOpen():
raise IOError("IO operation on closed device!")
def _check_random(self):
"""Check if the device supports random access, raise IOError if not."""
if not self.seekable():
raise IOError("Random access not allowed!")
def fileno(self):
raise io.UnsupportedOperation
def seek(self, offset, whence=io.SEEK_SET):
self._check_open()
self._check_random()
if whence == io.SEEK_SET:
ok = self._dev.seek(offset)
elif whence == io.SEEK_CUR:
ok = self._dev.seek(self.tell() + offset)
elif whence == io.SEEK_END:
ok = self._dev.seek(len(self) + offset)
else:
raise io.UnsupportedOperation("whence = {} is not "
"supported!".format(whence))
if not ok:
raise IOError(self._dev.errorString())
def truncate(self, size=None): # pylint: disable=unused-argument
raise io.UnsupportedOperation
def close(self):
self._dev.close()
@property
def closed(self):
return not self._dev.isOpen()
def flush(self):
self._check_open()
self._dev.waitForBytesWritten(-1)
def isatty(self):
self._check_open()
return False
def readable(self):
return self._dev.isReadable()
def readline(self, size=-1):
self._check_open()
if size == -1:
size = 0
return self._dev.readLine(size)
def seekable(self):
return not self._dev.isSequential()
def tell(self):
self._check_open()
self._check_random()
return self._dev.pos()
def writable(self):
return self._dev.isWritable()
def readinto(self, b):
self._check_open()
return self._dev.read(b, len(b))
def write(self, b):
self._check_open()
num = self._dev.write(b)
if num == -1 or num < len(b):
raise IOError(self._dev.errorString())
return num
def read(self, size):
self._check_open()
buf = bytes()
num = self._dev.read(buf, size)
if num == -1:
raise IOError(self._dev.errorString())
return num
class QtValueError(ValueError):
"""Exception which gets raised by ensure_valid."""