mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-12-23 18:04:52 +01:00
Implement Thread() and Threads()
Most of Thread() is implemented now and all of Threads(). Reorganized the source somewhat and various minor fixes throughout.
This commit is contained in:
parent
bb57345740
commit
2a14b523b0
4 changed files with 478 additions and 119 deletions
|
@ -1,6 +1,8 @@
|
||||||
import ctypes, os
|
import ctypes, os
|
||||||
from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool, byref
|
from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool, byref
|
||||||
from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
|
from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
|
||||||
|
from cnotmuch.thread import Thread, Threads
|
||||||
|
from cnotmuch.tags import Tags
|
||||||
import logging
|
import logging
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
@ -355,6 +357,10 @@ class Query(object):
|
||||||
A query selects and filters a subset of messages from the notmuch
|
A query selects and filters a subset of messages from the notmuch
|
||||||
database we derive from.
|
database we derive from.
|
||||||
|
|
||||||
|
Query() provides an instance attribute :attr:`sort`, which
|
||||||
|
contains the sort order (if specified via :meth:`set_sort`) or
|
||||||
|
`None`.
|
||||||
|
|
||||||
Technically, it wraps the underlying *notmuch_query_t* struct.
|
Technically, it wraps the underlying *notmuch_query_t* struct.
|
||||||
|
|
||||||
.. note:: Do remember that as soon as we tear down this object,
|
.. note:: Do remember that as soon as we tear down this object,
|
||||||
|
@ -371,6 +377,10 @@ class Query(object):
|
||||||
_create = nmlib.notmuch_query_create
|
_create = nmlib.notmuch_query_create
|
||||||
_create.restype = c_void_p
|
_create.restype = c_void_p
|
||||||
|
|
||||||
|
"""notmuch_query_search_threads"""
|
||||||
|
_search_threads = nmlib.notmuch_query_search_threads
|
||||||
|
_search_threads.restype = c_void_p
|
||||||
|
|
||||||
"""notmuch_query_search_messages"""
|
"""notmuch_query_search_messages"""
|
||||||
_search_messages = nmlib.notmuch_query_search_messages
|
_search_messages = nmlib.notmuch_query_search_messages
|
||||||
_search_messages.restype = c_void_p
|
_search_messages.restype = c_void_p
|
||||||
|
@ -389,6 +399,7 @@ class Query(object):
|
||||||
"""
|
"""
|
||||||
self._db = None
|
self._db = None
|
||||||
self._query = None
|
self._query = None
|
||||||
|
self.sort = None
|
||||||
self.create(db, querystr)
|
self.create(db, querystr)
|
||||||
|
|
||||||
def create(self, db, querystr):
|
def create(self, db, querystr):
|
||||||
|
@ -432,8 +443,35 @@ class Query(object):
|
||||||
if self._query is None:
|
if self._query is None:
|
||||||
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
self.sort = sort
|
||||||
nmlib.notmuch_query_set_sort(self._query, sort)
|
nmlib.notmuch_query_set_sort(self._query, sort)
|
||||||
|
|
||||||
|
def search_threads(self):
|
||||||
|
"""Execute a query for threads
|
||||||
|
|
||||||
|
Execute a query for threads, returning a :class:`Threads` iterator.
|
||||||
|
The returned threads are owned by the query and as such, will only be
|
||||||
|
valid until the Query is deleted.
|
||||||
|
|
||||||
|
Technically, it wraps the underlying
|
||||||
|
*notmuch_query_search_threads* function.
|
||||||
|
|
||||||
|
:returns: :class:`Threads`
|
||||||
|
:exception: :exc:`NotmuchError`
|
||||||
|
|
||||||
|
* STATUS.NOT_INITIALIZED if query is not inited
|
||||||
|
* STATUS.NULL_POINTER if search_messages failed
|
||||||
|
"""
|
||||||
|
if self._query is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
threads_p = Query._search_threads(self._query)
|
||||||
|
|
||||||
|
if threads_p is None:
|
||||||
|
NotmuchError(STATUS.NULL_POINTER)
|
||||||
|
|
||||||
|
return Threads(threads_p,self)
|
||||||
|
|
||||||
def search_messages(self):
|
def search_messages(self):
|
||||||
"""Filter messages according to the query and return
|
"""Filter messages according to the query and return
|
||||||
:class:`Messages` in the defined sort order
|
:class:`Messages` in the defined sort order
|
||||||
|
@ -483,115 +521,6 @@ class Query(object):
|
||||||
logging.debug("Freeing the Query now")
|
logging.debug("Freeing the Query now")
|
||||||
nmlib.notmuch_query_destroy (self._query)
|
nmlib.notmuch_query_destroy (self._query)
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
|
||||||
class Tags(object):
|
|
||||||
"""Represents a list of notmuch tags
|
|
||||||
|
|
||||||
This object provides an iterator over a list of notmuch tags. Do
|
|
||||||
note that the underlying library only provides a one-time iterator
|
|
||||||
(it cannot reset the iterator to the start). Thus iterating over
|
|
||||||
the function will "exhaust" the list of tags, and a subsequent
|
|
||||||
iteration attempt will raise a :exc:`NotmuchError`
|
|
||||||
STATUS.NOT_INITIALIZED. Also note, that any function that uses
|
|
||||||
iteration (nearly all) will also exhaust the tags. So both::
|
|
||||||
|
|
||||||
for tag in tags: print tag
|
|
||||||
|
|
||||||
as well as::
|
|
||||||
|
|
||||||
number_of_tags = len(tags)
|
|
||||||
|
|
||||||
and even a simple::
|
|
||||||
|
|
||||||
#str() iterates over all tags to construct a space separated list
|
|
||||||
print(str(tags))
|
|
||||||
|
|
||||||
will "exhaust" the Tags. If you need to re-iterate over a list of
|
|
||||||
tags you will need to retrieve a new :class:`Tags` object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#notmuch_tags_get
|
|
||||||
_get = nmlib.notmuch_tags_get
|
|
||||||
_get.restype = c_char_p
|
|
||||||
|
|
||||||
def __init__(self, tags_p, parent=None):
|
|
||||||
"""
|
|
||||||
:param tags_p: A pointer to an underlying *notmuch_tags_t*
|
|
||||||
structure. These are not publically exposed, so a user
|
|
||||||
will almost never instantiate a :class:`Tags` object
|
|
||||||
herself. They are usually handed back as a result,
|
|
||||||
e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
|
|
||||||
valid, we will raise an :exc:`NotmuchError`
|
|
||||||
(STATUS.NULL_POINTER) if it is `None`.
|
|
||||||
:type tags_p: :class:`ctypes.c_void_p`
|
|
||||||
:param parent: The parent object (ie :class:`Database` or
|
|
||||||
:class:`Message` these tags are derived from, and saves a
|
|
||||||
reference to it, so we can automatically delete the db object
|
|
||||||
once all derived objects are dead.
|
|
||||||
:TODO: Make the iterator optionally work more than once by
|
|
||||||
cache the tags in the Python object(?)
|
|
||||||
"""
|
|
||||||
if tags_p is None:
|
|
||||||
NotmuchError(STATUS.NULL_POINTER)
|
|
||||||
|
|
||||||
self._tags = tags_p
|
|
||||||
#save reference to parent object so we keep it alive
|
|
||||||
self._parent = parent
|
|
||||||
logging.debug("Inited Tags derived from %s" %(repr(parent)))
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
""" Make Tags an iterator """
|
|
||||||
return self
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
if self._tags is None:
|
|
||||||
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
|
||||||
|
|
||||||
if not nmlib.notmuch_tags_valid(self._tags):
|
|
||||||
self._tags = None
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
tag = Tags._get (self._tags)
|
|
||||||
nmlib.notmuch_tags_move_to_next(self._tags)
|
|
||||||
return tag
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
"""len(:class:`Tags`) returns the number of contained tags
|
|
||||||
|
|
||||||
.. note:: As this iterates over the tags, we will not be able
|
|
||||||
to iterate over them again (as in retrieve them)! If
|
|
||||||
the tags have been exhausted already, this will raise a
|
|
||||||
:exc:`NotmuchError` STATUS.NOT_INITIALIZED on
|
|
||||||
subsequent attempts.
|
|
||||||
"""
|
|
||||||
if self._tags is None:
|
|
||||||
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
|
||||||
|
|
||||||
i=0
|
|
||||||
while nmlib.notmuch_tags_valid(self._msgs):
|
|
||||||
nmlib.notmuch_tags_move_to_next(self._msgs)
|
|
||||||
i += 1
|
|
||||||
self._tags = None
|
|
||||||
return i
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""The str() representation of Tags() is a space separated list of tags
|
|
||||||
|
|
||||||
.. note:: As this iterates over the tags, we will not be able
|
|
||||||
to iterate over them again (as in retrieve them)! If
|
|
||||||
the tags have been exhausted already, this will raise a
|
|
||||||
:exc:`NotmuchError` STATUS.NOT_INITIALIZED on
|
|
||||||
subsequent attempts.
|
|
||||||
"""
|
|
||||||
return " ".join(self)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""Close and free the notmuch tags"""
|
|
||||||
if self._tags is not None:
|
|
||||||
logging.debug("Freeing the Tags now")
|
|
||||||
nmlib.notmuch_tags_destroy (self._tags)
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
class Messages(object):
|
class Messages(object):
|
||||||
"""Represents a list of notmuch messages
|
"""Represents a list of notmuch messages
|
||||||
|
@ -721,6 +650,12 @@ class Messages(object):
|
||||||
if len(msgs) > 0: #this 'exhausts' msgs
|
if len(msgs) > 0: #this 'exhausts' msgs
|
||||||
# next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
|
# next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
|
||||||
for msg in msgs: print msg
|
for msg in msgs: print msg
|
||||||
|
|
||||||
|
Most of the time, using the
|
||||||
|
:meth:`Query.count_messages` is therefore more
|
||||||
|
appropriate (and much faster). While not guaranteeing
|
||||||
|
that it will return the exact same number than len(),
|
||||||
|
in my tests it effectively always did so.
|
||||||
"""
|
"""
|
||||||
if self._msgs is None:
|
if self._msgs is None:
|
||||||
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
@ -855,7 +790,7 @@ class Message(object):
|
||||||
message call notmuch_message_get_header() with a header value of
|
message call notmuch_message_get_header() with a header value of
|
||||||
"date".
|
"date".
|
||||||
|
|
||||||
:returns: a time_t timestamp
|
:returns: A time_t timestamp.
|
||||||
:rtype: c_unit64
|
:rtype: c_unit64
|
||||||
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
||||||
is not initialized.
|
is not initialized.
|
||||||
|
@ -892,7 +827,7 @@ class Message(object):
|
||||||
return header
|
return header
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
"""Return the file path of the message file
|
"""Returns the file path of the message file
|
||||||
|
|
||||||
:returns: Absolute file path & name of the message file
|
:returns: Absolute file path & name of the message file
|
||||||
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
||||||
|
@ -903,10 +838,9 @@ class Message(object):
|
||||||
return Message._get_filename(self._msg)
|
return Message._get_filename(self._msg)
|
||||||
|
|
||||||
def get_tags(self):
|
def get_tags(self):
|
||||||
""" Return the message tags
|
"""Returns the message tags
|
||||||
|
|
||||||
:returns: Message tags
|
:returns: A :class:`Tags` iterator.
|
||||||
:rtype: :class:`Tags`
|
|
||||||
:exception: :exc:`NotmuchError`
|
:exception: :exc:`NotmuchError`
|
||||||
|
|
||||||
* STATUS.NOT_INITIALIZED if the message
|
* STATUS.NOT_INITIALIZED if the message
|
||||||
|
@ -922,7 +856,7 @@ class Message(object):
|
||||||
return Tags(tags_p, self)
|
return Tags(tags_p, self)
|
||||||
|
|
||||||
def add_tag(self, tag):
|
def add_tag(self, tag):
|
||||||
"""Add a tag to the given message
|
"""Adds a tag to the given message
|
||||||
|
|
||||||
Adds a tag to the current message. The maximal tag length is defined in
|
Adds a tag to the current message. The maximal tag length is defined in
|
||||||
the notmuch library and is currently 200 bytes.
|
the notmuch library and is currently 200 bytes.
|
||||||
|
|
|
@ -34,9 +34,7 @@ Many of its objects use python's logging module to log some output at DEBUG leve
|
||||||
:class:`Message`, and :class:`Tags`.
|
:class:`Message`, and :class:`Tags`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import ctypes
|
from database import Database, Query
|
||||||
from ctypes import c_int, c_char_p
|
|
||||||
from database import Database,Tags,Query,Messages,Message,Tags
|
|
||||||
from cnotmuch.globals import nmlib, STATUS, NotmuchError
|
from cnotmuch.globals import nmlib, STATUS, NotmuchError
|
||||||
__LICENSE__="GPL v3+"
|
__LICENSE__="GPL v3+"
|
||||||
__VERSION__='0.1.1'
|
__VERSION__='0.1.1'
|
||||||
|
|
108
cnotmuch/tags.py
Normal file
108
cnotmuch/tags.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
from ctypes import c_char_p
|
||||||
|
from cnotmuch.globals import nmlib, STATUS, NotmuchError
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
class Tags(object):
|
||||||
|
"""Represents a list of notmuch tags
|
||||||
|
|
||||||
|
This object provides an iterator over a list of notmuch tags. Do
|
||||||
|
note that the underlying library only provides a one-time iterator
|
||||||
|
(it cannot reset the iterator to the start). Thus iterating over
|
||||||
|
the function will "exhaust" the list of tags, and a subsequent
|
||||||
|
iteration attempt will raise a :exc:`NotmuchError`
|
||||||
|
STATUS.NOT_INITIALIZED. Also note, that any function that uses
|
||||||
|
iteration (nearly all) will also exhaust the tags. So both::
|
||||||
|
|
||||||
|
for tag in tags: print tag
|
||||||
|
|
||||||
|
as well as::
|
||||||
|
|
||||||
|
number_of_tags = len(tags)
|
||||||
|
|
||||||
|
and even a simple::
|
||||||
|
|
||||||
|
#str() iterates over all tags to construct a space separated list
|
||||||
|
print(str(tags))
|
||||||
|
|
||||||
|
will "exhaust" the Tags. If you need to re-iterate over a list of
|
||||||
|
tags you will need to retrieve a new :class:`Tags` object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#notmuch_tags_get
|
||||||
|
_get = nmlib.notmuch_tags_get
|
||||||
|
_get.restype = c_char_p
|
||||||
|
|
||||||
|
def __init__(self, tags_p, parent=None):
|
||||||
|
"""
|
||||||
|
:param tags_p: A pointer to an underlying *notmuch_tags_t*
|
||||||
|
structure. These are not publically exposed, so a user
|
||||||
|
will almost never instantiate a :class:`Tags` object
|
||||||
|
herself. They are usually handed back as a result,
|
||||||
|
e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
|
||||||
|
valid, we will raise an :exc:`NotmuchError`
|
||||||
|
(STATUS.NULL_POINTER) if it is `None`.
|
||||||
|
:type tags_p: :class:`ctypes.c_void_p`
|
||||||
|
:param parent: The parent object (ie :class:`Database` or
|
||||||
|
:class:`Message` these tags are derived from, and saves a
|
||||||
|
reference to it, so we can automatically delete the db object
|
||||||
|
once all derived objects are dead.
|
||||||
|
:TODO: Make the iterator optionally work more than once by
|
||||||
|
cache the tags in the Python object(?)
|
||||||
|
"""
|
||||||
|
if tags_p is None:
|
||||||
|
NotmuchError(STATUS.NULL_POINTER)
|
||||||
|
|
||||||
|
self._tags = tags_p
|
||||||
|
#save reference to parent object so we keep it alive
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
""" Make Tags an iterator """
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self._tags is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
if not nmlib.notmuch_tags_valid(self._tags):
|
||||||
|
self._tags = None
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
tag = Tags._get (self._tags)
|
||||||
|
nmlib.notmuch_tags_move_to_next(self._tags)
|
||||||
|
return tag
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""len(:class:`Tags`) returns the number of contained tags
|
||||||
|
|
||||||
|
.. note:: As this iterates over the tags, we will not be able
|
||||||
|
to iterate over them again (as in retrieve them)! If
|
||||||
|
the tags have been exhausted already, this will raise a
|
||||||
|
:exc:`NotmuchError` STATUS.NOT_INITIALIZED on
|
||||||
|
subsequent attempts.
|
||||||
|
"""
|
||||||
|
if self._tags is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while nmlib.notmuch_tags_valid(self._msgs):
|
||||||
|
nmlib.notmuch_tags_move_to_next(self._msgs)
|
||||||
|
i += 1
|
||||||
|
self._tags = None
|
||||||
|
return i
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""The str() representation of Tags() is a space separated list of tags
|
||||||
|
|
||||||
|
.. note:: As this iterates over the tags, we will not be able
|
||||||
|
to iterate over them again (as in retrieve them)! If
|
||||||
|
the tags have been exhausted already, this will raise a
|
||||||
|
:exc:`NotmuchError` STATUS.NOT_INITIALIZED on
|
||||||
|
subsequent attempts.
|
||||||
|
"""
|
||||||
|
return " ".join(self)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Close and free the notmuch tags"""
|
||||||
|
if self._tags is not None:
|
||||||
|
nmlib.notmuch_tags_destroy (self._tags)
|
319
cnotmuch/thread.py
Normal file
319
cnotmuch/thread.py
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
from ctypes import c_char_p, c_void_p, c_uint64
|
||||||
|
from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
|
||||||
|
from cnotmuch.tags import Tags
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
class Threads(object):
|
||||||
|
"""Represents a list of notmuch threads
|
||||||
|
|
||||||
|
This object provides an iterator over a list of notmuch threads
|
||||||
|
(Technically, it provides a wrapper for the underlying
|
||||||
|
*notmuch_threads_t* structure). Do note that the underlying
|
||||||
|
library only provides a one-time iterator (it cannot reset the
|
||||||
|
iterator to the start). Thus iterating over the function will
|
||||||
|
"exhaust" the list of threads, and a subsequent iteration attempt
|
||||||
|
will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
|
||||||
|
note, that any function that uses iteration will also
|
||||||
|
exhaust the messages. So both::
|
||||||
|
|
||||||
|
for thread in threads: print thread
|
||||||
|
|
||||||
|
as well as::
|
||||||
|
|
||||||
|
number_of_msgs = len(threads)
|
||||||
|
|
||||||
|
will "exhaust" the threads. If you need to re-iterate over a list of
|
||||||
|
messages you will need to retrieve a new :class:`Threads` object.
|
||||||
|
|
||||||
|
Things are not as bad as it seems though, you can store and reuse
|
||||||
|
the single Thread objects as often as you want as long as you
|
||||||
|
keep the parent Threads object around. (Recall that due to
|
||||||
|
hierarchical memory allocation, all derived Threads objects will
|
||||||
|
be invalid when we delete the parent Threads() object, even if it
|
||||||
|
was already "exhausted".) So this works::
|
||||||
|
|
||||||
|
db = Database()
|
||||||
|
threads = Query(db,'').search_threads() #get a Threads() object
|
||||||
|
threadlist = []
|
||||||
|
for thread in threads:
|
||||||
|
threadlist.append(thread)
|
||||||
|
|
||||||
|
# threads is "exhausted" now and even len(threads) will raise an
|
||||||
|
# exception.
|
||||||
|
# However it will be kept around until all retrieved Thread() objects are
|
||||||
|
# also deleted. If you did e.g. an explicit del(threads) here, the
|
||||||
|
# following lines would fail.
|
||||||
|
|
||||||
|
# You can reiterate over *threadlist* however as often as you want.
|
||||||
|
# It is simply a list with Thread objects.
|
||||||
|
|
||||||
|
print (threadlist[0].get_thread_id())
|
||||||
|
print (threadlist[1].get_thread_id())
|
||||||
|
print (threadlist[0].get_total_messages())
|
||||||
|
"""
|
||||||
|
|
||||||
|
#notmuch_threads_get
|
||||||
|
_get = nmlib.notmuch_threads_get
|
||||||
|
_get.restype = c_void_p
|
||||||
|
|
||||||
|
def __init__(self, threads_p, parent=None):
|
||||||
|
"""
|
||||||
|
:param threads_p: A pointer to an underlying *notmuch_threads_t*
|
||||||
|
structure. These are not publically exposed, so a user
|
||||||
|
will almost never instantiate a :class:`Threads` object
|
||||||
|
herself. They are usually handed back as a result,
|
||||||
|
e.g. in :meth:`Query.search_threads`. *threads_p* must be
|
||||||
|
valid, we will raise an :exc:`NotmuchError`
|
||||||
|
(STATUS.NULL_POINTER) if it is `None`.
|
||||||
|
:type threads_p: :class:`ctypes.c_void_p`
|
||||||
|
:param parent: The parent object
|
||||||
|
(ie :class:`Query`) these tags are derived from. It saves
|
||||||
|
a reference to it, so we can automatically delete the db
|
||||||
|
object once all derived objects are dead.
|
||||||
|
:TODO: Make the iterator work more than once and cache the tags in
|
||||||
|
the Python object.(?)
|
||||||
|
"""
|
||||||
|
if threads_p is None:
|
||||||
|
NotmuchError(STATUS.NULL_POINTER)
|
||||||
|
|
||||||
|
self._threads = threads_p
|
||||||
|
#store parent, so we keep them alive as long as self is alive
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
""" Make Threads an iterator """
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self._threads is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
if not nmlib.notmuch_threads_valid(self._threads):
|
||||||
|
self._threads = None
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
thread = Thread(Threads._get (self._threads), self)
|
||||||
|
nmlib.notmuch_threads_move_to_next(self._threads)
|
||||||
|
return thread
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""len(:class:`Threads`) returns the number of contained Threads
|
||||||
|
|
||||||
|
.. note:: As this iterates over the threads, we will not be able to
|
||||||
|
iterate over them again! So this will fail::
|
||||||
|
|
||||||
|
#THIS FAILS
|
||||||
|
threads = Database().create_query('').search_threads()
|
||||||
|
if len(threads) > 0: #this 'exhausts' threads
|
||||||
|
# next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
|
||||||
|
for thread in threads: print thread
|
||||||
|
"""
|
||||||
|
if self._threads is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
i=0
|
||||||
|
# returns 'bool'. On out-of-memory it returns None
|
||||||
|
while nmlib.notmuch_threads_valid(self._threads):
|
||||||
|
nmlib.notmuch_threads_move_to_next(self._threads)
|
||||||
|
i += 1
|
||||||
|
# reset self._threads to mark as "exhausted"
|
||||||
|
self._threads = None
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Close and free the notmuch Threads"""
|
||||||
|
if self._threads is not None:
|
||||||
|
nmlib.notmuch_messages_destroy (self._threads)
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
class Thread(object):
|
||||||
|
"""Represents a single message thread."""
|
||||||
|
|
||||||
|
"""notmuch_thread_get_thread_id"""
|
||||||
|
_get_thread_id = nmlib.notmuch_thread_get_thread_id
|
||||||
|
_get_thread_id.restype = c_char_p
|
||||||
|
|
||||||
|
"""notmuch_thread_get_authors"""
|
||||||
|
_get_authors = nmlib.notmuch_thread_get_authors
|
||||||
|
_get_authors.restype = c_char_p
|
||||||
|
|
||||||
|
"""notmuch_thread_get_subject"""
|
||||||
|
_get_subject = nmlib.notmuch_thread_get_subject
|
||||||
|
_get_subject.restype = c_char_p
|
||||||
|
|
||||||
|
_get_newest_date = nmlib.notmuch_thread_get_newest_date
|
||||||
|
_get_newest_date.restype = c_uint64
|
||||||
|
|
||||||
|
_get_oldest_date = nmlib.notmuch_thread_get_oldest_date
|
||||||
|
_get_oldest_date.restype = c_uint64
|
||||||
|
|
||||||
|
"""notmuch_thread_get_tags"""
|
||||||
|
_get_tags = nmlib.notmuch_thread_get_tags
|
||||||
|
_get_tags.restype = c_void_p
|
||||||
|
|
||||||
|
def __init__(self, thread_p, parent=None):
|
||||||
|
"""
|
||||||
|
:param thread_p: A pointer to an internal notmuch_thread_t
|
||||||
|
Structure. These are not publically exposed, so a user
|
||||||
|
will almost never instantiate a :class:`Thread` object
|
||||||
|
herself. They are usually handed back as a result,
|
||||||
|
e.g. when iterating through :class:`Threads`. *thread_p*
|
||||||
|
must be valid, we will raise an :exc:`NotmuchError`
|
||||||
|
(STATUS.NULL_POINTER) if it is `None`.
|
||||||
|
|
||||||
|
:param parent: A 'parent' object is passed which this message is
|
||||||
|
derived from. We save a reference to it, so we can
|
||||||
|
automatically delete the parent object once all derived
|
||||||
|
objects are dead.
|
||||||
|
"""
|
||||||
|
if thread_p is None:
|
||||||
|
NotmuchError(STATUS.NULL_POINTER)
|
||||||
|
self._thread = thread_p
|
||||||
|
#keep reference to parent, so we keep it alive
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
def get_thread_id(self):
|
||||||
|
"""Get the thread ID of 'thread'
|
||||||
|
|
||||||
|
The returned string belongs to 'thread' and will only be valid
|
||||||
|
for as long as the thread is valid.
|
||||||
|
|
||||||
|
:returns: String with a message ID
|
||||||
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
|
||||||
|
is not initialized.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return Thread._get_thread_id(self._thread)
|
||||||
|
|
||||||
|
def get_total_messages(self):
|
||||||
|
"""Get the total number of messages in 'thread'
|
||||||
|
|
||||||
|
:returns: The number of all messages in the database
|
||||||
|
belonging to this thread. Contrast with
|
||||||
|
:meth:`get_matched_messages`.
|
||||||
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
|
||||||
|
is not initialized.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return nmlib.notmuch_thread_get_total_messages(self._thread)
|
||||||
|
|
||||||
|
|
||||||
|
###TODO: notmuch_messages_t * notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread);
|
||||||
|
|
||||||
|
def get_matched_messages(self):
|
||||||
|
"""Returns the number of messages in 'thread' that matched the query
|
||||||
|
|
||||||
|
:returns: The number of all messages belonging to this thread that
|
||||||
|
matched the :class:`Query`from which this thread was created.
|
||||||
|
Contrast with :meth:`get_total_messages`.
|
||||||
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
|
||||||
|
is not initialized.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return nmlib.notmuch_thread_get_matched_messages(self._thread)
|
||||||
|
|
||||||
|
def get_authors(self):
|
||||||
|
"""Returns the authors of 'thread'
|
||||||
|
|
||||||
|
The returned string is a comma-separated list of the names of the
|
||||||
|
authors of mail messages in the query results that belong to this
|
||||||
|
thread.
|
||||||
|
|
||||||
|
The returned string belongs to 'thread' and will only be valid for
|
||||||
|
as long as this Thread() is not deleted.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return Thread._get_authors(self._thread)
|
||||||
|
|
||||||
|
def get_subject(self):
|
||||||
|
"""Returns the Subject of 'thread'
|
||||||
|
|
||||||
|
The returned string belongs to 'thread' and will only be valid for
|
||||||
|
as long as this Thread() is not deleted.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return Thread._get_subject(self._thread)
|
||||||
|
|
||||||
|
def get_newest_date(self):
|
||||||
|
"""Returns time_t of the newest message date
|
||||||
|
|
||||||
|
:returns: A time_t timestamp.
|
||||||
|
:rtype: c_unit64
|
||||||
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
||||||
|
is not initialized.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return Thread._get_newest_date(self._thread)
|
||||||
|
|
||||||
|
def get_oldest_date(self):
|
||||||
|
"""Returns time_t of the oldest message date
|
||||||
|
|
||||||
|
:returns: A time_t timestamp.
|
||||||
|
:rtype: c_unit64
|
||||||
|
:exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
|
||||||
|
is not initialized.
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
return Thread._get_oldest_date(self._thread)
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
""" Returns the message tags
|
||||||
|
|
||||||
|
In the Notmuch database, tags are stored on individual
|
||||||
|
messages, not on threads. So the tags returned here will be all
|
||||||
|
tags of the messages which matched the search and which belong to
|
||||||
|
this thread.
|
||||||
|
|
||||||
|
The :class:`Tags` object is owned by the thread and as such, will only
|
||||||
|
be valid for as long as this :class:`Thread` is valid (e.g. until the
|
||||||
|
query from which it derived is explicitely deleted).
|
||||||
|
|
||||||
|
:returns: A :class:`Tags` iterator.
|
||||||
|
:exception: :exc:`NotmuchError`
|
||||||
|
|
||||||
|
* STATUS.NOT_INITIALIZED if the thread
|
||||||
|
is not initialized.
|
||||||
|
* STATUS.NULL_POINTER, on error
|
||||||
|
"""
|
||||||
|
if self._thread is None:
|
||||||
|
raise NotmuchError(STATUS.NOT_INITIALIZED)
|
||||||
|
|
||||||
|
tags_p = Thread._get_tags(self._thread)
|
||||||
|
if tags_p == None:
|
||||||
|
raise NotmuchError(STATUS.NULL_POINTER)
|
||||||
|
return Tags(tags_p, self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""A str(Thread()) is represented by a 1-line summary"""
|
||||||
|
thread = {}
|
||||||
|
thread['id'] = self.get_thread_id()
|
||||||
|
|
||||||
|
###TODO: How do we find out the current sort order of Threads?
|
||||||
|
###Add a "sort" attribute to the Threads() object?
|
||||||
|
#if (sort == NOTMUCH_SORT_OLDEST_FIRST)
|
||||||
|
# date = notmuch_thread_get_oldest_date (thread);
|
||||||
|
#else
|
||||||
|
# date = notmuch_thread_get_newest_date (thread);
|
||||||
|
thread['date'] = date.fromtimestamp(self.get_newest_date())
|
||||||
|
thread['matched'] = self.get_matched_messages()
|
||||||
|
thread['total'] = self.get_total_messages()
|
||||||
|
thread['authors'] = self.get_authors()
|
||||||
|
thread['subject'] = self.get_subject()
|
||||||
|
thread['tags'] = self.get_tags()
|
||||||
|
|
||||||
|
return "thread:%(id)s %(date)12s [%(matched)d/%(total)d] %(authors)s; %(subject)s (%(tags)s)" % (thread)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Close and free the notmuch Thread"""
|
||||||
|
if self._thread is not None:
|
||||||
|
nmlib.notmuch_thread_destroy (self._thread)
|
Loading…
Reference in a new issue