# Copyright 2014 Lars Wirzenius # # 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 3 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, see . # # =*= License: GPL-3+ =*= import contextlib import mailbox import os import distixlib class TicketDirectoryExistsError(distixlib.StructuredError): msg = "Can't save to directory {dirname}: it already exists" class TicketDirectoryDoesNotExistError(distixlib.StructuredError): msg = ( "Can't save ticket changes to directory {dirname}: " "it does not exist") class TicketSaver(object): _tag_name = '.this-dir-not-empty' def create_ticket_on_disk(self, ticket, dirname): '''Create new ticket on disk. Return list of pathnames to regular files that got created. ''' if os.path.exists(dirname): raise distixlib.TicketDirectoryExistsError(dirname=dirname) self._create_ticket_directory(dirname) filenames = [self._save_ticket_metadata(ticket, dirname)] return filenames def _create_ticket_directory(self, dirname): os.mkdir(dirname) def _save_ticket_metadata(self, ticket, dirname): filename = os.path.join(dirname, 'ticket.yaml') saver = distixlib.MetadataSaver() saver.save_to_file(filename, ticket.get_ticket_metadata()) return filename def _save_messages(self, messages, dirname): # pragma: no cover maildir_pathname = self._get_maildir_pathname(dirname) maildir = self._open_maildir(maildir_pathname) filenames = self._tag_empty_maildir_subdirs(maildir_pathname) with contextlib.closing(maildir): return ( filenames + self._save_messages_to_maildir(messages, maildir)) def _get_maildir_pathname(self, dirname): # pragma: no cover return os.path.join(dirname, distixlib.maildir_name) def _tag_empty_maildir_subdirs(self, maildir_pathname): # pragma: no cover subdirs = ('tmp', 'new', 'cur') filenames = [ os.path.join(maildir_pathname, subdir, self._tag_name) for subdir in subdirs] for filename in filenames: self._tag_directory(filename) return filenames def _tag_directory(self, dirname): # pragma: no cover # Python's mailbox.Maildir does NOT ignore files with a # leading dot, despite the spec # (http://cr.yp.to/proto/maildir.html) clearly saying it should. # We work around this by creating a dot-something subdirectory # putting an empty file there. subdir = os.path.join(dirname, '.empty') if not os.path.exists(subdir): os.makedirs(subdir) filename = os.path.join(subdir, 'empty-file') with open(filename, 'wb'): pass def _save_messages_to_maildir(self, messages, maildir): # pragma: no cover filenames = [] for message in messages: filenames.extend(self._save_message_to_maildir(message, maildir)) return filenames def _save_message_to_maildir(self, message, maildir): # pragma: no cover key = maildir.add(message) with contextlib.closing(maildir.get_file(key)) as f: # The following is really ugly. It uses a private # attribute of the file-like object that the Maildir # object returns. The file-like object is, in Python 2.7, # an instance of mailbox._ProxyFile, and for Maildir # objects it wraps around a real file object. Real file # objects store the name of the file they access in the # name attribute. # # I could not find any other name to get the name of the # file of the message we have just added to the Maildir. return [f._file.name] def _open_maildir(self, maildir_path): # pragma: no cover return mailbox.Maildir(maildir_path, factory=None, create=True) def save_changes_to_ticket(self, ticket, dirname): # pragma: no cover if not os.path.exists(dirname): raise distixlib.TicketDirectoryDoesNotExistError( dirname=dirname) filenames = [self._save_ticket_metadata(ticket, dirname)] filenames.extend( self._save_messages(ticket.get_dirty_messages(), dirname)) return filenames