remove message_number_capability and require uid capatibility.
[platform/upstream/evolution-data-server.git] / camel / providers / nntp / camel-nntp-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-nntp-folder.c : Abstract class for an email folder */
3
4 /* 
5  * Author : Chris Toshok <toshok@helixcode.com> 
6  *
7  * Copyright (C) 2000 Helix Code .
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of the GNU General Public License as 
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22  * USA
23  */
24
25
26 #include <config.h> 
27
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <dirent.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <fcntl.h>
36
37 #include "camel-folder-summary.h"
38 #include "camel-nntp-store.h"
39 #include "camel-nntp-folder.h"
40 #include "camel-nntp-store.h"
41 #include "camel-nntp-utils.h"
42
43 #include "string-utils.h"
44 #include "camel-stream-mem.h"
45 #include "camel-stream-buffer.h"
46 #include "camel-data-wrapper.h"
47 #include "camel-mime-message.h"
48 #include "camel-folder-summary.h"
49
50 #include "camel-exception.h"
51
52 static CamelFolderClass *parent_class=NULL;
53
54 /* Returns the class for a CamelNNTPFolder */
55 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (GTK_OBJECT(so)->klass)
56 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (GTK_OBJECT(so)->klass)
57 #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (GTK_OBJECT(so)->klass)
58
59
60 static void _init (CamelFolder *folder, CamelStore *parent_store,
61                    CamelFolder *parent_folder, const gchar *name,
62                    gchar separator, CamelException *ex);
63
64 static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex);
65 static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex);
66 static gboolean _exists (CamelFolder *folder, CamelException *ex);
67 static gboolean _create(CamelFolder *folder, CamelException *ex);
68 static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex);
69 static gboolean _delete_messages (CamelFolder *folder, CamelException *ex);
70 static GList *_list_subfolders (CamelFolder *folder, CamelException *ex);
71 static CamelMimeMessage *_get_message_by_number (CamelFolder *folder, gint number, CamelException *ex);
72 static gint _get_message_count (CamelFolder *folder, CamelException *ex);
73 static void _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex);
74 static GPtrArray *_get_uid_array  (CamelFolder *folder, CamelException *ex);
75 static CamelMimeMessage *_get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex);
76 #if 0
77 static void _expunge (CamelFolder *folder, CamelException *ex);
78 static void _copy_message_to (CamelFolder *folder, CamelMimeMessage *message, CamelFolder *dest_folder, CamelException *ex);
79 #endif
80 static const gchar *_get_message_uid (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex);
81
82 static void _finalize (GtkObject *object);
83
84 static void
85 camel_nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
86 {
87         CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);
88         GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (camel_folder_class);
89
90         parent_class = gtk_type_class (camel_folder_get_type ());
91                 
92         /* virtual method definition */
93
94         /* virtual method overload */
95         camel_folder_class->init = _init;
96         camel_folder_class->open = _open;
97         camel_folder_class->close = _close;
98         camel_folder_class->exists = _exists;
99         camel_folder_class->create = _create;
100         camel_folder_class->delete = _delete;
101         camel_folder_class->delete_messages = _delete_messages;
102         camel_folder_class->list_subfolders = _list_subfolders;
103         camel_folder_class->get_message_count = _get_message_count;
104         camel_folder_class->get_uid_array = _get_uid_array;
105         camel_folder_class->get_message_by_uid = _get_message_by_uid;
106 #if 0 
107         camel_folder_class->append_message = _append_message;
108         camel_folder_class->expunge = _expunge;
109         camel_folder_class->copy_message_to = _copy_message_to;
110         camel_folder_class->get_message_uid = _get_message_uid;
111 #endif
112
113         gtk_object_class->finalize = _finalize;
114         
115 }
116
117 static void           
118 _finalize (GtkObject *object)
119 {
120         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object);
121
122         g_free (nntp_folder->summary_file_path);
123
124         GTK_OBJECT_CLASS (parent_class)->finalize (object);
125 }
126
127 GtkType
128 camel_nntp_folder_get_type (void)
129 {
130         static GtkType camel_nntp_folder_type = 0;
131         
132         if (!camel_nntp_folder_type)    {
133                 GtkTypeInfo camel_nntp_folder_info =    
134                 {
135                         "CamelNNTPFolder",
136                         sizeof (CamelNNTPFolder),
137                         sizeof (CamelNNTPFolderClass),
138                         (GtkClassInitFunc) camel_nntp_folder_class_init,
139                         (GtkObjectInitFunc) NULL,
140                                 /* reserved_1 */ NULL,
141                                 /* reserved_2 */ NULL,
142                         (GtkClassInitFunc) NULL,
143                 };
144                 
145                 camel_nntp_folder_type = gtk_type_unique (CAMEL_FOLDER_TYPE, &camel_nntp_folder_info);
146         }
147         
148         return camel_nntp_folder_type;
149 }
150
151 static void 
152 _init (CamelFolder *folder, CamelStore *parent_store,
153        CamelFolder *parent_folder, const gchar *name, gchar separator,
154        CamelException *ex)
155 {
156         /* call parent method */
157         parent_class->init (folder, parent_store, parent_folder,
158                             name, separator, ex);
159         if (camel_exception_get_id (ex)) return;
160
161         /* we assume that the parent init
162            method checks for the existance of @folder */
163
164         if (!strcmp(name, "/"))
165           {
166             folder->has_summary_capability = FALSE;
167             folder->can_hold_messages = FALSE;
168             folder->can_hold_folders = TRUE;
169           }
170         else
171           {
172             folder->has_summary_capability = TRUE;
173             folder->can_hold_messages = TRUE;
174             folder->can_hold_folders = TRUE;
175           }
176
177         folder->has_uid_capability = TRUE;
178         folder->has_search_capability = FALSE;
179 }
180
181 /* internal method used to : 
182    - test for the existence of a summary file 
183    - test the sync between the summary and the newsgroup
184    - load the summary or create it if necessary 
185 */ 
186 static void
187 _check_get_or_maybe_generate_summary_file (CamelNNTPFolder *nntp_folder,
188                                            CamelException *ex)
189 {
190         CamelFolder *folder = CAMEL_FOLDER (nntp_folder);
191
192         nntp_folder->summary = camel_folder_summary_new ();
193         camel_folder_summary_set_filename (nntp_folder->summary, nntp_folder->summary_file_path);
194
195         if (-1 == camel_folder_summary_load (nntp_folder->summary)) {
196                 /* Bad or nonexistant summary file */
197                 camel_nntp_get_headers (CAMEL_FOLDER( folder )->parent_store, nntp_folder, ex);
198                 if (camel_exception_get_id (ex))
199                         return;
200
201                 /* XXX check return value */
202                 camel_folder_summary_save (nntp_folder->summary);
203         }
204 }
205
206
207 static void
208 _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex)
209 {
210         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
211         const gchar *root_dir_path;
212
213         /* call parent class */
214         parent_class->open (folder, mode, ex);
215         if (camel_exception_get_id(ex))
216                 return;
217
218 #if 0
219         /* get (or create) uid list */
220         if (!(nntp_load_uid_list (nntp_folder) > 0))
221                 nntp_generate_uid_list (nntp_folder);
222 #endif
223
224         root_dir_path = camel_nntp_store_get_toplevel_dir (CAMEL_NNTP_STORE(folder->parent_store));
225
226         nntp_folder->summary_file_path = g_strdup_printf ("%s/%s-ev-summary", root_dir_path, folder->name);
227
228         _check_get_or_maybe_generate_summary_file (nntp_folder, ex);
229 }
230
231
232 static void
233 _close (CamelFolder *folder, gboolean expunge, CamelException *ex)
234 {
235         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
236         CamelFolderSummary *summary = nntp_folder->summary;
237
238         /* call parent implementation */
239         parent_class->close (folder, expunge, ex);
240
241         /* XXX only if dirty? */
242         camel_folder_summary_save (summary);
243 }
244
245 static gboolean
246 _exists (CamelFolder *folder, CamelException *ex)
247 {
248 #if 0
249         CamelNNTPFolder *nntp_folder;
250         struct stat stat_buf;
251         gint stat_error;
252         gboolean exists;
253
254         g_assert(folder != NULL);
255
256         nntp_folder = CAMEL_NNTP_FOLDER (folder);
257
258         /* check if the nntp summary path is determined */
259         if (!nntp_folder->summary_file_path) {
260                 camel_exception_set (ex, 
261                                      CAMEL_EXCEPTION_FOLDER_INVALID,
262                                      "undetermined folder summary path. Maybe use set_name ?");
263                 return FALSE;
264         }
265
266         /* check if the nntp file exists */
267         stat_error = stat (nntp_folder->summary_file_path, &stat_buf);
268         if (stat_error == -1)
269                 return FALSE;
270         
271         exists = S_ISREG (stat_buf.st_mode);
272         /* we should  check the rights here  */
273         
274         return exists;
275 #endif
276         return TRUE;
277 }
278
279 static gboolean
280 _create (CamelFolder *folder, CamelException *ex)
281 {
282 #if 0
283         CamelNNTPSummary *summary;
284         g_assert(folder != NULL);
285
286         /* call default implementation */
287         parent_class->create (folder, ex);
288
289         /* create the summary object */
290         summary = CAMEL_NNTP_SUMMARY (gtk_object_new (camel_nntp_summary_get_type (), NULL));
291         summary->nb_message = 0;
292         summary->next_uid = 1;
293         summary->nntp_file_size = 0;
294         summary->message_info = g_array_new (FALSE, FALSE, sizeof (CamelNNTPSummaryInformation));
295 #endif
296         
297         return TRUE;
298 }
299
300 static gboolean
301 _delete (CamelFolder *folder, gboolean recurse, CamelException *ex)
302 {
303 #if 0
304         gboolean folder_already_exists;
305
306         g_assert(folder != NULL);
307
308         /* check if the folder object exists */
309
310         /* in the case where the folder does not exist, 
311            return immediatly */
312         folder_already_exists = camel_folder_exists (folder, ex);
313         if (camel_exception_get_id (ex))
314                 return FALSE;
315
316         if (!folder_already_exists)
317                 return TRUE;
318
319
320         /* call default implementation.
321            It should delete the messages in the folder
322            and recurse the operation to subfolders */
323         parent_class->delete (folder, recurse, ex);
324 #endif
325         return TRUE;
326 }
327
328 gboolean
329 _delete_messages (CamelFolder *folder, CamelException *ex)
330 {
331         
332         gboolean folder_already_exists;
333
334         g_assert(folder!=NULL);
335         
336         /* in the case where the folder does not exist, 
337            return immediatly */
338         folder_already_exists = camel_folder_exists (folder, ex);
339         if (camel_exception_get_id (ex)) return FALSE;
340
341         if (!folder_already_exists) return TRUE;
342
343         return TRUE;
344 }
345
346 static GList *
347 _list_subfolders (CamelFolder *folder, CamelException *ex)
348 {
349         /* newsgroups don't have subfolders */
350         return NULL;
351 }
352
353 static gint
354 _get_message_count (CamelFolder *folder, CamelException *ex)
355 {
356         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER(folder);
357
358         g_assert (folder);
359         g_assert (nntp_folder->summary);
360
361         return camel_folder_summary_count(nntp_folder->summary);
362 }
363
364 #if 0
365 static void
366 _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex)
367 {
368         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
369 #if 0
370         CamelNNTPSummary *summary = CAMEL_NNTP_SUMMARY (folder->summary);
371 #endif
372         CamelStream *output_stream;
373         guint32 tmp_file_size;
374         guint32 next_uid;
375         gint tmp_file_fd;
376         GArray *message_info_array;
377 #if 0
378         GArray *nntp_summary_info;
379 #endif
380         gchar *tmp_message_filename;
381         gint fd1, fd2;
382         int i;
383
384         /* write the message itself */
385         output_stream = camel_stream_fs_new_with_name (tmp_message_filename,
386                                                        CAMEL_STREAM_FS_WRITE);
387         if (output_stream != NULL) {
388                 camel_stream_write_string (output_stream, "From - \n");
389                 camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), output_stream);
390         }
391         camel_stream_flush (output_stream);
392         gtk_object_unref (GTK_OBJECT (output_stream));
393
394         /* at this point we have saved the message to a
395            temporary file, now, we have to add the x-evolution 
396            field and also update the main summary */
397
398         /* 
399            First : parse the nntp file, but only from the 
400            position where the message has been added, 
401            wich happens to be the last postion in the 
402            nntp file before we added the message.
403            This position is still stored in the summary 
404            for the moment 
405         */
406         next_uid = summary->next_uid;
407         tmp_file_fd = open (tmp_message_filename, O_RDONLY);
408         message_info_array =
409                 camel_nntp_parse_file (tmp_file_fd, "From - ", 0,
410                                        &tmp_file_size, &next_uid, TRUE,
411                                        NULL, 0, ex); 
412         
413         close (tmp_file_fd);
414
415         /* get the value of the last available UID
416            as saved in the summary file, again */
417         next_uid = summary->next_uid;
418
419         /* make sure all our of message info's have 0 uid - ignore any
420            set elsewhere */
421         for (i=0;i<message_info_array->len;i++) {
422                 g_array_index(message_info_array, CamelNNTPParserMessageInfo, i).uid = 0;
423         }
424
425         /* 
426            OK, this is not very efficient, we should not use the same
427            method as for parsing an entire mail file, 
428            but I have no time to write a simpler parser 
429         */
430 #if 0
431         next_uid = camel_nntp_write_xev (nntp_folder, tmp_message_filename, 
432                                          message_info_array, &tmp_file_size, next_uid, ex);
433         
434 #endif
435         if (camel_exception_get_id (ex)) { 
436                 /* ** FIXME : free the preparsed information */
437                 return;
438         }
439
440 #if 0
441         nntp_summary_info =
442                 parsed_information_to_nntp_summary (message_info_array);
443 #endif
444
445         /* store the number of messages as well as the summary array */
446         summary->nb_message += 1;               
447         summary->next_uid = next_uid;   
448
449         ((CamelNNTPSummaryInformation *)(nntp_summary_info->data))->position +=
450                 summary->nntp_file_size;
451         summary->nntp_file_size += tmp_file_size;               
452
453         camel_nntp_summary_append_entries (summary, nntp_summary_info);
454         g_array_free (nntp_summary_info, TRUE); 
455         
456
457         /* append the temporary file message to the nntp file */
458         fd1 = open (tmp_message_filename, O_RDONLY);
459         fd2 = open (nntp_folder->folder_file_path, 
460                     O_WRONLY | O_CREAT | O_APPEND,
461                     0600);
462
463         if (fd2 == -1) {
464                 camel_exception_setv (ex, 
465                                       CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
466                                       "could not open the nntp folder file for appending the message\n"
467                                       "\t%s\n"
468                                       "Full error is : %s\n",
469                                       nntp_folder->folder_file_path,
470                                       strerror (errno));
471                 return;
472         }
473
474         camel_nntp_copy_file_chunk (fd1,
475                                     fd2, 
476                                     tmp_file_size, 
477                                     ex);
478         close (fd1);
479         close (fd2);
480
481         /* remove the temporary file */
482         unlink (tmp_message_filename);
483
484         g_free (tmp_message_filename);
485 }
486 #endif
487
488
489
490 static GPtrArray *
491 _get_uid_array (CamelFolder *folder, CamelException *ex) 
492 {
493         CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
494         GPtrArray *message_info_array, *out;
495         CamelMessageInfo *message_info;
496         int i;
497
498         message_info_array = nntp_folder->summary->messages;
499
500         out = g_ptr_array_new ();
501         g_ptr_array_set_size (out, message_info_array->len);
502         
503         for (i=0; i<message_info_array->len; i++) {
504                 message_info = (CamelMessageInfo *)(message_info_array->pdata) + i;
505                 out->pdata[i] = g_strdup (message_info->uid);
506         }
507         
508         return out;
509 }
510
511 static CamelMimeMessage *
512 _get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex)
513 {
514         CamelStream *nntp_istream;
515         CamelStream *message_stream;
516         CamelMimeMessage *message = NULL;
517         CamelStore *parent_store;
518         char *buf;
519         int buf_len;
520         int buf_alloc;
521         int status;
522         gboolean done;
523
524         /* get the parent store */
525         parent_store = camel_folder_get_parent_store (folder, ex);
526         if (camel_exception_get_id (ex)) {
527                 return NULL;
528         }
529
530         status = camel_nntp_command (CAMEL_NNTP_STORE( parent_store ), NULL, "ARTICLE %s", uid);
531
532         nntp_istream = CAMEL_NNTP_STORE (parent_store)->istream;
533
534         /* if the uid was not found, raise an exception and return */
535         if (status != CAMEL_NNTP_OK) {
536                 camel_exception_setv (ex, 
537                                      CAMEL_EXCEPTION_FOLDER_INVALID_UID,
538                                      "message %s not found.",
539                                       uid);
540                 return NULL;
541         }
542
543         /* XXX ick ick ick.  read the entire message into a buffer and
544            then create a stream_mem for it. */
545         buf_alloc = 2048;
546         buf_len = 0;
547         buf = malloc(buf_alloc);
548         done = FALSE;
549
550         buf[0] = 0;
551
552         while (!done) {
553                 char *line = camel_stream_buffer_read_line ( CAMEL_STREAM_BUFFER ( nntp_istream ), ex);
554                 int line_length;
555
556                 /* XXX check exception */
557
558                 line_length = strlen ( line );
559
560                 if (!strcmp(line, ".")) {
561                         done = TRUE;
562                         g_free (line);
563                 }
564                 else {
565                         if (buf_len + line_length > buf_alloc) {
566                                 buf_alloc *= 2;
567                                 buf = realloc (buf, buf_alloc);
568                         }
569                         strcat(buf, line);
570                         strcat(buf, "\n");
571                         buf_len += strlen(line) + 1;
572                         g_free (line);
573                 }
574         }
575
576         /* create a stream bound to the message */
577         message_stream = camel_stream_mem_new_with_buffer(buf, buf_len);
578
579         message = camel_mime_message_new ();
580         if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *)message, message_stream) == -1) {
581                 gtk_object_unref ((GtkObject *)message);
582                 gtk_object_unref ((GtkObject *)message_stream);
583                 camel_exception_setv (ex,
584                                       CAMEL_EXCEPTION_FOLDER_INVALID_UID, /* XXX */
585                                       "Could not create message for uid %s.", uid);
586
587                 return NULL;
588         }
589         gtk_object_unref ((GtkObject *)message_stream);
590
591         /* init other fields? */
592         message->folder = folder;
593         gtk_object_ref((GtkObject *)folder);
594         message->message_uid = g_strdup(uid);
595
596 #if 0
597         gtk_signal_connect((GtkObject *)message, "message_changed", message_changed, folder);
598 #endif
599
600         return message;
601 }
602
603 /* get message info for a range of messages */
604 static GPtrArray *
605 summary_get_message_info (CamelFolder *folder, int first, int count)
606 {
607         GPtrArray *array = g_ptr_array_new();
608         int i, maxcount;
609         CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *)folder;
610
611         maxcount = camel_folder_summary_count(nntp_folder->summary);
612         maxcount = MIN(first + count, maxcount);
613         for (i=first;i<maxcount;i++)
614                 g_ptr_array_add(array, g_ptr_array_index(nntp_folder->summary->messages, i));
615
616         return array;
617 }