1// Wrapper around the notmuch library 2 3package notmuch 4 5/* 6#cgo LDFLAGS: -lnotmuch 7 8#include <stdlib.h> 9#include <string.h> 10#include <time.h> 11#include "notmuch.h" 12*/ 13import "C" 14import "unsafe" 15 16// Status codes used for the return values of most functions 17type Status C.notmuch_status_t 18 19const ( 20 STATUS_SUCCESS Status = iota 21 STATUS_OUT_OF_MEMORY 22 STATUS_READ_ONLY_DATABASE 23 STATUS_XAPIAN_EXCEPTION 24 STATUS_FILE_ERROR 25 STATUS_FILE_NOT_EMAIL 26 STATUS_DUPLICATE_MESSAGE_ID 27 STATUS_NULL_POINTER 28 STATUS_TAG_TOO_LONG 29 STATUS_UNBALANCED_FREEZE_THAW 30 STATUS_UNBALANCED_ATOMIC 31 32 STATUS_LAST_STATUS 33) 34 35func (self Status) String() string { 36 var p *C.char 37 38 // p is read-only 39 p = C.notmuch_status_to_string(C.notmuch_status_t(self)) 40 if p != nil { 41 s := C.GoString(p) 42 return s 43 } 44 return "" 45} 46 47/* Various opaque data types. For each notmuch_<foo>_t see the various 48 * notmuch_<foo> functions below. */ 49 50type Database struct { 51 db *C.notmuch_database_t 52} 53 54type Query struct { 55 query *C.notmuch_query_t 56} 57 58type Threads struct { 59 threads *C.notmuch_threads_t 60} 61 62type Thread struct { 63 thread *C.notmuch_thread_t 64} 65 66type Messages struct { 67 messages *C.notmuch_messages_t 68} 69 70type Message struct { 71 message *C.notmuch_message_t 72} 73 74type Tags struct { 75 tags *C.notmuch_tags_t 76} 77 78type Directory struct { 79 dir *C.notmuch_directory_t 80} 81 82type Filenames struct { 83 fnames *C.notmuch_filenames_t 84} 85 86type DatabaseMode C.notmuch_database_mode_t 87 88const ( 89 DATABASE_MODE_READ_ONLY DatabaseMode = iota 90 DATABASE_MODE_READ_WRITE 91) 92 93// Create a new, empty notmuch database located at 'path' 94func NewDatabase(path string) (*Database, Status) { 95 96 var c_path *C.char = C.CString(path) 97 defer C.free(unsafe.Pointer(c_path)) 98 99 if c_path == nil { 100 return nil, STATUS_OUT_OF_MEMORY 101 } 102 103 self := &Database{db: nil} 104 st := Status(C.notmuch_database_create(c_path, &self.db)) 105 if st != STATUS_SUCCESS { 106 return nil, st 107 } 108 return self, st 109} 110 111/* Open an existing notmuch database located at 'path'. 112 * 113 * The database should have been created at some time in the past, 114 * (not necessarily by this process), by calling 115 * notmuch_database_create with 'path'. By default the database should be 116 * opened for reading only. In order to write to the database you need to 117 * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode. 118 * 119 * An existing notmuch database can be identified by the presence of a 120 * directory named ".notmuch" below 'path'. 121 * 122 * The caller should call notmuch_database_destroy when finished with 123 * this database. 124 * 125 * In case of any failure, this function returns NULL, (after printing 126 * an error message on stderr). 127 */ 128func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) { 129 130 var c_path *C.char = C.CString(path) 131 defer C.free(unsafe.Pointer(c_path)) 132 133 if c_path == nil { 134 return nil, STATUS_OUT_OF_MEMORY 135 } 136 137 self := &Database{db: nil} 138 st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db)) 139 if st != STATUS_SUCCESS { 140 return nil, st 141 } 142 return self, st 143} 144 145/* Close the given notmuch database, freeing all associated 146 * resources. See notmuch_database_open. */ 147func (self *Database) Close() Status { 148 return Status(C.notmuch_database_destroy(self.db)) 149} 150 151/* Return the database path of the given database. 152 */ 153func (self *Database) GetPath() string { 154 155 /* The return value is a string owned by notmuch so should not be 156 * modified nor freed by the caller. */ 157 var p *C.char = C.notmuch_database_get_path(self.db) 158 if p != nil { 159 s := C.GoString(p) 160 return s 161 } 162 return "" 163} 164 165/* Return the database format version of the given database. */ 166func (self *Database) GetVersion() uint { 167 return uint(C.notmuch_database_get_version(self.db)) 168} 169 170/* Does this database need to be upgraded before writing to it? 171 * 172 * If this function returns TRUE then no functions that modify the 173 * database (notmuch_database_index_file, notmuch_message_add_tag, 174 * notmuch_directory_set_mtime, etc.) will work unless the function 175 * notmuch_database_upgrade is called successfully first. */ 176func (self *Database) NeedsUpgrade() bool { 177 do_upgrade := C.notmuch_database_needs_upgrade(self.db) 178 if do_upgrade == 0 { 179 return false 180 } 181 return true 182} 183 184// TODO: notmuch_database_upgrade 185 186/* Retrieve a directory object from the database for 'path'. 187 * 188 * Here, 'path' should be a path relative to the path of 'database' 189 * (see notmuch_database_get_path), or else should be an absolute path 190 * with initial components that match the path of 'database'. 191 * 192 * Can return NULL if a Xapian exception occurs. 193 */ 194func (self *Database) GetDirectory(path string) (*Directory, Status) { 195 var c_path *C.char = C.CString(path) 196 defer C.free(unsafe.Pointer(c_path)) 197 198 if c_path == nil { 199 return nil, STATUS_OUT_OF_MEMORY 200 } 201 202 var c_dir *C.notmuch_directory_t 203 st := Status(C.notmuch_database_get_directory(self.db, c_path, &c_dir)) 204 if st != STATUS_SUCCESS || c_dir == nil { 205 return nil, st 206 } 207 return &Directory{dir: c_dir}, st 208} 209 210/* Add a new message to the given notmuch database. 211 * 212 * Here,'filename' should be a path relative to the path of 213 * 'database' (see notmuch_database_get_path), or else should be an 214 * absolute filename with initial components that match the path of 215 * 'database'. 216 * 217 * The file should be a single mail message (not a multi-message mbox) 218 * that is expected to remain at its current location, (since the 219 * notmuch database will reference the filename, and will not copy the 220 * entire contents of the file. 221 * 222 * If 'message' is not NULL, then, on successful return '*message' 223 * will be initialized to a message object that can be used for things 224 * such as adding tags to the just-added message. The user should call 225 * notmuch_message_destroy when done with the message. On any failure 226 * '*message' will be set to NULL. 227 * 228 * Return value: 229 * 230 * NOTMUCH_STATUS_SUCCESS: Message successfully added to database. 231 * 232 * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, 233 * message not added. 234 * 235 * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message 236 * ID as another message already in the database. The new 237 * filename was successfully added to the message in the database 238 * (if not already present). 239 * 240 * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the 241 * file, (such as permission denied, or file not found, 242 * etc.). Nothing added to the database. 243 * 244 * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look 245 * like an email message. Nothing added to the database. 246 * 247 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 248 * mode so no message can be added. 249 */ 250func (self *Database) AddMessage(fname string) (*Message, Status) { 251 var c_fname *C.char = C.CString(fname) 252 defer C.free(unsafe.Pointer(c_fname)) 253 254 if c_fname == nil { 255 return nil, STATUS_OUT_OF_MEMORY 256 } 257 258 var c_msg *C.notmuch_message_t = new(C.notmuch_message_t) 259 st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg)) 260 261 return &Message{message: c_msg}, st 262} 263 264/* Remove a message from the given notmuch database. 265 * 266 * Note that only this particular filename association is removed from 267 * the database. If the same message (as determined by the message ID) 268 * is still available via other filenames, then the message will 269 * persist in the database for those filenames. When the last filename 270 * is removed for a particular message, the database content for that 271 * message will be entirely removed. 272 * 273 * Return value: 274 * 275 * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the 276 * message was removed from the database. 277 * 278 * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, 279 * message not removed. 280 * 281 * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but 282 * the message persists in the database with at least one other 283 * filename. 284 * 285 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 286 * mode so no message can be removed. 287 */ 288func (self *Database) RemoveMessage(fname string) Status { 289 290 var c_fname *C.char = C.CString(fname) 291 defer C.free(unsafe.Pointer(c_fname)) 292 293 if c_fname == nil { 294 return STATUS_OUT_OF_MEMORY 295 } 296 297 st := C.notmuch_database_remove_message(self.db, c_fname) 298 return Status(st) 299} 300 301/* Find a message with the given message_id. 302 * 303 * If the database contains a message with the given message_id, then 304 * a new notmuch_message_t object is returned. The caller should call 305 * notmuch_message_destroy when done with the message. 306 * 307 * This function returns NULL in the following situations: 308 * 309 * * No message is found with the given message_id 310 * * An out-of-memory situation occurs 311 * * A Xapian exception occurs 312 */ 313func (self *Database) FindMessage(message_id string) (*Message, Status) { 314 315 var c_msg_id *C.char = C.CString(message_id) 316 defer C.free(unsafe.Pointer(c_msg_id)) 317 318 if c_msg_id == nil { 319 return nil, STATUS_OUT_OF_MEMORY 320 } 321 322 msg := &Message{message: nil} 323 st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message)) 324 if st != STATUS_SUCCESS { 325 return nil, st 326 } 327 return msg, st 328} 329 330/* Return a list of all tags found in the database. 331 * 332 * This function creates a list of all tags found in the database. The 333 * resulting list contains all tags from all messages found in the database. 334 * 335 * On error this function returns NULL. 336 */ 337func (self *Database) GetAllTags() *Tags { 338 tags := C.notmuch_database_get_all_tags(self.db) 339 if tags == nil { 340 return nil 341 } 342 return &Tags{tags: tags} 343} 344 345/* Create a new query for 'database'. 346 * 347 * Here, 'database' should be an open database, (see 348 * notmuch_database_open and notmuch_database_create). 349 * 350 * For the query string, we'll document the syntax here more 351 * completely in the future, but it's likely to be a specialized 352 * version of the general Xapian query syntax: 353 * 354 * https://xapian.org/docs/queryparser.html 355 * 356 * As a special case, passing either a length-zero string, (that is ""), 357 * or a string consisting of a single asterisk (that is "*"), will 358 * result in a query that returns all messages in the database. 359 * 360 * See notmuch_query_set_sort for controlling the order of results. 361 * See notmuch_query_search_messages and notmuch_query_search_threads 362 * to actually execute the query. 363 * 364 * User should call notmuch_query_destroy when finished with this 365 * query. 366 * 367 * Will return NULL if insufficient memory is available. 368 */ 369func (self *Database) CreateQuery(query string) *Query { 370 371 var c_query *C.char = C.CString(query) 372 defer C.free(unsafe.Pointer(c_query)) 373 374 if c_query == nil { 375 return nil 376 } 377 378 q := C.notmuch_query_create(self.db, c_query) 379 if q == nil { 380 return nil 381 } 382 return &Query{query: q} 383} 384 385/* Sort values for notmuch_query_set_sort */ 386type Sort C.notmuch_sort_t 387 388const ( 389 SORT_OLDEST_FIRST Sort = 0 390 SORT_NEWEST_FIRST 391 SORT_MESSAGE_ID 392 SORT_UNSORTED 393) 394 395/* Return the query_string of this query. See notmuch_query_create. */ 396func (self *Query) String() string { 397 // FIXME: do we own 'q' or not ? 398 q := C.notmuch_query_get_query_string(self.query) 399 //defer C.free(unsafe.Pointer(q)) 400 401 if q != nil { 402 s := C.GoString(q) 403 return s 404 } 405 406 return "" 407} 408 409/* Specify the sorting desired for this query. */ 410func (self *Query) SetSort(sort Sort) { 411 C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort)) 412} 413 414/* Return the sort specified for this query. See notmuch_query_set_sort. */ 415func (self *Query) GetSort() Sort { 416 return Sort(C.notmuch_query_get_sort(self.query)) 417} 418 419/* Execute a query for threads, returning a notmuch_threads_t object 420 * which can be used to iterate over the results. The returned threads 421 * object is owned by the query and as such, will only be valid until 422 * notmuch_query_destroy. 423 * 424 * Typical usage might be: 425 * 426 * notmuch_query_t *query; 427 * notmuch_threads_t *threads; 428 * notmuch_thread_t *thread; 429 * 430 * query = notmuch_query_create (database, query_string); 431 * 432 * for (threads = notmuch_query_search_threads (query); 433 * notmuch_threads_valid (threads); 434 * notmuch_threads_move_to_next (threads)) 435 * { 436 * thread = notmuch_threads_get (threads); 437 * .... 438 * notmuch_thread_destroy (thread); 439 * } 440 * 441 * notmuch_query_destroy (query); 442 * 443 * Note: If you are finished with a thread before its containing 444 * query, you can call notmuch_thread_destroy to clean up some memory 445 * sooner (as in the above example). Otherwise, if your thread objects 446 * are long-lived, then you don't need to call notmuch_thread_destroy 447 * and all the memory will still be reclaimed when the query is 448 * destroyed. 449 * 450 * Note that there's no explicit destructor needed for the 451 * notmuch_threads_t object. (For consistency, we do provide a 452 * notmuch_threads_destroy function, but there's no good reason 453 * to call it if the query is about to be destroyed). 454 * 455 * If a Xapian exception occurs this function will return NULL. 456 */ 457func (self *Query) SearchThreads() *Threads { 458 threads := C.notmuch_query_search_threads(self.query) 459 if threads == nil { 460 return nil 461 } 462 return &Threads{threads: threads} 463} 464 465/* Execute a query for messages, returning a notmuch_messages_t object 466 * which can be used to iterate over the results. The returned 467 * messages object is owned by the query and as such, will only be 468 * valid until notmuch_query_destroy. 469 * 470 * Typical usage might be: 471 * 472 * notmuch_query_t *query; 473 * notmuch_messages_t *messages; 474 * notmuch_message_t *message; 475 * 476 * query = notmuch_query_create (database, query_string); 477 * 478 * for (messages = notmuch_query_search_messages (query); 479 * notmuch_messages_valid (messages); 480 * notmuch_messages_move_to_next (messages)) 481 * { 482 * message = notmuch_messages_get (messages); 483 * .... 484 * notmuch_message_destroy (message); 485 * } 486 * 487 * notmuch_query_destroy (query); 488 * 489 * Note: If you are finished with a message before its containing 490 * query, you can call notmuch_message_destroy to clean up some memory 491 * sooner (as in the above example). Otherwise, if your message 492 * objects are long-lived, then you don't need to call 493 * notmuch_message_destroy and all the memory will still be reclaimed 494 * when the query is destroyed. 495 * 496 * Note that there's no explicit destructor needed for the 497 * notmuch_messages_t object. (For consistency, we do provide a 498 * notmuch_messages_destroy function, but there's no good 499 * reason to call it if the query is about to be destroyed). 500 * 501 * If a Xapian exception occurs this function will return NULL. 502 */ 503func (self *Query) SearchMessages() *Messages { 504 msgs := C.notmuch_query_search_messages(self.query) 505 if msgs == nil { 506 return nil 507 } 508 return &Messages{messages: msgs} 509} 510 511/* Destroy a notmuch_query_t along with any associated resources. 512 * 513 * This will in turn destroy any notmuch_threads_t and 514 * notmuch_messages_t objects generated by this query, (and in 515 * turn any notmuch_thread_t and notmuch_message_t objects generated 516 * from those results, etc.), if such objects haven't already been 517 * destroyed. 518 */ 519func (self *Query) Destroy() { 520 if self.query != nil { 521 C.notmuch_query_destroy(self.query) 522 } 523} 524 525/* Return an estimate of the number of messages matching a search 526 * 527 * This function performs a search and returns Xapian's best 528 * guess as to number of matching messages. 529 * 530 * If a Xapian exception occurs, this function may return 0 (after 531 * printing a message). 532 */ 533func (self *Query) CountMessages() uint { 534 return uint(C.notmuch_query_count_messages(self.query)) 535} 536 537/* Is the given 'threads' iterator pointing at a valid thread. 538 * 539 * When this function returns TRUE, notmuch_threads_get will return a 540 * valid object. Whereas when this function returns FALSE, 541 * notmuch_threads_get will return NULL. 542 * 543 * See the documentation of notmuch_query_search_threads for example 544 * code showing how to iterate over a notmuch_threads_t object. 545 */ 546func (self *Threads) Valid() bool { 547 if self.threads == nil { 548 return false 549 } 550 valid := C.notmuch_threads_valid(self.threads) 551 if valid == 0 { 552 return false 553 } 554 return true 555} 556 557/* Get the current thread from 'threads' as a notmuch_thread_t. 558 * 559 * Note: The returned thread belongs to 'threads' and has a lifetime 560 * identical to it (and the query to which it belongs). 561 * 562 * See the documentation of notmuch_query_search_threads for example 563 * code showing how to iterate over a notmuch_threads_t object. 564 * 565 * If an out-of-memory situation occurs, this function will return 566 * NULL. 567 */ 568func (self *Threads) Get() *Thread { 569 if self.threads == nil { 570 return nil 571 } 572 thread := C.notmuch_threads_get(self.threads) 573 if thread == nil { 574 return nil 575 } 576 return &Thread{thread} 577} 578 579/* Move the 'threads' iterator to the next thread. 580 * 581 * If 'threads' is already pointing at the last thread then the 582 * iterator will be moved to a point just beyond that last thread, 583 * (where notmuch_threads_valid will return FALSE and 584 * notmuch_threads_get will return NULL). 585 * 586 * See the documentation of notmuch_query_search_threads for example 587 * code showing how to iterate over a notmuch_threads_t object. 588 */ 589func (self *Threads) MoveToNext() { 590 if self.threads == nil { 591 return 592 } 593 C.notmuch_threads_move_to_next(self.threads) 594} 595 596/* Destroy a notmuch_threads_t object. 597 * 598 * It's not strictly necessary to call this function. All memory from 599 * the notmuch_threads_t object will be reclaimed when the 600 * containing query object is destroyed. 601 */ 602func (self *Threads) Destroy() { 603 if self.threads != nil { 604 C.notmuch_threads_destroy(self.threads) 605 } 606} 607 608/** 609 * Get the thread ID of 'thread'. 610 * 611 * The returned string belongs to 'thread' and as such, should not be 612 * modified by the caller and will only be valid for as long as the 613 * thread is valid, (which is until notmuch_thread_destroy or until 614 * the query from which it derived is destroyed). 615 */ 616func (self *Thread) GetThreadId() string { 617 if self.thread == nil { 618 return "" 619 } 620 id := C.notmuch_thread_get_thread_id(self.thread) 621 if id == nil { 622 return "" 623 } 624 return C.GoString(id) 625} 626 627/** 628 * Get the total number of messages in 'thread'. 629 * 630 * This count consists of all messages in the database belonging to 631 * this thread. Contrast with notmuch_thread_get_matched_messages() . 632 */ 633func (self *Thread) GetTotalMessages() int { 634 if self.thread == nil { 635 return 0 636 } 637 return int(C.notmuch_thread_get_total_messages(self.thread)) 638} 639 640/** 641 * Get a notmuch_messages_t iterator for the top-level messages in 642 * 'thread' in oldest-first order. 643 * 644 * This iterator will not necessarily iterate over all of the messages 645 * in the thread. It will only iterate over the messages in the thread 646 * which are not replies to other messages in the thread. 647 * 648 * The returned list will be destroyed when the thread is destroyed. 649 */ 650func (self *Thread) GetToplevelMessages() (*Messages, Status) { 651 if self.thread == nil { 652 return nil, STATUS_NULL_POINTER 653 } 654 655 msgs := C.notmuch_thread_get_toplevel_messages(self.thread) 656 if msgs == nil { 657 return nil, STATUS_NULL_POINTER 658 } 659 return &Messages{msgs}, STATUS_SUCCESS 660} 661 662/** 663 * Get a notmuch_thread_t iterator for all messages in 'thread' in 664 * oldest-first order. 665 * 666 * The returned list will be destroyed when the thread is destroyed. 667 */ 668func (self *Thread) GetMessages() (*Messages, Status) { 669 if self.thread == nil { 670 return nil, STATUS_NULL_POINTER 671 } 672 673 msgs := C.notmuch_thread_get_messages(self.thread) 674 if msgs == nil { 675 return nil, STATUS_NULL_POINTER 676 } 677 return &Messages{msgs}, STATUS_SUCCESS 678} 679 680/** 681 * Get the number of messages in 'thread' that matched the search. 682 * 683 * This count includes only the messages in this thread that were 684 * matched by the search from which the thread was created and were 685 * not excluded by any exclude tags passed in with the query (see 686 * notmuch_query_add_tag_exclude). Contrast with 687 * notmuch_thread_get_total_messages() . 688 */ 689func (self *Thread) GetMatchedMessages() int { 690 if self.thread == nil { 691 return 0 692 } 693 return int(C.notmuch_thread_get_matched_messages(self.thread)) 694} 695 696/** 697 * Get the authors of 'thread' as a UTF-8 string. 698 * 699 * The returned string is a comma-separated list of the names of the 700 * authors of mail messages in the query results that belong to this 701 * thread. 702 * 703 * The string contains authors of messages matching the query first, then 704 * non-matched authors (with the two groups separated by '|'). Within 705 * each group, authors are ordered by date. 706 * 707 * The returned string belongs to 'thread' and as such, should not be 708 * modified by the caller and will only be valid for as long as the 709 * thread is valid, (which is until notmuch_thread_destroy or until 710 * the query from which it derived is destroyed). 711 */ 712func (self *Thread) GetAuthors() string { 713 if self.thread == nil { 714 return "" 715 } 716 str := C.notmuch_thread_get_authors(self.thread) 717 if str == nil { 718 return "" 719 } 720 return C.GoString(str) 721} 722 723/** 724 * Get the subject of 'thread' as a UTF-8 string. 725 * 726 * The subject is taken from the first message (according to the query 727 * order---see notmuch_query_set_sort) in the query results that 728 * belongs to this thread. 729 * 730 * The returned string belongs to 'thread' and as such, should not be 731 * modified by the caller and will only be valid for as long as the 732 * thread is valid, (which is until notmuch_thread_destroy or until 733 * the query from which it derived is destroyed). 734 */ 735func (self *Thread) GetSubject() string { 736 if self.thread == nil { 737 return "" 738 } 739 str := C.notmuch_thread_get_subject(self.thread) 740 if str == nil { 741 return "" 742 } 743 return C.GoString(str) 744} 745 746/** 747 * Get the date of the oldest message in 'thread' as a time_t value. 748 */ 749func (self *Thread) GetOldestDate() int64 { 750 if self.thread == nil { 751 return 0 752 } 753 date := C.notmuch_thread_get_oldest_date(self.thread) 754 755 return int64(date) 756} 757 758/** 759 * Get the date of the newest message in 'thread' as a time_t value. 760 */ 761func (self *Thread) GetNewestDate() int64 { 762 if self.thread == nil { 763 return 0 764 } 765 date := C.notmuch_thread_get_newest_date(self.thread) 766 767 return int64(date) 768} 769 770/** 771 * Get the tags for 'thread', returning a notmuch_tags_t object which 772 * can be used to iterate over all tags. 773 * 774 * Note: In the Notmuch database, tags are stored on individual 775 * messages, not on threads. So the tags returned here will be all 776 * tags of the messages which matched the search and which belong to 777 * this thread. 778 * 779 * The tags object is owned by the thread and as such, will only be 780 * valid for as long as the thread is valid, (for example, until 781 * notmuch_thread_destroy or until the query from which it derived is 782 * destroyed). 783 * 784 * Typical usage might be: 785 * 786 * notmuch_thread_t *thread; 787 * notmuch_tags_t *tags; 788 * const char *tag; 789 * 790 * thread = notmuch_threads_get (threads); 791 * 792 * for (tags = notmuch_thread_get_tags (thread); 793 * notmuch_tags_valid (tags); 794 * notmuch_tags_move_to_next (tags)) 795 * { 796 * tag = notmuch_tags_get (tags); 797 * .... 798 * } 799 * 800 * notmuch_thread_destroy (thread); 801 * 802 * Note that there's no explicit destructor needed for the 803 * notmuch_tags_t object. (For consistency, we do provide a 804 * notmuch_tags_destroy function, but there's no good reason to call 805 * it if the message is about to be destroyed). 806 */ 807func (self *Thread) GetTags() *Tags { 808 if self.thread == nil { 809 return nil 810 } 811 812 tags := C.notmuch_thread_get_tags(self.thread) 813 if tags == nil { 814 return nil 815 } 816 817 return &Tags{tags} 818} 819 820/** 821 * Destroy a notmuch_thread_t object. 822 */ 823func (self *Thread) Destroy() { 824 if self.thread != nil { 825 C.notmuch_thread_destroy(self.thread) 826 } 827} 828 829/* Is the given 'messages' iterator pointing at a valid message. 830 * 831 * When this function returns TRUE, notmuch_messages_get will return a 832 * valid object. Whereas when this function returns FALSE, 833 * notmuch_messages_get will return NULL. 834 * 835 * See the documentation of notmuch_query_search_messages for example 836 * code showing how to iterate over a notmuch_messages_t object. 837 */ 838func (self *Messages) Valid() bool { 839 if self.messages == nil { 840 return false 841 } 842 valid := C.notmuch_messages_valid(self.messages) 843 if valid == 0 { 844 return false 845 } 846 return true 847} 848 849/* Get the current message from 'messages' as a notmuch_message_t. 850 * 851 * Note: The returned message belongs to 'messages' and has a lifetime 852 * identical to it (and the query to which it belongs). 853 * 854 * See the documentation of notmuch_query_search_messages for example 855 * code showing how to iterate over a notmuch_messages_t object. 856 * 857 * If an out-of-memory situation occurs, this function will return 858 * NULL. 859 */ 860func (self *Messages) Get() *Message { 861 if self.messages == nil { 862 return nil 863 } 864 msg := C.notmuch_messages_get(self.messages) 865 if msg == nil { 866 return nil 867 } 868 return &Message{message: msg} 869} 870 871/* Move the 'messages' iterator to the next message. 872 * 873 * If 'messages' is already pointing at the last message then the 874 * iterator will be moved to a point just beyond that last message, 875 * (where notmuch_messages_valid will return FALSE and 876 * notmuch_messages_get will return NULL). 877 * 878 * See the documentation of notmuch_query_search_messages for example 879 * code showing how to iterate over a notmuch_messages_t object. 880 */ 881func (self *Messages) MoveToNext() { 882 if self.messages == nil { 883 return 884 } 885 C.notmuch_messages_move_to_next(self.messages) 886} 887 888/* Destroy a notmuch_messages_t object. 889 * 890 * It's not strictly necessary to call this function. All memory from 891 * the notmuch_messages_t object will be reclaimed when the containing 892 * query object is destroyed. 893 */ 894func (self *Messages) Destroy() { 895 if self.messages != nil { 896 C.notmuch_messages_destroy(self.messages) 897 } 898} 899 900/* Return a list of tags from all messages. 901 * 902 * The resulting list is guaranteed not to contain duplicated tags. 903 * 904 * WARNING: You can no longer iterate over messages after calling this 905 * function, because the iterator will point at the end of the list. 906 * We do not have a function to reset the iterator yet and the only 907 * way how you can iterate over the list again is to recreate the 908 * message list. 909 * 910 * The function returns NULL on error. 911 */ 912func (self *Messages) CollectTags() *Tags { 913 if self.messages == nil { 914 return nil 915 } 916 tags := C.notmuch_messages_collect_tags(self.messages) 917 if tags == nil { 918 return nil 919 } 920 return &Tags{tags: tags} 921} 922 923/* Get the message ID of 'message'. 924 * 925 * The returned string belongs to 'message' and as such, should not be 926 * modified by the caller and will only be valid for as long as the 927 * message is valid, (which is until the query from which it derived 928 * is destroyed). 929 * 930 * This function will not return NULL since Notmuch ensures that every 931 * message has a unique message ID, (Notmuch will generate an ID for a 932 * message if the original file does not contain one). 933 */ 934func (self *Message) GetMessageId() string { 935 936 if self.message == nil { 937 return "" 938 } 939 id := C.notmuch_message_get_message_id(self.message) 940 // we don't own id 941 // defer C.free(unsafe.Pointer(id)) 942 if id == nil { 943 return "" 944 } 945 return C.GoString(id) 946} 947 948/* Get the thread ID of 'message'. 949 * 950 * The returned string belongs to 'message' and as such, should not be 951 * modified by the caller and will only be valid for as long as the 952 * message is valid, (for example, until the user calls 953 * notmuch_message_destroy on 'message' or until a query from which it 954 * derived is destroyed). 955 * 956 * This function will not return NULL since Notmuch ensures that every 957 * message belongs to a single thread. 958 */ 959func (self *Message) GetThreadId() string { 960 961 if self.message == nil { 962 return "" 963 } 964 id := C.notmuch_message_get_thread_id(self.message) 965 // we don't own id 966 // defer C.free(unsafe.Pointer(id)) 967 968 if id == nil { 969 return "" 970 } 971 972 return C.GoString(id) 973} 974 975/* Get a notmuch_messages_t iterator for all of the replies to 976 * 'message'. 977 * 978 * Note: This call only makes sense if 'message' was ultimately 979 * obtained from a notmuch_thread_t object, (such as by coming 980 * directly from the result of calling notmuch_thread_get_ 981 * toplevel_messages or by any number of subsequent 982 * calls to notmuch_message_get_replies). 983 * 984 * If 'message' was obtained through some non-thread means, (such as 985 * by a call to notmuch_query_search_messages), then this function 986 * will return NULL. 987 * 988 * If there are no replies to 'message', this function will return 989 * NULL. (Note that notmuch_messages_valid will accept that NULL 990 * value as legitimate, and simply return FALSE for it.) 991 */ 992func (self *Message) GetReplies() *Messages { 993 if self.message == nil { 994 return nil 995 } 996 msgs := C.notmuch_message_get_replies(self.message) 997 if msgs == nil { 998 return nil 999 } 1000 return &Messages{messages: msgs} 1001} 1002 1003/* Get a filename for the email corresponding to 'message'. 1004 * 1005 * The returned filename is an absolute filename, (the initial 1006 * component will match notmuch_database_get_path() ). 1007 * 1008 * The returned string belongs to the message so should not be 1009 * modified or freed by the caller (nor should it be referenced after 1010 * the message is destroyed). 1011 * 1012 * Note: If this message corresponds to multiple files in the mail 1013 * store, (that is, multiple files contain identical message IDs), 1014 * this function will arbitrarily return a single one of those 1015 * filenames. 1016 */ 1017func (self *Message) GetFileName() string { 1018 if self.message == nil { 1019 return "" 1020 } 1021 fname := C.notmuch_message_get_filename(self.message) 1022 // we don't own fname 1023 // defer C.free(unsafe.Pointer(fname)) 1024 1025 if fname == nil { 1026 return "" 1027 } 1028 1029 return C.GoString(fname) 1030} 1031 1032type Flag C.notmuch_message_flag_t 1033 1034const ( 1035 MESSAGE_FLAG_MATCH Flag = 0 1036) 1037 1038/* Get a value of a flag for the email corresponding to 'message'. */ 1039func (self *Message) GetFlag(flag Flag) bool { 1040 if self.message == nil { 1041 return false 1042 } 1043 v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag)) 1044 if v == 0 { 1045 return false 1046 } 1047 return true 1048} 1049 1050/* Set a value of a flag for the email corresponding to 'message'. */ 1051func (self *Message) SetFlag(flag Flag, value bool) { 1052 if self.message == nil { 1053 return 1054 } 1055 var v C.notmuch_bool_t = 0 1056 if value { 1057 v = 1 1058 } 1059 C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v) 1060} 1061 1062/* Get the timestamp (seconds since the epoch) of 'message'. 1063 * 1064 * Return status: 1065 * 1066 * NOTMUCH_STATUS_SUCCESS: Timestamp successfully retrieved 1067 * 1068 * NOTMUCH_STATUS_NULL_POINTER: The 'message' argument is NULL 1069 * 1070 */ 1071func (self *Message) GetDate() (int64, Status) { 1072 if self.message == nil { 1073 return -1, STATUS_NULL_POINTER 1074 } 1075 timestamp := C.notmuch_message_get_date(self.message) 1076 return int64(timestamp), STATUS_SUCCESS 1077} 1078 1079/* Get the value of the specified header from 'message'. 1080 * 1081 * The value will be read from the actual message file, not from the 1082 * notmuch database. The header name is case insensitive. 1083 * 1084 * The returned string belongs to the message so should not be 1085 * modified or freed by the caller (nor should it be referenced after 1086 * the message is destroyed). 1087 * 1088 * Returns an empty string ("") if the message does not contain a 1089 * header line matching 'header'. Returns NULL if any error occurs. 1090 */ 1091func (self *Message) GetHeader(header string) string { 1092 if self.message == nil { 1093 return "" 1094 } 1095 1096 var c_header *C.char = C.CString(header) 1097 defer C.free(unsafe.Pointer(c_header)) 1098 1099 /* we don't own value */ 1100 value := C.notmuch_message_get_header(self.message, c_header) 1101 if value == nil { 1102 return "" 1103 } 1104 1105 return C.GoString(value) 1106} 1107 1108/* Get the tags for 'message', returning a notmuch_tags_t object which 1109 * can be used to iterate over all tags. 1110 * 1111 * The tags object is owned by the message and as such, will only be 1112 * valid for as long as the message is valid, (which is until the 1113 * query from which it derived is destroyed). 1114 * 1115 * Typical usage might be: 1116 * 1117 * notmuch_message_t *message; 1118 * notmuch_tags_t *tags; 1119 * const char *tag; 1120 * 1121 * message = notmuch_database_find_message (database, message_id); 1122 * 1123 * for (tags = notmuch_message_get_tags (message); 1124 * notmuch_tags_valid (tags); 1125 * notmuch_result_move_to_next (tags)) 1126 * { 1127 * tag = notmuch_tags_get (tags); 1128 * .... 1129 * } 1130 * 1131 * notmuch_message_destroy (message); 1132 * 1133 * Note that there's no explicit destructor needed for the 1134 * notmuch_tags_t object. (For consistency, we do provide a 1135 * notmuch_tags_destroy function, but there's no good reason to call 1136 * it if the message is about to be destroyed). 1137 */ 1138func (self *Message) GetTags() *Tags { 1139 if self.message == nil { 1140 return nil 1141 } 1142 tags := C.notmuch_message_get_tags(self.message) 1143 if tags == nil { 1144 return nil 1145 } 1146 return &Tags{tags: tags} 1147} 1148 1149/* The longest possible tag value. */ 1150const TAG_MAX = 200 1151 1152/* Add a tag to the given message. 1153 * 1154 * Return value: 1155 * 1156 * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message 1157 * 1158 * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL 1159 * 1160 * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long 1161 * (exceeds NOTMUCH_TAG_MAX) 1162 * 1163 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 1164 * mode so message cannot be modified. 1165 */ 1166func (self *Message) AddTag(tag string) Status { 1167 if self.message == nil { 1168 return STATUS_NULL_POINTER 1169 } 1170 c_tag := C.CString(tag) 1171 defer C.free(unsafe.Pointer(c_tag)) 1172 1173 return Status(C.notmuch_message_add_tag(self.message, c_tag)) 1174} 1175 1176/* Remove a tag from the given message. 1177 * 1178 * Return value: 1179 * 1180 * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message 1181 * 1182 * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL 1183 * 1184 * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long 1185 * (exceeds NOTMUCH_TAG_MAX) 1186 * 1187 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 1188 * mode so message cannot be modified. 1189 */ 1190func (self *Message) RemoveTag(tag string) Status { 1191 if self.message == nil { 1192 return STATUS_NULL_POINTER 1193 } 1194 c_tag := C.CString(tag) 1195 defer C.free(unsafe.Pointer(c_tag)) 1196 1197 return Status(C.notmuch_message_remove_tag(self.message, c_tag)) 1198} 1199 1200/* Remove all tags from the given message. 1201 * 1202 * See notmuch_message_freeze for an example showing how to safely 1203 * replace tag values. 1204 * 1205 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 1206 * mode so message cannot be modified. 1207 */ 1208func (self *Message) RemoveAllTags() Status { 1209 if self.message == nil { 1210 return STATUS_NULL_POINTER 1211 } 1212 return Status(C.notmuch_message_remove_all_tags(self.message)) 1213} 1214 1215/* Freeze the current state of 'message' within the database. 1216 * 1217 * This means that changes to the message state, (via 1218 * notmuch_message_add_tag, notmuch_message_remove_tag, and 1219 * notmuch_message_remove_all_tags), will not be committed to the 1220 * database until the message is thawed with notmuch_message_thaw. 1221 * 1222 * Multiple calls to freeze/thaw are valid and these calls will 1223 * "stack". That is there must be as many calls to thaw as to freeze 1224 * before a message is actually thawed. 1225 * 1226 * The ability to do freeze/thaw allows for safe transactions to 1227 * change tag values. For example, explicitly setting a message to 1228 * have a given set of tags might look like this: 1229 * 1230 * notmuch_message_freeze (message); 1231 * 1232 * notmuch_message_remove_all_tags (message); 1233 * 1234 * for (i = 0; i < NUM_TAGS; i++) 1235 * notmuch_message_add_tag (message, tags[i]); 1236 * 1237 * notmuch_message_thaw (message); 1238 * 1239 * With freeze/thaw used like this, the message in the database is 1240 * guaranteed to have either the full set of original tag values, or 1241 * the full set of new tag values, but nothing in between. 1242 * 1243 * Imagine the example above without freeze/thaw and the operation 1244 * somehow getting interrupted. This could result in the message being 1245 * left with no tags if the interruption happened after 1246 * notmuch_message_remove_all_tags but before notmuch_message_add_tag. 1247 * 1248 * Return value: 1249 * 1250 * NOTMUCH_STATUS_SUCCESS: Message successfully frozen. 1251 * 1252 * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only 1253 * mode so message cannot be modified. 1254 */ 1255func (self *Message) Freeze() Status { 1256 if self.message == nil { 1257 return STATUS_NULL_POINTER 1258 } 1259 return Status(C.notmuch_message_freeze(self.message)) 1260} 1261 1262/* Thaw the current 'message', synchronizing any changes that may have 1263 * occurred while 'message' was frozen into the notmuch database. 1264 * 1265 * See notmuch_message_freeze for an example of how to use this 1266 * function to safely provide tag changes. 1267 * 1268 * Multiple calls to freeze/thaw are valid and these calls with 1269 * "stack". That is there must be as many calls to thaw as to freeze 1270 * before a message is actually thawed. 1271 * 1272 * Return value: 1273 * 1274 * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least 1275 * its frozen count has successfully been reduced by 1). 1276 * 1277 * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw 1278 * an unfrozen message. That is, there have been an unbalanced 1279 * number of calls to notmuch_message_freeze and 1280 * notmuch_message_thaw. 1281 */ 1282func (self *Message) Thaw() Status { 1283 if self.message == nil { 1284 return STATUS_NULL_POINTER 1285 } 1286 1287 return Status(C.notmuch_message_thaw(self.message)) 1288} 1289 1290/* Destroy a notmuch_message_t object. 1291 * 1292 * It can be useful to call this function in the case of a single 1293 * query object with many messages in the result, (such as iterating 1294 * over the entire database). Otherwise, it's fine to never call this 1295 * function and there will still be no memory leaks. (The memory from 1296 * the messages get reclaimed when the containing query is destroyed.) 1297 */ 1298func (self *Message) Destroy() { 1299 if self.message == nil { 1300 return 1301 } 1302 C.notmuch_message_destroy(self.message) 1303} 1304 1305/* Is the given 'tags' iterator pointing at a valid tag. 1306 * 1307 * When this function returns TRUE, notmuch_tags_get will return a 1308 * valid string. Whereas when this function returns FALSE, 1309 * notmuch_tags_get will return NULL. 1310 * 1311 * See the documentation of notmuch_message_get_tags for example code 1312 * showing how to iterate over a notmuch_tags_t object. 1313 */ 1314func (self *Tags) Valid() bool { 1315 if self.tags == nil { 1316 return false 1317 } 1318 v := C.notmuch_tags_valid(self.tags) 1319 if v == 0 { 1320 return false 1321 } 1322 return true 1323} 1324 1325/* Get the current tag from 'tags' as a string. 1326 * 1327 * Note: The returned string belongs to 'tags' and has a lifetime 1328 * identical to it (and the query to which it ultimately belongs). 1329 * 1330 * See the documentation of notmuch_message_get_tags for example code 1331 * showing how to iterate over a notmuch_tags_t object. 1332 */ 1333func (self *Tags) Get() string { 1334 if self.tags == nil { 1335 return "" 1336 } 1337 s := C.notmuch_tags_get(self.tags) 1338 // we don't own 's' 1339 1340 return C.GoString(s) 1341} 1342func (self *Tags) String() string { 1343 return self.Get() 1344} 1345 1346/* Move the 'tags' iterator to the next tag. 1347 * 1348 * If 'tags' is already pointing at the last tag then the iterator 1349 * will be moved to a point just beyond that last tag, (where 1350 * notmuch_tags_valid will return FALSE and notmuch_tags_get will 1351 * return NULL). 1352 * 1353 * See the documentation of notmuch_message_get_tags for example code 1354 * showing how to iterate over a notmuch_tags_t object. 1355 */ 1356func (self *Tags) MoveToNext() { 1357 if self.tags == nil { 1358 return 1359 } 1360 C.notmuch_tags_move_to_next(self.tags) 1361} 1362 1363/* Destroy a notmuch_tags_t object. 1364 * 1365 * It's not strictly necessary to call this function. All memory from 1366 * the notmuch_tags_t object will be reclaimed when the containing 1367 * message or query objects are destroyed. 1368 */ 1369func (self *Tags) Destroy() { 1370 if self.tags == nil { 1371 return 1372 } 1373 C.notmuch_tags_destroy(self.tags) 1374} 1375 1376// TODO: wrap notmuch_directory_<fct> 1377 1378/* Destroy a notmuch_directory_t object. */ 1379func (self *Directory) Destroy() { 1380 if self.dir == nil { 1381 return 1382 } 1383 C.notmuch_directory_destroy(self.dir) 1384} 1385 1386// TODO: wrap notmuch_filenames_<fct> 1387 1388/* Destroy a notmuch_filenames_t object. 1389 * 1390 * It's not strictly necessary to call this function. All memory from 1391 * the notmuch_filenames_t object will be reclaimed when the 1392 * containing directory object is destroyed. 1393 * 1394 * It is acceptable to pass NULL for 'filenames', in which case this 1395 * function will do nothing. 1396 */ 1397func (self *Filenames) Destroy() { 1398 if self.fnames == nil { 1399 return 1400 } 1401 C.notmuch_filenames_destroy(self.fnames) 1402} 1403 1404/* EOF */ 1405