Ignore:
Timestamp:
Mar 19, 2014, 11:31:01 PM (11 years ago)
Author:
dmik
Message:

python: Merge vendor 2.7.6 to trunk.

Location:
python/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • python/trunk

  • python/trunk/Lib/mailbox.py

    r2 r391  
    1919import email.message
    2020import email.generator
    21 import rfc822
    2221import StringIO
    2322try:
     
    2827except ImportError:
    2928    fcntl = None
     29
     30import warnings
     31with warnings.catch_warnings():
     32    if sys.py3kwarning:
     33        warnings.filterwarnings("ignore", ".*rfc822 has been removed",
     34                                DeprecationWarning)
     35    import rfc822
    3036
    3137__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF',
     
    191197        """Flush and close the mailbox."""
    192198        raise NotImplementedError('Method must be implemented by subclass')
     199
     200    # Whether each message must end in a newline
     201    _append_newline = False
    193202
    194203    def _dump_message(self, message, target, mangle_from_=False):
     
    202211            gen.flatten(message)
    203212            buffer.seek(0)
    204             target.write(buffer.read().replace('\n', os.linesep))
     213            data = buffer.read().replace('\n', os.linesep)
     214            target.write(data)
     215            if self._append_newline and not data.endswith(os.linesep):
     216                # Make sure the message ends with a newline
     217                target.write(os.linesep)
    205218        elif isinstance(message, str):
    206219            if mangle_from_:
     
    208221            message = message.replace('\n', os.linesep)
    209222            target.write(message)
     223            if self._append_newline and not message.endswith(os.linesep):
     224                # Make sure the message ends with a newline
     225                target.write(os.linesep)
    210226        elif hasattr(message, 'read'):
     227            lastline = None
    211228            while True:
    212229                line = message.readline()
     
    217234                line = line.replace('\n', os.linesep)
    218235                target.write(line)
     236                lastline = line
     237            if self._append_newline and lastline and not lastline.endswith(os.linesep):
     238                # Make sure the message ends with a newline
     239                target.write(os.linesep)
    219240        else:
    220241            raise TypeError('Invalid message type: %s' % type(message))
     
    229250        """Initialize a Maildir instance."""
    230251        Mailbox.__init__(self, dirname, factory, create)
     252        self._paths = {
     253            'tmp': os.path.join(self._path, 'tmp'),
     254            'new': os.path.join(self._path, 'new'),
     255            'cur': os.path.join(self._path, 'cur'),
     256            }
    231257        if not os.path.exists(self._path):
    232258            if create:
    233259                os.mkdir(self._path, 0700)
    234                 os.mkdir(os.path.join(self._path, 'tmp'), 0700)
    235                 os.mkdir(os.path.join(self._path, 'new'), 0700)
    236                 os.mkdir(os.path.join(self._path, 'cur'), 0700)
     260                for path in self._paths.values():
     261                    os.mkdir(path, 0o700)
    237262            else:
    238263                raise NoSuchMailboxError(self._path)
    239264        self._toc = {}
     265        self._toc_mtimes = {'cur': 0, 'new': 0}
     266        self._last_read = 0         # Records last time we read cur/new
     267        self._skewfactor = 0.1      # Adjust if os/fs clocks are skewing
    240268
    241269    def add(self, message):
     
    244272        try:
    245273            self._dump_message(message, tmp_file)
    246         finally:
    247             _sync_close(tmp_file)
     274        except BaseException:
     275            tmp_file.close()
     276            os.remove(tmp_file.name)
     277            raise
     278        _sync_close(tmp_file)
    248279        if isinstance(message, MaildirMessage):
    249280            subdir = message.get_subdir()
     
    256287        uniq = os.path.basename(tmp_file.name).split(self.colon)[0]
    257288        dest = os.path.join(self._path, subdir, uniq + suffix)
     289        if isinstance(message, MaildirMessage):
     290            os.utime(tmp_file.name,
     291                     (os.path.getatime(tmp_file.name), message.get_date()))
     292        # No file modification should be done after the file is moved to its
     293        # final position in order to prevent race conditions with changes
     294        # from other programs
    258295        try:
    259296            if hasattr(os, 'link'):
     
    269306            else:
    270307                raise
    271         if isinstance(message, MaildirMessage):
    272             os.utime(dest, (os.path.getatime(dest), message.get_date()))
    273308        return uniq
    274309
     
    305340            suffix = ''
    306341        self.discard(key)
     342        tmp_path = os.path.join(self._path, temp_subpath)
    307343        new_path = os.path.join(self._path, subdir, key + suffix)
    308         os.rename(os.path.join(self._path, temp_subpath), new_path)
    309344        if isinstance(message, MaildirMessage):
    310             os.utime(new_path, (os.path.getatime(new_path),
    311                                 message.get_date()))
     345            os.utime(tmp_path,
     346                     (os.path.getatime(tmp_path), message.get_date()))
     347        # No file modification should be done after the file is moved to its
     348        # final position in order to prevent race conditions with changes
     349        # from other programs
     350        os.rename(tmp_path, new_path)
    312351
    313352    def get_message(self, key):
     
    364403    def flush(self):
    365404        """Write any pending changes to disk."""
    366         return  # Maildir changes are always written immediately.
     405        # Maildir changes are always written immediately, so there's nothing
     406        # to do.
     407        pass
    367408
    368409    def lock(self):
     
    462503    def _refresh(self):
    463504        """Update table of contents mapping."""
     505        # If it has been less than two seconds since the last _refresh() call,
     506        # we have to unconditionally re-read the mailbox just in case it has
     507        # been modified, because os.path.mtime() has a 2 sec resolution in the
     508        # most common worst case (FAT) and a 1 sec resolution typically.  This
     509        # results in a few unnecessary re-reads when _refresh() is called
     510        # multiple times in that interval, but once the clock ticks over, we
     511        # will only re-read as needed.  Because the filesystem might be being
     512        # served by an independent system with its own clock, we record and
     513        # compare with the mtimes from the filesystem.  Because the other
     514        # system's clock might be skewing relative to our clock, we add an
     515        # extra delta to our wait.  The default is one tenth second, but is an
     516        # instance variable and so can be adjusted if dealing with a
     517        # particularly skewed or irregular system.
     518        if time.time() - self._last_read > 2 + self._skewfactor:
     519            refresh = False
     520            for subdir in self._toc_mtimes:
     521                mtime = os.path.getmtime(self._paths[subdir])
     522                if mtime > self._toc_mtimes[subdir]:
     523                    refresh = True
     524                self._toc_mtimes[subdir] = mtime
     525            if not refresh:
     526                return
     527        # Refresh toc
    464528        self._toc = {}
    465         for subdir in ('new', 'cur'):
    466             subdir_path = os.path.join(self._path, subdir)
    467             for entry in os.listdir(subdir_path):
    468                 p = os.path.join(subdir_path, entry)
     529        for subdir in self._toc_mtimes:
     530            path = self._paths[subdir]
     531            for entry in os.listdir(path):
     532                p = os.path.join(path, entry)
    469533                if os.path.isdir(p):
    470534                    continue
    471535                uniq = entry.split(self.colon)[0]
    472536                self._toc[uniq] = os.path.join(subdir, entry)
     537        self._last_read = time.time()
    473538
    474539    def _lookup(self, key):
     
    513578                else:
    514579                    raise NoSuchMailboxError(self._path)
    515             elif e.errno == errno.EACCES:
     580            elif e.errno in (errno.EACCES, errno.EROFS):
    516581                f = open(self._path, 'rb')
    517582            else:
     
    520585        self._toc = None
    521586        self._next_key = 0
    522         self._pending = False   # No changes require rewriting the file.
     587        self._pending = False       # No changes require rewriting the file.
     588        self._pending_sync = False  # No need to sync the file
    523589        self._locked = False
    524         self._file_length = None        # Used to record mailbox size
     590        self._file_length = None    # Used to record mailbox size
    525591
    526592    def add(self, message):
     
    529595        self._toc[self._next_key] = self._append_message(message)
    530596        self._next_key += 1
    531         self._pending = True
     597        # _append_message appends the message to the mailbox file. We
     598        # don't need a full rewrite + rename, sync is enough.
     599        self._pending_sync = True
    532600        return self._next_key - 1
    533601
     
    575643        """Write any pending changes to disk."""
    576644        if not self._pending:
     645            if self._pending_sync:
     646                # Messages have only been added, so syncing the file
     647                # is enough.
     648                _sync_flush(self._file)
     649                self._pending_sync = False
    577650            return
    578651
     
    608681                new_toc[key] = (new_start, new_file.tell())
    609682                self._post_message_hook(new_file)
     683            self._file_length = new_file.tell()
    610684        except:
    611685            new_file.close()
     
    615689        # self._file is about to get replaced, so no need to sync.
    616690        self._file.close()
     691        # Make sure the new file's mode is the same as the old file's
     692        mode = os.stat(self._path).st_mode
     693        os.chmod(new_file.name, mode)
    617694        try:
    618695            os.rename(new_file.name, self._path)
     
    627704        self._toc = new_toc
    628705        self._pending = False
     706        self._pending_sync = False
    629707        if self._locked:
    630708            _lock_file(self._file, dotlock=False)
     
    662740        """Append message to mailbox and return (start, stop) offsets."""
    663741        self._file.seek(0, 2)
    664         self._pre_message_hook(self._file)
    665         offsets = self._install_message(message)
    666         self._post_message_hook(self._file)
     742        before = self._file.tell()
     743        if len(self._toc) == 0 and not self._pending:
     744            # This is the first message, and the _pre_mailbox_hook
     745            # hasn't yet been called. If self._pending is True,
     746            # messages have been removed, so _pre_mailbox_hook must
     747            # have been called already.
     748            self._pre_mailbox_hook(self._file)
     749        try:
     750            self._pre_message_hook(self._file)
     751            offsets = self._install_message(message)
     752            self._post_message_hook(self._file)
     753        except BaseException:
     754            self._file.truncate(before)
     755            raise
    667756        self._file.flush()
    668757        self._file_length = self._file.tell()  # Record current length of mailbox
     
    732821    _mangle_from_ = True
    733822
     823    # All messages must end in a newline character, and
     824    # _post_message_hooks outputs an empty line between messages.
     825    _append_newline = True
     826
    734827    def __init__(self, path, factory=None, create=True):
    735828        """Initialize an mbox mailbox."""
     
    737830        _mboxMMDF.__init__(self, path, factory, create)
    738831
    739     def _pre_message_hook(self, f):
    740         """Called before writing each message to file f."""
    741         if f.tell() != 0:
    742             f.write(os.linesep)
     832    def _post_message_hook(self, f):
     833        """Called after writing each message to file f."""
     834        f.write(os.linesep)
    743835
    744836    def _generate_toc(self):
    745837        """Generate key-to-(start, stop) table of contents."""
    746838        starts, stops = [], []
     839        last_was_empty = False
    747840        self._file.seek(0)
    748841        while True:
     
    751844            if line.startswith('From '):
    752845                if len(stops) < len(starts):
     846                    if last_was_empty:
     847                        stops.append(line_pos - len(os.linesep))
     848                    else:
     849                        # The last line before the "From " line wasn't
     850                        # blank, but we consider it a start of a
     851                        # message anyway.
     852                        stops.append(line_pos)
     853                starts.append(line_pos)
     854                last_was_empty = False
     855            elif not line:
     856                if last_was_empty:
    753857                    stops.append(line_pos - len(os.linesep))
    754                 starts.append(line_pos)
    755             elif line == '':
    756                 stops.append(line_pos)
     858                else:
     859                    stops.append(line_pos)
    757860                break
     861            elif line == os.linesep:
     862                last_was_empty = True
     863            else:
     864                last_was_empty = False
    758865        self._toc = dict(enumerate(zip(starts, stops)))
    759866        self._next_key = len(self._toc)
     
    830937        new_path = os.path.join(self._path, str(new_key))
    831938        f = _create_carefully(new_path)
     939        closed = False
    832940        try:
    833941            if self._locked:
    834942                _lock_file(f)
    835943            try:
    836                 self._dump_message(message, f)
     944                try:
     945                    self._dump_message(message, f)
     946                except BaseException:
     947                    # Unlock and close so it can be deleted on Windows
     948                    if self._locked:
     949                        _unlock_file(f)
     950                    _sync_close(f)
     951                    closed = True
     952                    os.remove(new_path)
     953                    raise
    837954                if isinstance(message, MHMessage):
    838955                    self._dump_sequences(message, new_key)
     
    841958                    _unlock_file(f)
    842959        finally:
    843             _sync_close(f)
     960            if not closed:
     961                _sync_close(f)
    844962        return new_key
    845963
     
    854972            else:
    855973                raise
    856         try:
    857             if self._locked:
    858                 _lock_file(f)
    859             try:
    860                 f.close()
    861                 os.remove(os.path.join(self._path, str(key)))
    862             finally:
    863                 if self._locked:
    864                     _unlock_file(f)
    865         finally:
     974        else:
    866975            f.close()
     976            os.remove(path)
    867977
    868978    def __setitem__(self, key, message):
     
    13181428                self._file.write(line.replace('\n', os.linesep))
    13191429                if line == '\n' or line == '':
    1320                     self._file.write('*** EOOH ***' + os.linesep)
    13211430                    if first_pass:
    13221431                        first_pass = False
     1432                        self._file.write('*** EOOH ***' + os.linesep)
    13231433                        message.seek(original_pos)
    13241434                    else:
     
    18031913    def close(self):
    18041914        """Close the file."""
    1805         del self._file
     1915        if hasattr(self, '_file'):
     1916            if hasattr(self._file, 'close'):
     1917                self._file.close()
     1918            del self._file
    18061919
    18071920    def _read(self, size, read_method):
     
    18471960        return _ProxyFile._read(self, size, read_method)
    18481961
     1962    def close(self):
     1963        # do *not* close the underlying file object for partial files,
     1964        # since it's global to the mailbox object
     1965        if hasattr(self, '_file'):
     1966            del self._file
     1967
    18491968
    18501969def _lock_file(f, dotlock=True):
     
    18561975                fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    18571976            except IOError, e:
    1858                 if e.errno in (errno.EAGAIN, errno.EACCES):
     1977                if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS):
    18591978                    raise ExternalClashError('lockf: lock unavailable: %s' %
    18601979                                             f.name)
     
    18661985                pre_lock.close()
    18671986            except IOError, e:
    1868                 if e.errno == errno.EACCES:
     1987                if e.errno in (errno.EACCES, errno.EROFS):
    18691988                    return  # Without write access, just skip dotlocking.
    18701989                else:
Note: See TracChangeset for help on using the changeset viewer.