Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/datastore/src/carquinyol/filestore.py
diff options
context:
space:
mode:
Diffstat (limited to 'datastore/src/carquinyol/filestore.py')
-rw-r--r--datastore/src/carquinyol/filestore.py222
1 files changed, 222 insertions, 0 deletions
diff --git a/datastore/src/carquinyol/filestore.py b/datastore/src/carquinyol/filestore.py
new file mode 100644
index 0000000..9724397
--- /dev/null
+++ b/datastore/src/carquinyol/filestore.py
@@ -0,0 +1,222 @@
+# Copyright (C) 2008, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import errno
+import logging
+import tempfile
+
+import gobject
+
+from carquinyol import layoutmanager
+
+
+class FileStore(object):
+ """Handle the storage of one file per entry.
+ """
+
+ # TODO: add protection against store and retrieve operations on entries
+ # that are being processed async.
+
+ def store(self, uid, file_path, transfer_ownership, completion_cb):
+ """Store a file for a given entry.
+
+ """
+ dir_path = layoutmanager.get_instance().get_entry_path(uid)
+ if not os.path.exists(dir_path):
+ os.makedirs(dir_path)
+
+ destination_path = layoutmanager.get_instance().get_data_path(uid)
+ if file_path:
+ if not os.path.isfile(file_path):
+ raise ValueError('No file at %r' % file_path)
+ if transfer_ownership:
+ try:
+ logging.debug('FileStore moving from %r to %r', file_path,
+ destination_path)
+ os.rename(file_path, destination_path)
+ completion_cb()
+ except OSError, e:
+ if e.errno == errno.EXDEV:
+ self._async_copy(file_path, destination_path,
+ completion_cb, unlink_src=True)
+ else:
+ raise
+ else:
+ self._async_copy(file_path, destination_path, completion_cb,
+ unlink_src=False)
+ """
+ TODO: How can we support deleting the file of an entry?
+ elif not file_path and os.path.exists(destination_path):
+ logging.debug('FileStore: deleting %r' % destination_path)
+ os.remove(destination_path)
+ completion_cb()
+ """
+ else:
+ logging.debug('FileStore: Nothing to do')
+ completion_cb()
+
+ def _async_copy(self, file_path, destination_path, completion_cb,
+ unlink_src):
+ """Start copying a file asynchronously.
+
+ """
+ logging.debug('FileStore copying from %r to %r', file_path,
+ destination_path)
+ async_copy = AsyncCopy(file_path, destination_path, completion_cb,
+ unlink_src)
+ async_copy.start()
+
+ def retrieve(self, uid, user_id, extension):
+ """Place the file associated to a given entry into a directory
+ where the user can read it. The caller is reponsible for
+ deleting this file.
+
+ """
+ file_path = layoutmanager.get_instance().get_data_path(uid)
+ if not os.path.exists(file_path):
+ logging.debug('Entry %r doesnt have any file', uid)
+ return ''
+
+ use_instance_dir = os.path.exists('/etc/olpc-security') and \
+ os.getuid() != user_id
+ if use_instance_dir:
+ if not user_id:
+ raise ValueError('Couldnt determine the current user uid.')
+ destination_dir = os.path.join(os.environ['HOME'], 'isolation',
+ '1', 'uid_to_instance_dir', str(user_id))
+ else:
+ profile = os.environ.get('SUGAR_PROFILE', 'default')
+ destination_dir = os.path.join(os.path.expanduser('~'), '.sugar',
+ profile, 'data')
+ if not os.path.exists(destination_dir):
+ os.makedirs(destination_dir)
+
+ if extension is None:
+ extension = ''
+ elif extension:
+ extension = '.' + extension
+
+ fd, destination_path = tempfile.mkstemp(prefix=uid + '_',
+ suffix=extension, dir=destination_dir)
+ os.close(fd)
+ os.unlink(destination_path)
+
+ # Try to hard link from the original file to the targetpath. This can
+ # fail if the file is in a different filesystem. Do a symlink instead.
+ try:
+ os.link(file_path, destination_path)
+ except OSError, e:
+ if e.errno == errno.EXDEV:
+ os.symlink(file_path, destination_path)
+ else:
+ raise
+
+ # Try to make the original file readable. This can fail if the file is
+ # in a FAT filesystem.
+ try:
+ os.chmod(file_path, 0604)
+ except OSError, e:
+ if e.errno != errno.EPERM:
+ raise
+
+ return destination_path
+
+ def get_file_path(self, uid):
+ return layoutmanager.get_instance().get_data_path(uid)
+
+ def delete(self, uid):
+ """Remove the file associated to a given entry.
+
+ """
+ file_path = layoutmanager.get_instance().get_data_path(uid)
+ if os.path.exists(file_path):
+ os.remove(file_path)
+
+ def hard_link_entry(self, new_uid, existing_uid):
+ existing_file = layoutmanager.get_instance().get_data_path(existing_uid)
+ new_file = layoutmanager.get_instance().get_data_path(new_uid)
+
+ logging.debug('removing %r', new_file)
+ os.remove(new_file)
+
+ logging.debug('hard linking %r -> %r', new_file, existing_file)
+ os.link(existing_file, new_file)
+
+
+class AsyncCopy(object):
+ """Copy a file in chunks in the idle loop.
+
+ """
+ CHUNK_SIZE = 65536
+
+ def __init__(self, src, dest, completion, unlink_src=False):
+ self.src = src
+ self.dest = dest
+ self.completion = completion
+ self._unlink_src = unlink_src
+ self.src_fp = -1
+ self.dest_fp = -1
+ self.written = 0
+ self.size = 0
+
+ def _cleanup(self):
+ os.close(self.src_fp)
+ os.close(self.dest_fp)
+
+ def _copy_block(self, user_data=None):
+ try:
+ data = os.read(self.src_fp, AsyncCopy.CHUNK_SIZE)
+ count = os.write(self.dest_fp, data)
+ self.written += len(data)
+
+ # error writing data to file?
+ if count < len(data):
+ logging.error('AC: Error writing %s -> %s: wrote less than '
+ 'expected', self.src, self.dest)
+ self._complete(RuntimeError(
+ 'Error writing data to destination file'))
+ return False
+
+ # FIXME: emit progress here
+
+ # done?
+ if len(data) < AsyncCopy.CHUNK_SIZE:
+ self._complete(None)
+ return False
+ except Exception, err:
+ logging.error('AC: Error copying %s -> %s: %r', self.src, self.
+ dest, err)
+ self._complete(err)
+ return False
+
+ return True
+
+ def _complete(self, *args):
+ self._cleanup()
+ if self._unlink_src:
+ os.unlink(self.src)
+ self.completion(*args)
+
+ def start(self):
+ self.src_fp = os.open(self.src, os.O_RDONLY)
+ self.dest_fp = os.open(self.dest, os.O_RDWR | os.O_TRUNC | os.O_CREAT,
+ 0644)
+
+ stat = os.fstat(self.src_fp)
+ self.size = stat[6]
+
+ gobject.idle_add(self._copy_block)