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, from qutebrowser.config import (configdata, iniparsers, configtypes,
textwrapper, keyconfparser) textwrapper, keyconfparser)
from qutebrowser.commands import cmdexc, cmdutils 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 from qutebrowser.utils.usertypes import Completion
@ -579,7 +579,7 @@ class ConfigManager(QObject):
return return
configfile = os.path.join(self._configdir, self._fname) configfile = os.path.join(self._configdir, self._fname)
log.destroy.debug("Saving config to {}".format(configfile)) 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)) f.write(str(self))
def dump_userconfig(self): def dump_userconfig(self):

View File

@ -23,7 +23,7 @@ import os
import os.path import os.path
import configparser import configparser
from qutebrowser.utils import log, utils from qutebrowser.utils import log, utils, qtutils
class ReadConfigParser(configparser.ConfigParser): class ReadConfigParser(configparser.ConfigParser):
@ -67,5 +67,5 @@ class ReadWriteConfigParser(ReadConfigParser):
if not os.path.exists(self._configdir): if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755) os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile)) 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) self.write(f)

View File

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

View File

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

View File

@ -27,14 +27,16 @@ Module attributes:
""" """
import io
import os import os
import sys import sys
import operator import operator
import distutils.version # pylint: disable=no-name-in-module,import-error import distutils.version # pylint: disable=no-name-in-module,import-error
# https://bitbucket.org/logilab/pylint/issue/73/ # https://bitbucket.org/logilab/pylint/issue/73/
import contextlib
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
QIODevice) QIODevice, QSaveFile)
MAXVALS = { MAXVALS = {
@ -155,6 +157,132 @@ def deserialize(data, obj):
_check_qdatastream(stream) _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): class QtValueError(ValueError):
"""Exception which gets raised by ensure_valid.""" """Exception which gets raised by ensure_valid."""