Safe Haskell | None |
---|---|
Language | Haskell2010 |
High-level interface to the notmuch mail indexer.
Example program to add/remove a tag on all messages matching a query:
main :: IO () main = getArgs >>= \args -> case args of [path, expr, '+':tag] -> go path expr tagmessageAddTag
[path, expr, '-':tag] -> go path expr tagmessageRemoveTag
_ ->die
"usage: hs-notmuch-tag-set DB-DIR SEARCH-TERM +TAG|-TAG" where go path expr tag f =runExceptT
(do db <-databaseOpen
pathquery
db (Bare
expr) >>=messages
>>= traverse_ (f (fromString
tag))databaseDestroy
db ) >>= either (die . (show ::Status
-> String)) pure
File descriptor exhaustion
Some Message
operations cause the message file to be opened (and
remain open until the object gets garbage collected):
messageHeader
will open the file to read the headers, except for theFrom
,Subject
andMessage-Id
headers which are indexed.
If the RTS is using a multi-generation collector (the default), and if you are working with lots of messages, you may hit max open files limits. The best way to avoid this is to avoid the scenarios outlined above. Alternative approaches that could help include:
- Use a single-generation collector (build with
-rtsopts
and run with+RTS -G1
). This incurs the cost of scanning the entire heap on every GC run. - In an interactive program, build with
-threaded
to enable parallel GC. By default, major GC will be triggered when the program is idle for a certain time. - Manually execute
performMajorGC
at relevant times to ensure that older generations get cleaned up.
- databaseOpen :: (Mode a, AsNotmuchError e, MonadError e m, MonadIO m) => FilePath -> m (Database a)
- databaseOpenReadOnly :: (AsNotmuchError e, MonadError e m, MonadIO m) => FilePath -> m (Database RO)
- databaseDestroy :: (AsNotmuchError e, MonadError e m, MonadIO m) => Database a -> m ()
- databaseVersion :: MonadIO m => Database a -> m Int
- data Database (a :: DatabaseMode)
- class Mode a
- data DatabaseMode
- type RO = DatabaseModeReadOnly
- type RW = DatabaseModeReadWrite
- data Query (a :: DatabaseMode)
- query :: MonadIO m => Database a -> SearchTerm -> m (Query a)
- queryCountMessages :: (AsNotmuchError e, MonadError e m, MonadIO m) => Query a -> m Int
- queryCountThreads :: (AsNotmuchError e, MonadError e m, MonadIO m) => Query a -> m Int
- data SearchTerm
- class HasThread a where
- data Thread (a :: DatabaseMode)
- threadToplevelMessages :: (AsNotmuchError e, MonadError e m, MonadIO m) => Thread a -> m [Message 0 a]
- threadNewestDate :: MonadIO m => Thread a -> m UTCTime
- threadSubject :: MonadIO m => Thread a -> m ByteString
- threadAuthors :: MonadIO m => Thread a -> m ThreadAuthors
- threadTotalMessages :: MonadIO m => Thread a -> m Int
- type ThreadId = ByteString
- class HasThreads a where
- data ThreadAuthors
- type Author = Text
- matchedAuthors :: Lens' ThreadAuthors [Author]
- unmatchedAuthors :: Lens' ThreadAuthors [Author]
- findMessage :: (AsNotmuchError e, MonadError e m, MonadIO m) => Database a -> MessageId -> m (Maybe (Message 0 a))
- class HasMessages a where
- data Message (n :: Nat) (a :: DatabaseMode)
- type MessageId = ByteString
- messageId :: MonadIO m => Message n a -> m MessageId
- messageDate :: MonadIO m => Message n a -> m UTCTime
- messageHeader :: MonadIO m => ByteString -> Message n a -> m (Maybe ByteString)
- messageFilename :: MonadIO m => Message n a -> m FilePath
- messageSetTags :: (MonadIO m, Foldable t) => t Tag -> Message 0 RW -> m ()
- messageAddTag :: MonadIO m => Tag -> Message n RW -> m ()
- messageRemoveTag :: MonadIO m => Tag -> Message n RW -> m ()
- withFrozenMessage :: (Message 1 RW -> IO a) -> Message 0 RW -> IO a
- class HasTags a where
- data Tag
- mkTag :: ByteString -> Maybe Tag
- getTag :: Tag -> ByteString
- tagMaxLen :: Int
- data Status
- = StatusSuccess
- | StatusOutOfMemory
- | StatusReadOnlyDatabase
- | StatusXapianException
- | StatusFileError
- | StatusFileNotEmail
- | StatusDuplicateMessageId
- | StatusNullPointer
- | StatusTagTooLong
- | StatusUnbalancedFreezeThaw
- | StatusUnbalancedAtomic
- | StatusUnsupportedOperation
- | StatusUpgradeRequired
- | StatusPathError
- | StatusIgnored
- | StatusIllegalArgument
- | StatusMalformedCryptoProtocol
- | StatusFailedCryptoContextCreation
- | StatusUnknownCryptoProtocol
- | StatusLastStatus
- class AsNotmuchError s where
- libnotmuchVersion :: Version
Opening a database
databaseOpen :: (Mode a, AsNotmuchError e, MonadError e m, MonadIO m) => FilePath -> m (Database a) Source #
Open a database. The mode is determined by usage.
Because read-only functions also work on read-write databases,
databaseOpenReadOnly
is also provided for convenience.
Use databaseDestroy
to close a database and free resources.
databaseOpenReadOnly :: (AsNotmuchError e, MonadError e m, MonadIO m) => FilePath -> m (Database RO) Source #
Convenience function for opening a database read-only
databaseDestroy :: (AsNotmuchError e, MonadError e m, MonadIO m) => Database a -> m () Source #
Close the database and free associated resources
Don't use any resources derived from this database after using this function!
databaseVersion :: MonadIO m => Database a -> m Int Source #
Database format version of the given database.
data Database (a :: DatabaseMode) Source #
A database handle. Use databaseDestroy
to close a database
(take care not to use any derived objects afterwards!)
Use query
to perform a search or findMessage
to search for a
particular message.
The Database
type carries a phantom for the database mode, which
is propgated to derived Query
, Thread
and Message
objects.
This is used to prevent write operations being performed against
a read-only database.
Database modes
This is an internal class whose instances are the promoted
DatabaseMode
constructors.
getMode, upgrade
type RO = DatabaseModeReadOnly Source #
Convenience synonym for the promoted DatabaseModeReadOnly
constructor.
type RW = DatabaseModeReadWrite Source #
Convenience synonym for the promoted DatabaseModeReadWrite
constructor.
Querying the database
data Query (a :: DatabaseMode) Source #
Query object. Cleaned up when garbate collected.
Use messages
or threads
to get the results.
The Query
type carries a phantom for the database mode, so that
write operations on messages derived from it are restricted to
read/write database sessions.
HasThreads Query Source # | Retrieve the threads matching a |
HasMessages Query Source # | Retrieve all messages matching a |
queryCountMessages :: (AsNotmuchError e, MonadError e m, MonadIO m) => Query a -> m Int Source #
Count the number of messages matching a query.
Complexity: same as the underlying Xapian search…
queryCountThreads :: (AsNotmuchError e, MonadError e m, MonadIO m) => Query a -> m Int Source #
Count the number of threads matching a query.
Θ(n) in the number of messages!
Search expressions
data SearchTerm Source #
Search expression. Use Bare
if you want to use a query
string as-is (see also notmuch-search-terms(7)
).
Use show
to stringify a SearchTerm
.
Show SearchTerm Source # | Stringify a query expression. |
Generic SearchTerm Source # | |
NFData SearchTerm Source # | |
type Rep SearchTerm Source # | |
Working with threads
data Thread (a :: DatabaseMode) Source #
Thread object. Cleaned up when garbage collected.
Use messages
to get the messages of a thread.
The Thread
type carries a phantom for the database mode, so that
write operations on messages derived from it are restricted to
read/write database sessions.
threadToplevelMessages :: (AsNotmuchError e, MonadError e m, MonadIO m) => Thread a -> m [Message 0 a] Source #
Returns only messages in a thread which are not replies to other messages in the thread.
threadNewestDate :: MonadIO m => Thread a -> m UTCTime Source #
Returns the date of the newest message in a Thread
.
threadSubject :: MonadIO m => Thread a -> m ByteString Source #
Returns the subject of the first message in the query results that belongs to this thread.
threadAuthors :: MonadIO m => Thread a -> m ThreadAuthors Source #
Return authors of a thread. These are split into:
- Authors of messages matching the query (accessible via
matchedAuthors
). - Authors of non-matching messages (accessible via
unmatchedAuthors
).
threadTotalMessages :: MonadIO m => Thread a -> m Int Source #
All messages in the database belonging to the given Thread
.
Thread ID
type ThreadId = ByteString Source #
Thread identifier generated and used by libnotmuch.
class HasThreads a where Source #
Objects with associated threads
threads :: (AsNotmuchError e, MonadError e m, MonadIO m) => a mode -> m [Thread mode] Source #
HasThreads Query Source # | Retrieve the threads matching a |
Thread authors
data ThreadAuthors Source #
Authors belonging to messages in a query result of a thread ordered by date.
matchedAuthors :: Lens' ThreadAuthors [Author] Source #
Lens to matched authors. See also threadAuthors
.
unmatchedAuthors :: Lens' ThreadAuthors [Author] Source #
Lens to unmatched authors. See also threadAuthors
.
Working with messages
findMessage :: (AsNotmuchError e, MonadError e m, MonadIO m) => Database a -> MessageId -> m (Maybe (Message 0 a)) Source #
Look for a particular message in the database.
class HasMessages a where Source #
Objects with associated messages.
messages :: (AsNotmuchError e, MonadError e m, MonadIO m) => a mode -> m [Message 0 mode] Source #
HasMessages Query Source # | Retrieve all messages matching a |
HasMessages Thread Source # | Retrieve the messages in a |
HasMessages (Message n) Source # | Retrieve the replies to a |
data Message (n :: Nat) (a :: DatabaseMode) Source #
Message object. Cleaned up when garbage collected.
The Message
type carries a phantom for the database mode, so that
write operations are restricted to read/write database sessions.
There is also a phantom type parameter for the degree of frozenness
of the message. Tag operations on a frozen message are atomic, only
becoming visible to other threads/processes after the thaw. The
freeze/thaw behaviour is available via withFrozenMessage
.
type MessageId = ByteString Source #
Message-Id
header value.
messageHeader :: MonadIO m => ByteString -> Message n a -> m (Maybe ByteString) Source #
Get the named header as a UTF-8 encoded string.
Empty string if header is missing or Nothing
on error.
May open a file descriptor that will not be closed until the message gets garbage collected.
messageSetTags :: (MonadIO m, Foldable t) => t Tag -> Message 0 RW -> m () Source #
Set tags for the message. Atomic.
messageAddTag :: MonadIO m => Tag -> Message n RW -> m () Source #
Add the tag to a message. If adding/removing multiple tags,
use messageSetTags
to set the whole tag list atomically, or use
withFrozenMessage
to avoid inconsistent states when
adding/removing tags.
messageRemoveTag :: MonadIO m => Tag -> Message n RW -> m () Source #
Remove the tag from a message. If adding/removing multiple
tags, use messageSetTags
to set the whole tag list atomically,
or use withFrozenMessage
to avoid inconsistent states when
adding/removing tags.
withFrozenMessage :: (Message 1 RW -> IO a) -> Message 0 RW -> IO a Source #
Freeze the message, run the given computation and return the result. The message is always thawed at the end. (Don't thaw the message as part of the computation!)
Have to start with Message 0 RW
due to GHC type system limitation
(type-level Nat is not inductive).
Tags
Message tag. Use mkTag
to construct a tag. Or use
-XOverloadedStrings
, but beware that the IsString
instance
is non-total.
This data type avoid copying when passing tags to libnotmuch. But copies do occur when reading tags from a database.
A previous experiment with interning showed no benefit. Tags are typically very short so the overhead erodes any advantage.
mkTag :: ByteString -> Maybe Tag Source #
O(n) Just
a tag, or Nothing
if the string is too long
Use UTF-8 encoding to include non-ASCII characters in a tag.
getTag :: Tag -> ByteString Source #
O(1)
Errors
class AsNotmuchError s where Source #
_NotmuchError :: Prism' s Status Source #
Library information
libnotmuchVersion :: Version Source #
The version of libnotmuch that hs-notmuch was built against. (The program could be running against a different version.)