source: main/waeup.kofa/trunk/src/waeup/kofa/datacenter.py @ 8374

Last change on this file since 8374 was 8372, checked in by uli, 13 years ago

Add deleted dir in standard filecenter paths.

  • Property svn:keywords set to Id
File size: 9.5 KB
RevLine 
[7193]1## $Id: datacenter.py 8372 2012-05-06 20:48:22Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
[7819]18"""Kofa data center.
[4146]19
20The waeup data center cares for management of upload data and provides
21tools for importing/exporting CSV data.
22"""
[4873]23import logging
[4146]24import os
[4896]25import shutil
[4146]26import grok
27from datetime import datetime
[7568]28from zope.component import getUtility
[4883]29from zope.component.interfaces import ObjectEvent
[7811]30from waeup.kofa.interfaces import (IDataCenter, IDataCenterFile,
[7568]31                                   IDataCenterStorageMovedEvent,
[7579]32                                   IDataCenterConfig)
[7811]33from waeup.kofa.utils.helpers import copy_filesystem_tree
34from waeup.kofa.utils.logger import Logger
[4146]35
[6578]36class DataCenter(grok.Container, Logger):
[4146]37    """A data center contains CSV files.
38    """
[4669]39    grok.implements(IDataCenter)
[4166]40
[7811]41    logger_name = 'waeup.kofa.${sitename}.datacenter'
[6578]42    logger_filename = 'datacenter.log'
[4873]43
[4892]44    def __init__(self, *args, **kw):
45        super(DataCenter, self).__init__(*args, **kw)
[7579]46        self.storage = getUtility(IDataCenterConfig)['path']
[4892]47        self._createSubDirs()
[6286]48
[4892]49    def _createSubDirs(self):
50        """Create standard subdirs.
51        """
[8372]52        for name in ['finished', 'unfinished', 'logs', 'deleted']:
[4892]53            path = os.path.join(self.storage, name)
54            if os.path.exists(path):
55                continue
56            os.mkdir(path)
57        return
[6286]58
[4858]59    def getFiles(self, sort='name'):
[4146]60        """Get a list of files stored in `storage`.
[4574]61
62        Files are sorted by basename.
[4146]63        """
64        result = []
65        if not os.path.exists(self.storage):
66            return result
[4574]67        for filename in sorted(os.listdir(self.storage)):
[4146]68            fullpath = os.path.join(self.storage, filename)
69            if not os.path.isfile(fullpath):
70                continue
71            result.append(DataCenterFile(fullpath))
[4858]72        if sort == 'date':
73            # sort results in newest-first order...
74            result = sorted(result, key=lambda x: x.getTimeStamp(),
75                            reverse=True)
[4146]76        return result
77
[4858]78    def getLogFiles(self):
[4908]79        """Get the files from logs/ subdir. Files are sorted by name.
[4858]80        """
81        result = []
[4908]82        logdir = os.path.join(self.storage, 'logs')
83        if not os.path.exists(logdir):
84            os.mkdir(logdir)
85        for name in sorted(os.listdir(logdir)):
86            if not os.path.isfile(os.path.join(logdir, name)):
[4858]87                continue
88            result.append(
[4908]89                LogFile(os.path.join(self.storage, 'logs', name)))
[4858]90        return result
[6286]91
[4190]92    def setStoragePath(self, path, move=False, overwrite=False):
[4146]93        """Set the path where to store files.
94        """
95        path = os.path.abspath(path)
[4190]96        not_copied = []
[4152]97        if not os.path.exists(path):
98            raise ValueError('The path given does not exist: %s' % path)
[4173]99        if move is True:
[7186]100            not_copied = copy_filesystem_tree(self.storage, path,
[4190]101                                            overwrite=overwrite)
[4146]102        self.storage = path
[4892]103        self._createSubDirs()
[4883]104        grok.notify(DataCenterStorageMovedEvent(self))
[4190]105        return not_copied
[4146]106
[4896]107    def _moveFile(self, source, dest):
108        """Move file source to dest preserving ctime, mtime, etc.
109        """
110        if not os.path.exists(source):
111            self.logger.warn('No such source path: %s' % source)
112            return
113        if source == dest:
114            return
115        shutil.copyfile(source, dest)
116        shutil.copystat(source, dest)
117        os.unlink(source)
[6286]118
[4896]119    def distProcessedFiles(self, successful, source_path, finished_file,
[4996]120                           pending_file, mode='create', move_orig=True):
[4896]121        """Put processed files into final locations.
[4858]122
[4896]123        ``successful`` is a boolean that tells, whether processing was
124        successful.
125
126        ``source_path``: path to file that was processed.
127
128        ``finished_file``, ``pending_file``: paths to the respective
129        generated .pending and .finished file. The .pending file path
130        may be ``None``.
131
[4904]132        If finished file is placed in a location outside the local
133        storage dir, the complete directory is removed
[7933]134        afterwards. Regular processors should put their stuff in
[4904]135        dedicated temporary dirs.
[6286]136
[4896]137        See datacenter.txt for more info about how this works.
138        """
139        basename = os.path.basename(source_path)
140        pending_name = basename
141        pending = False
142        finished_dir = os.path.join(self.storage, 'finished')
143        unfinished_dir = os.path.join(self.storage, 'unfinished')
144
145        if basename.endswith('.pending.csv'):
[4996]146            maybe_basename = "%s.csv" % basename.rsplit('.', 3)[0]
[4896]147            maybe_src = os.path.join(unfinished_dir, maybe_basename)
148            if os.path.isfile(maybe_src):
149                basename = maybe_basename
150                pending = True
[6286]151
[4896]152        base, ext = os.path.splitext(basename)
[4996]153        finished_name = "%s.%s.finished%s" % (base, mode, ext)
[4896]154        if not pending:
[4996]155            pending_name = "%s.%s.pending%s" % (base, mode, ext)
[4896]156
157        # Put .pending and .finished file into respective places...
158        pending_dest = os.path.join(self.storage, pending_name)
159        finished_dest = os.path.join(finished_dir, finished_name)
160        self._moveFile(finished_file, finished_dest)
161        if pending_file is not None:
162            self._moveFile(pending_file, pending_dest)
163
164        # Put source file into final location...
165        finished_dest = os.path.join(finished_dir, basename)
166        unfinished_dest = os.path.join(unfinished_dir, basename)
167        if successful and not pending:
168            self._moveFile(source_path, finished_dest)
169        elif successful and pending:
170            self._moveFile(unfinished_dest, finished_dest)
171            os.unlink(source_path)
172        elif not successful and not pending:
173            self._moveFile(source_path, unfinished_dest)
[4904]174
175        # If finished and pending-file were created in a location
176        # outside datacenter storage, we remove it.
[4906]177        maybe_temp_dir = os.path.dirname(finished_file)
[4904]178        if os.path.commonprefix(
[4906]179            [self.storage, maybe_temp_dir]) != self.storage:
180            shutil.rmtree(maybe_temp_dir)
[4896]181        return
182
[6286]183
[4146]184class DataCenterFile(object):
185    """A description of a file stored in data center.
186    """
[4166]187    grok.implements(IDataCenterFile)
[6286]188
[4146]189    def __init__(self, context):
190        self.context = context
191        self.name = os.path.basename(self.context)
192        self.size = self.getSize()
193        self.uploaddate = self.getDate()
[4858]194        self.lines = self.getLinesNumber()
[4146]195
196    def getDate(self):
197        """Get a human readable datetime representation.
198        """
199        date = datetime.fromtimestamp(os.path.getctime(self.context))
[6827]200        return date.strftime("%Y-%m-%d %H:%M:%S")
[4146]201
[4858]202    def getTimeStamp(self):
203        """Get a (machine readable) timestamp.
204        """
205        return os.path.getctime(self.context)
[6286]206
[4146]207    def getSize(self):
208        """Get a human readable file size.
209        """
210        bytesize = os.path.getsize(self.context)
211        size = "%s bytes" % bytesize
212        units = ['kb', 'MB', 'GB']
213        for power, unit in reversed(list(enumerate(units))):
214            power += 1
215            if bytesize >= 1024 ** power:
216                size = "%.2f %s" % (bytesize/(1024.0**power), unit)
217                break
218        return size
219
[4858]220    def getLinesNumber(self):
221        """Get number of lines.
222        """
223        num = 0
224        for line in open(self.context, 'rb'):
225            num += 1
226        return num
[6286]227
[4858]228class LogFile(DataCenterFile):
229    """A description of a log file.
230    """
231    def __init__(self, context):
232        super(LogFile, self).__init__(context)
233        self._markers = dict()
234        self._parsed = False
235        self.userid = self.getUserId()
236        self.mode = self.getMode()
237        self.stats = self.getStats()
238        self.source = self.getSourcePath()
239
240    def _parseFile(self, maxline=10):
241        """Find markers in a file.
242        """
243        if self._parsed:
244            return
245        for line in open(self.context, 'rb'):
246            line = line.strip()
247            if not ':' in line:
248                continue
249            name, text = line.split(':', 1)
250            self._markers[name.lower()] = text
251        self._parsed = True
252        return
253
254    def _getMarker(self, marker):
255        marker = marker.lower()
256        if not self._parsed:
257            self._parseFile()
258        if marker in self._markers.keys():
259            return self._markers[marker]
[6286]260
[4858]261    def getUserId(self):
262        return self._getMarker('user') or '<UNKNOWN>'
263
264    def getMode(self):
265        return self._getMarker('mode') or '<NOT SET>'
266
267    def getStats(self):
268        return self._getMarker('processed') or '<Info not avail.>'
269
270    def getSourcePath(self):
271        return self._getMarker('source') or None
[4883]272
[4961]273
[4883]274class DataCenterStorageMovedEvent(ObjectEvent):
275    """An event fired, when datacenter storage moves.
276    """
277    grok.implements(IDataCenterStorageMovedEvent)
Note: See TracBrowser for help on using the repository browser.