Commit 8e0fdda1 authored by Pierre-Yves David's avatar Pierre-Yves David
Browse files

[storage] BFSS now create read only file (closes #2151672)

In the process binary.to_file now takes a file instead of a file-path. As
storage was currently the only user it should not impact anyone.
parent e52a084e955c
......@@ -77,25 +77,24 @@ class Binary(StringIO):
"Binary objects must use raw strings, not %s" % data.__class__
StringIO.write(self, data)
def to_file(self, filename):
def to_file(self, fobj):
"""write a binary to disk
the writing is performed in a safe way for files stored on
Windows SMB shares
"""
pos = self.tell()
with open(filename, 'wb') as fobj:
self.seek(0)
if sys.platform == 'win32':
while True:
# the 16kB chunksize comes from the shutil module
# in stdlib
chunk = self.read(16*1024)
if not chunk:
break
fobj.write(chunk)
else:
fobj.write(self.read())
self.seek(0)
if sys.platform == 'win32':
while True:
# the 16kB chunksize comes from the shutil module
# in stdlib
chunk = self.read(16*1024)
if not chunk:
break
fobj.write(chunk)
else:
fobj.write(self.read())
self.seek(pos)
@staticmethod
......
......@@ -18,6 +18,7 @@
"""custom storages for the system source"""
import os
import sys
from os import unlink, path as osp
from contextlib import contextmanager
......@@ -112,11 +113,29 @@ def fsimport(session):
class BytesFileSystemStorage(Storage):
"""store Bytes attribute value on the file system"""
def __init__(self, defaultdir, fsencoding='utf-8'):
def __init__(self, defaultdir, fsencoding='utf-8', wmode=0444):
if type(defaultdir) is unicode:
defaultdir = defaultdir.encode(fsencoding)
self.default_directory = defaultdir
self.fsencoding = fsencoding
# extra umask to use when creating file
# 0444 as in "only allow read bit in permission"
self._wmode = wmode
def _writecontent(self, path, binary):
"""write the content of a binary in readonly file
As the bfss never alter a create file it does not prevent it to work as
intended. This is a beter safe than sorry approach.
"""
write_flag = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if sys.platform == 'win32':
write_flag |= os.O_BINARY
fd = os.open(path, write_flag, self._wmode)
fileobj = os.fdopen(fd, 'wb')
binary.to_file(fileobj)
fileobj.close()
def callback(self, source, session, value):
"""sql generator callback when some attribute with a custom storage is
......@@ -138,7 +157,7 @@ class BytesFileSystemStorage(Storage):
fpath = self.new_fs_path(entity, attr)
# bytes storage used to store file's path
entity.cw_edited.edited_attribute(attr, Binary(fpath))
binary.to_file(fpath)
self._writecontent(fpath, binary)
AddFileOp.get_instance(entity._cw).add_data(fpath)
return binary
......@@ -171,7 +190,7 @@ class BytesFileSystemStorage(Storage):
fpath = self.new_fs_path(entity, attr)
assert not osp.exists(fpath)
# write attribute value on disk
binary.to_file(fpath)
self._writecontent(fpath, binary)
# Mark the new file as added during the transaction.
# The file will be removed on rollback
AddFileOp.get_instance(entity._cw).add_data(fpath)
......
......@@ -22,6 +22,7 @@ from __future__ import with_statement
from logilab.common.testlib import unittest_main, tag, Tags
from cubicweb.devtools.testlib import CubicWebTC
import os
import os.path as osp
import shutil
import tempfile
......@@ -90,6 +91,8 @@ class StorageTC(CubicWebTC):
expected_filepath = osp.join(self.tempdir, '%s_data_%s' %
(f1.eid, f1.data_name))
self.assertTrue(osp.isfile(expected_filepath))
# file should be read only
self.assertFalse(os.access(expected_filepath, os.W_OK))
self.assertEqual(file(expected_filepath).read(), 'the-data')
self.rollback()
self.assertFalse(osp.isfile(expected_filepath))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment