summaryrefslogtreecommitdiff
path: root/distixlib/ticket_saver.py
blob: bc182e0507dfde44fb46d36c6fc5f1b5d7e9ed62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# 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 <http://www.gnu.org/licenses/>.
#
# =*= 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