1"""
2This file is part of notmuch.
3
4Notmuch is free software: you can redistribute it and/or modify it
5under the terms of the GNU General Public License as published by the
6Free Software Foundation, either version 3 of the License, or (at your
7option) any later version.
8
9Notmuch is distributed in the hope that it will be useful, but WITHOUT
10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12for more details.
13
14You should have received a copy of the GNU General Public License
15along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
16
17Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
18"""
19
20from ctypes import c_uint, c_long
21from .globals import (
22    nmlib,
23    NotmuchDirectoryP,
24    NotmuchFilenamesP
25)
26from .errors import (
27    STATUS,
28    NotmuchError,
29    NotInitializedError,
30)
31from .filenames import Filenames
32
33class Directory(object):
34    """Represents a directory entry in the notmuch directory
35
36    Modifying attributes of this object will modify the
37    database, not the real directory attributes.
38
39    The Directory object is usually derived from another object
40    e.g. via :meth:`Database.get_directory`, and will automatically be
41    become invalid whenever that parent is deleted. You should
42    therefore initialized this object handing it a reference to the
43    parent, preventing the parent from automatically being garbage
44    collected.
45    """
46
47    """notmuch_directory_get_mtime"""
48    _get_mtime = nmlib.notmuch_directory_get_mtime
49    _get_mtime.argtypes = [NotmuchDirectoryP]
50    _get_mtime.restype = c_long
51
52    """notmuch_directory_set_mtime"""
53    _set_mtime = nmlib.notmuch_directory_set_mtime
54    _set_mtime.argtypes = [NotmuchDirectoryP, c_long]
55    _set_mtime.restype = c_uint
56
57    """notmuch_directory_get_child_files"""
58    _get_child_files = nmlib.notmuch_directory_get_child_files
59    _get_child_files.argtypes = [NotmuchDirectoryP]
60    _get_child_files.restype = NotmuchFilenamesP
61
62    """notmuch_directory_get_child_directories"""
63    _get_child_directories = nmlib.notmuch_directory_get_child_directories
64    _get_child_directories.argtypes = [NotmuchDirectoryP]
65    _get_child_directories.restype = NotmuchFilenamesP
66
67    def _assert_dir_is_initialized(self):
68        """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
69        if dir_p is None"""
70        if not self._dir_p:
71            raise NotInitializedError()
72
73    def __init__(self, path, dir_p, parent):
74        """
75        :param path:   The absolute path of the directory object.
76        :param dir_p:  The pointer to an internal notmuch_directory_t object.
77        :param parent: The object this Directory is derived from
78                       (usually a :class:`Database`). We do not directly use
79                       this, but store a reference to it as long as
80                       this Directory object lives. This keeps the
81                       parent object alive.
82        """
83        self._path = path
84        self._dir_p = dir_p
85        self._parent = parent
86
87    def set_mtime(self, mtime):
88        """Sets the mtime value of this directory in the database
89
90        The intention is for the caller to use the mtime to allow efficient
91        identification of new messages to be added to the database. The
92        recommended usage is as follows:
93
94        * Read the mtime of a directory from the filesystem
95
96        * Call :meth:`Database.index_file` for all mail files in
97          the directory
98
99        * Call notmuch_directory_set_mtime with the mtime read from the
100          filesystem.  Then, when wanting to check for updates to the
101          directory in the future, the client can call :meth:`get_mtime`
102          and know that it only needs to add files if the mtime of the
103          directory and files are newer than the stored timestamp.
104
105          .. note::
106
107                :meth:`get_mtime` function does not allow the caller to
108                distinguish a timestamp of 0 from a non-existent timestamp. So
109                don't store a timestamp of 0 unless you are comfortable with
110                that.
111
112        :param mtime: A (time_t) timestamp
113        :raises: :exc:`XapianError` a Xapian exception occurred, mtime
114                 not stored
115        :raises: :exc:`ReadOnlyDatabaseError` the database was opened
116                 in read-only mode so directory mtime cannot be modified
117        :raises: :exc:`NotInitializedError` the directory object has not
118                 been initialized
119        """
120        self._assert_dir_is_initialized()
121        status = Directory._set_mtime(self._dir_p, mtime)
122
123        if status != STATUS.SUCCESS:
124            raise NotmuchError(status)
125
126    def get_mtime(self):
127        """Gets the mtime value of this directory in the database
128
129        Retrieves a previously stored mtime for this directory.
130
131        :param mtime: A (time_t) timestamp
132        :raises: :exc:`NotmuchError`:
133
134                        :attr:`STATUS`.NOT_INITIALIZED
135                          The directory has not been initialized
136        """
137        self._assert_dir_is_initialized()
138        return Directory._get_mtime(self._dir_p)
139
140    # Make mtime attribute a property of Directory()
141    mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
142                     and setting of the Directory *mtime* (read-write)
143
144                     See :meth:`get_mtime` and :meth:`set_mtime` for usage and
145                     possible exceptions.""")
146
147    def get_child_files(self):
148        """Gets a Filenames iterator listing all the filenames of
149        messages in the database within the given directory.
150
151        The returned filenames will be the basename-entries only (not
152        complete paths.
153        """
154        self._assert_dir_is_initialized()
155        files_p = Directory._get_child_files(self._dir_p)
156        return Filenames(files_p, self)
157
158    def get_child_directories(self):
159        """Gets a :class:`Filenames` iterator listing all the filenames of
160        sub-directories in the database within the given directory
161
162        The returned filenames will be the basename-entries only (not
163        complete paths.
164        """
165        self._assert_dir_is_initialized()
166        files_p = Directory._get_child_directories(self._dir_p)
167        return Filenames(files_p, self)
168
169    @property
170    def path(self):
171        """Returns the absolute path of this Directory (read-only)"""
172        return self._path
173
174    def __repr__(self):
175        """Object representation"""
176        return "<notmuch Directory object '%s'>" % self._path
177
178    _destroy = nmlib.notmuch_directory_destroy
179    _destroy.argtypes = [NotmuchDirectoryP]
180    _destroy.restype = None
181
182    def __del__(self):
183        """Close and free the Directory"""
184        if self._dir_p:
185            self._destroy(self._dir_p)
186