1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */
3 /* nntp-method.c - VFS module for NNTP
5 Copyright (C) 2001 Andy Hertzfeld
7 The Gnome Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 The Gnome Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with the Gnome Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
22 based on Ian McKellar's (yakk@yakk.net) ftp method for gnome-vfs
24 presents a high level, file-oriented view of a newsgroup, integrating file fragments
25 and organizing them in folders
27 Author: Andy Hertzfeld <andy@differnet.com> December 2001
35 #include <stdlib.h> /* for atoi */
36 #include <stdio.h> /* for sscanf */
39 #include <sys/types.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
46 #include <gconf/gconf-client.h>
49 #include <libgnomevfs/gnome-vfs-context.h>
50 #include <libgnomevfs/gnome-vfs-socket-buffer.h>
51 #include <libgnomevfs/gnome-vfs-inet-connection.h>
52 #include <libgnomevfs/gnome-vfs-method.h>
53 #include <libgnomevfs/gnome-vfs-module.h>
54 #include <libgnomevfs/gnome-vfs-module-shared.h>
55 #include <libgnomevfs/gnome-vfs-module-callback-module-api.h>
56 #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
57 #include <libgnomevfs/gnome-vfs-mime.h>
58 #include <libgnomevfs/gnome-vfs-parse-ls.h>
59 #include <libgnomevfs/gnome-vfs-utils.h>
61 #include "nntp-method.h"
64 #define MAX_RESPONSE_SIZE 4096
65 #define READ_BUFFER_SIZE 16384
67 /* these parameters should eventually be fetched from gconf */
68 #define MAX_MESSAGE_COUNT 2400
69 #define MIN_FILE_SIZE_THRESHOLD 4095
71 /* macros for the checking of NNTP response codes */
72 #define IS_100(X) ((X) >= 100 && (X) < 200)
73 #define IS_200(X) ((X) >= 200 && (X) < 300)
74 #define IS_300(X) ((X) >= 300 && (X) < 400)
75 #define IS_400(X) ((X) >= 400 && (X) < 500)
76 #define IS_500(X) ((X) >= 500 && (X) < 600)
78 static GnomeVFSResult do_open (GnomeVFSMethod *method,
79 GnomeVFSMethodHandle **method_handle,
81 GnomeVFSOpenMode mode,
82 GnomeVFSContext *context);
83 static gboolean do_is_local (GnomeVFSMethod *method,
84 const GnomeVFSURI *uri);
85 static GnomeVFSResult do_open_directory (GnomeVFSMethod *method,
86 GnomeVFSMethodHandle **method_handle,
88 GnomeVFSFileInfoOptions options,
89 GnomeVFSContext *context);
90 static GnomeVFSResult do_close_directory (GnomeVFSMethod *method,
91 GnomeVFSMethodHandle *method_handle,
92 GnomeVFSContext *context);
93 static GnomeVFSResult do_read_directory (GnomeVFSMethod *method,
94 GnomeVFSMethodHandle *method_handle,
95 GnomeVFSFileInfo *file_info,
96 GnomeVFSContext *context);
98 guint nntp_connection_uri_hash (gconstpointer c);
99 int nntp_connection_uri_equal (gconstpointer c, gconstpointer d);
100 static GnomeVFSResult nntp_connection_acquire (GnomeVFSURI *uri,
101 NNTPConnection **connection,
102 GnomeVFSContext *context);
103 static void nntp_connection_release (NNTPConnection *conn);
105 static GList* assemble_files_from_overview (NNTPConnection *conn, char *command);
107 static const char *anon_user = "anonymous";
108 static const char *anon_pass = "nobody@gnome.org";
109 static const int nntp_port = 119;
112 /* A GHashTable of GLists of NNTPConnections */
113 static GHashTable *spare_connections = NULL;
114 G_LOCK_DEFINE_STATIC (spare_connections);
115 static int total_connections = 0;
116 static int allocated_connections = 0;
118 /* single element cache of file objects for a given newsgroup */
119 static char* current_newsgroup_name = NULL;
120 static GList* current_newsgroup_files = NULL;
122 static GnomeVFSResult
123 nntp_response_to_vfs_result (NNTPConnection *conn)
125 int response = conn->response_code;
130 return GNOME_VFS_ERROR_CANCELLED;
132 return GNOME_VFS_ERROR_ACCESS_DENIED;
137 return GNOME_VFS_ERROR_LOGIN_FAILED;
142 return GNOME_VFS_ERROR_NOT_FOUND;
145 return GNOME_VFS_ERROR_NO_SPACE;
147 return GNOME_VFS_ERROR_BAD_FILE;
150 /* is this the correct interpretation of this error? */
151 if (IS_100 (response)) return GNOME_VFS_OK;
152 if (IS_200 (response)) return GNOME_VFS_OK;
153 /* is this the correct interpretation of this error? */
154 if (IS_300 (response)) return GNOME_VFS_OK;
155 if (IS_400 (response)) return GNOME_VFS_ERROR_GENERIC;
156 if (IS_500 (response)) return GNOME_VFS_ERROR_INTERNAL;
158 return GNOME_VFS_ERROR_GENERIC;
162 static GnomeVFSResult read_response_line(NNTPConnection *conn, char **line) {
163 GnomeVFSFileSize bytes = MAX_RESPONSE_SIZE, bytes_read;
164 char *ptr, *buf = g_malloc (MAX_RESPONSE_SIZE+1);
166 GnomeVFSResult result = GNOME_VFS_OK;
168 while (!strstr (conn->response_buffer->str, "\r\n")) {
169 /* we don't have a full line. Lets read some... */
171 result = gnome_vfs_socket_buffer_read (conn->socketbuf, buf,
173 buf[bytes_read] = '\0';
174 conn->response_buffer = g_string_append (conn->response_buffer,
176 if (result != GNOME_VFS_OK) {
177 g_warning ("Error `%s' during read\n",
178 gnome_vfs_result_to_string(result));
186 ptr = strstr (conn->response_buffer->str, "\r\n");
187 line_length = ptr - conn->response_buffer->str;
189 *line = g_strndup (conn->response_buffer->str, line_length);
191 g_string_erase (conn->response_buffer, 0 , line_length + 2);
197 static GnomeVFSResult
198 get_response (NNTPConnection *conn)
200 /* all that should be pending is a response to the last command */
201 GnomeVFSResult result;
205 result = read_response_line (conn, &line);
207 if (result != GNOME_VFS_OK) {
209 g_warning ("Error reading response line.");
213 /* response needs to be at least: "### x" - I think*/
214 if (g_ascii_isdigit (line[0]) &&
215 g_ascii_isdigit (line[1]) &&
216 g_ascii_isdigit (line[2]) &&
217 g_ascii_isspace (line[3])) {
219 conn->response_code = g_ascii_digit_value (line[0]) * 100
220 + g_ascii_digit_value (line[1]) * 10
221 + g_ascii_digit_value (line[2]);
223 if (conn->response_message) g_free (conn->response_message);
224 conn->response_message = g_strdup (line+4);
228 return nntp_response_to_vfs_result (conn);
232 /* hmm - not a valid line - lets ignore it :-) */
236 return GNOME_VFS_OK; /* should never be reached */
240 static GnomeVFSResult do_control_write (NNTPConnection *conn,
243 char *actual_command = g_strdup_printf ("%s\r\n", command);
244 GnomeVFSFileSize bytes = strlen (actual_command), bytes_written;
245 GnomeVFSResult result = gnome_vfs_socket_buffer_write (conn->socketbuf,
246 actual_command, bytes, &bytes_written);
247 gnome_vfs_socket_buffer_flush (conn->socketbuf);
248 g_free (actual_command);
254 nntp_connection_reset_buffer (NNTPConnection *conn)
256 g_string_erase (conn->response_buffer, 0, strlen(conn->response_buffer->str));
259 static GnomeVFSResult
260 do_basic_command (NNTPConnection *conn,
263 GnomeVFSResult result;
265 nntp_connection_reset_buffer (conn);
267 result = do_control_write(conn, command);
269 if (result != GNOME_VFS_OK) {
273 result = get_response (conn);
278 /* the following routines manage the connection with the news server */
280 /* create a new connection */
281 static GnomeVFSResult
282 nntp_connection_create (NNTPConnection **connptr, GnomeVFSURI *uri, GnomeVFSContext *context)
284 NNTPConnection *conn = g_new (NNTPConnection, 1);
285 GnomeVFSResult result;
287 int port = nntp_port;
288 const char *user = anon_user;
289 const char *pass = anon_pass;
291 conn->uri = gnome_vfs_uri_dup (uri);
293 conn->response_buffer = g_string_new ("");
294 conn->response_message = NULL;
295 conn->response_code = -1;
297 conn->anonymous = TRUE;
299 /* allocate the read buffer */
300 conn->buffer = g_malloc(READ_BUFFER_SIZE);
301 conn->buffer_size = READ_BUFFER_SIZE;
302 conn->amount_in_buffer = 0;
303 conn->buffer_offset = 0;
305 conn->request_in_progress = FALSE;
307 if (gnome_vfs_uri_get_host_port (uri)) {
308 port = gnome_vfs_uri_get_host_port (uri);
311 if (gnome_vfs_uri_get_user_name (uri)) {
312 user = gnome_vfs_uri_get_user_name (uri);
313 conn->anonymous = FALSE;
316 if (gnome_vfs_uri_get_password (uri)) {
317 pass = gnome_vfs_uri_get_password (uri);
320 result = gnome_vfs_inet_connection_create (&conn->inet_connection,
321 gnome_vfs_uri_get_host_name (uri),
323 context ? gnome_vfs_context_get_cancellation(context) : NULL);
325 if (result != GNOME_VFS_OK) {
326 g_warning ("gnome_vfs_inet_connection_create (\"%s\", %d) = \"%s\"",
327 gnome_vfs_uri_get_host_name (uri),
328 gnome_vfs_uri_get_host_port (uri),
329 gnome_vfs_result_to_string (result));
330 g_string_free (conn->response_buffer, TRUE);
335 conn->socketbuf = gnome_vfs_inet_connection_to_socket_buffer (conn->inet_connection);
337 if (conn->socketbuf == NULL) {
338 g_warning ("gnome_vfs_inet_connection_get_socket_buffer () failed");
339 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
340 g_string_free (conn->response_buffer, TRUE);
342 return GNOME_VFS_ERROR_GENERIC;
345 result = get_response (conn);
347 if (result != GNOME_VFS_OK) {
348 g_warning ("nntp server (%s:%d) said `%d %s'",
349 gnome_vfs_uri_get_host_name (uri),
350 gnome_vfs_uri_get_host_port (uri),
351 conn->response_code, conn->response_message);
352 g_string_free (conn->response_buffer, TRUE);
357 if (!conn->anonymous) {
358 tmpstring = g_strdup_printf ("AUTHINFO user %s", user);
359 result = do_basic_command (conn, tmpstring);
362 if (IS_300 (conn->response_code)) {
363 tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass);
364 result = do_basic_command (conn, tmpstring);
368 if (result != GNOME_VFS_OK) {
370 g_warning ("NNTP server said: \"%d %s\"\n", conn->response_code,
371 conn->response_message);
372 gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE);
373 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
382 /* g_message("successfully logged into server, total connections: %d", total_connections); */
386 /* destroy the connection object and deallocate the associated resources */
388 nntp_connection_destroy (NNTPConnection *conn)
390 GnomeVFSResult result;
392 if (conn->inet_connection) {
393 result = do_basic_command(conn, "QUIT");
394 gnome_vfs_inet_connection_destroy (conn->inet_connection, NULL);
398 gnome_vfs_socket_buffer_destroy (conn->socketbuf, FALSE);
400 gnome_vfs_uri_unref (conn->uri);
402 if (conn->response_buffer)
403 g_string_free(conn->response_buffer, TRUE);
405 g_free (conn->response_message);
406 g_free (conn->server_type);
407 g_free (conn->buffer);
413 /* hash routines for managing the pool of connections */
414 /* g_str_hash and g_str_equal seem to fail with null arguments */
417 my_str_hash (const char *c)
420 return g_str_hash (c);
425 my_str_equal (gconstpointer c,
428 if ((c && !d) || (d &&!c))
432 return g_str_equal (c,d);
435 /* hash the bits of a GnomeVFSURI that distingush NNTP connections */
437 nntp_connection_uri_hash (gconstpointer c)
439 GnomeVFSURI *uri = (GnomeVFSURI *) c;
441 return my_str_hash (gnome_vfs_uri_get_host_name (uri)) +
442 my_str_hash (gnome_vfs_uri_get_user_name (uri)) +
443 my_str_hash (gnome_vfs_uri_get_password (uri)) +
444 gnome_vfs_uri_get_host_port (uri);
447 /* test the equality of the bits of a GnomeVFSURI that distingush NNTP
451 nntp_connection_uri_equal (gconstpointer c,
454 GnomeVFSURI *uri1 = (GnomeVFSURI *)c;
455 GnomeVFSURI *uri2 = (GnomeVFSURI *) d;
457 return my_str_equal (gnome_vfs_uri_get_host_name(uri1),
458 gnome_vfs_uri_get_host_name (uri2)) &&
459 my_str_equal (gnome_vfs_uri_get_user_name (uri1),
460 gnome_vfs_uri_get_user_name (uri2)) &&
461 my_str_equal (gnome_vfs_uri_get_password (uri1),
462 gnome_vfs_uri_get_password (uri2)) &&
463 gnome_vfs_uri_get_host_port (uri1) ==
464 gnome_vfs_uri_get_host_port (uri2);
467 static GnomeVFSResult
468 nntp_connection_acquire (GnomeVFSURI *uri, NNTPConnection **connection, GnomeVFSContext *context)
470 GList *possible_connections;
471 NNTPConnection *conn = NULL;
472 GnomeVFSResult result = GNOME_VFS_OK;
474 G_LOCK (spare_connections);
476 if (spare_connections == NULL) {
477 spare_connections = g_hash_table_new (nntp_connection_uri_hash,
478 nntp_connection_uri_equal);
481 possible_connections = g_hash_table_lookup (spare_connections, uri);
483 if (possible_connections) {
484 /* spare connection(s) found */
485 conn = (NNTPConnection *) possible_connections->data;
486 possible_connections = g_list_remove (possible_connections, conn);
488 if(!g_hash_table_lookup (spare_connections, uri)) {
489 /* uri will be used as a key in the hashtable */
490 uri = gnome_vfs_uri_dup (uri);
493 g_hash_table_insert (spare_connections, uri, possible_connections);
495 /* make sure connection hasn't timed out */
496 result = do_basic_command(conn, "MODE READER");
497 if (result != GNOME_VFS_OK) {
498 nntp_connection_destroy (conn);
499 result = nntp_connection_create (&conn, uri, context);
503 result = nntp_connection_create (&conn, uri, context);
506 G_UNLOCK (spare_connections);
510 if(result == GNOME_VFS_OK) allocated_connections++;
516 nntp_connection_release (NNTPConnection *conn)
518 GList *possible_connections;
521 g_return_if_fail (conn);
523 G_LOCK (spare_connections);
524 if (spare_connections == NULL)
526 g_hash_table_new (nntp_connection_uri_hash,
527 nntp_connection_uri_equal);
529 possible_connections = g_hash_table_lookup (spare_connections,
531 possible_connections = g_list_append (possible_connections, conn);
533 if (g_hash_table_lookup (spare_connections, conn->uri)) {
534 uri = conn->uri; /* no need to duplicate uri */
536 /* uri will be used as key */
537 uri = gnome_vfs_uri_dup (conn->uri);
539 g_hash_table_insert (spare_connections, uri, possible_connections);
540 allocated_connections--;
542 G_UNLOCK(spare_connections);
545 /* the following routines are the code for assembling the file list from the newsgroup headers. */
547 /* Here are the folder name filtering routines, which attempt to remove track numbers and other extraneous
548 * info from folder names, to make folder grouping work better. First, there are some character classifying
552 is_number_or_space (char test_char)
554 return g_ascii_isspace (test_char) || g_ascii_isdigit (test_char)
555 || test_char == '_' || test_char == '-' || test_char == '/';
559 all_numbers_or_spaces (char *left_ptr, char *right_ptr)
561 char *current_char_ptr;
564 current_char_ptr = left_ptr;
565 while (current_char_ptr < right_ptr) {
566 current_char = *current_char_ptr++;
567 if (!is_number_or_space(current_char)) {
574 /* each of the next set of routines implement a specific filtering operation */
577 remove_numbers_between_dashes(char *input_str)
579 char *left_ptr, *right_ptr;
580 int length_to_end, segment_size;
582 left_ptr = strchr (input_str, '-');
583 while (left_ptr != NULL) {
584 right_ptr = strchr(left_ptr + 1, '-');
585 if (right_ptr != NULL) {
586 segment_size = right_ptr - left_ptr;
587 if (all_numbers_or_spaces(left_ptr, right_ptr) && segment_size > 1) {
588 length_to_end = strlen(right_ptr + 1) + 1;
589 g_memmove(left_ptr, right_ptr + 1, length_to_end);
591 left_ptr = right_ptr;
595 right_ptr = input_str + strlen(input_str) - 1;
596 if (all_numbers_or_spaces(left_ptr, right_ptr)) {
606 remove_number_at_end (char *input_str)
608 char *space_ptr, *next_char;
611 space_ptr = strrchr(input_str, ' ');
612 next_char = space_ptr + 1;
614 if (space_ptr != NULL) {
616 while (*next_char != '\0') {
617 if (!is_number_or_space (*next_char)) {
630 trim_nonalpha_chars (char *input_str)
632 char *left_ptr, *right_ptr;
634 /* first handle the end of the string */
635 right_ptr = input_str + strlen(input_str) - 1;
636 while (!g_ascii_isalnum (*right_ptr) && right_ptr > input_str) {
643 /* now handle the beginning */
644 left_ptr = input_str;
645 while (*left_ptr && !g_ascii_isalnum(*left_ptr)) {
652 remove_of_expressions (char *input_str)
654 char *left_ptr, *right_ptr;
656 gboolean found_number;
658 left_ptr = strstr (input_str, "of");
659 if (left_ptr == NULL) {
660 left_ptr = strstr (input_str, "OF");
662 if (left_ptr == NULL) {
663 left_ptr = strstr (input_str, "/");
666 if (left_ptr != NULL) {
667 found_number = FALSE;
668 right_ptr = left_ptr + 2;
669 left_ptr = left_ptr - 1;
670 while (is_number_or_space(*left_ptr) && left_ptr >= input_str) {
671 found_number = found_number || g_ascii_isdigit(*left_ptr);
674 while (is_number_or_space(*right_ptr)) {
675 found_number = found_number || g_ascii_isdigit(*right_ptr);
680 length_to_end = strlen(right_ptr);
681 if (length_to_end > 0) {
682 g_memmove(left_ptr + 1, right_ptr, length_to_end + 1);
691 /* here is the main folder name filtering routine, which returns a new string containing the filtered
692 * version of the passed-in folder name
695 filter_folder_name(char *folder_name)
697 char *temp_str, *save_str, *result_str;
698 char *colon_ptr, *left_ptr, *right_ptr;
701 temp_str = g_strdup(folder_name);
703 temp_str = g_strstrip(temp_str);
705 /* if there is a colon, strip everything before it */
706 colon_ptr = strchr(temp_str, ':');
707 if (colon_ptr != NULL) {
708 temp_str = colon_ptr + 1;
711 /* remove everything in brackets */
712 left_ptr = strrchr(temp_str, '[');
713 if (left_ptr != NULL) {
714 right_ptr = strchr(left_ptr, ']');
715 if (right_ptr != NULL && right_ptr > left_ptr) {
716 length_to_end = strlen(right_ptr + 1) + 1;
717 g_memmove(left_ptr, right_ptr + 1, length_to_end);
721 /* eliminate "n of m" expressions */
722 remove_of_expressions(temp_str);
724 /* remove numbers at end of the string */
725 remove_number_at_end(temp_str);
727 /* remove numbers between dashes */
728 remove_numbers_between_dashes(temp_str);
730 /* trim leading and trailing non-alphanumeric characters */
731 temp_str = trim_nonalpha_chars(temp_str);
733 /* truncate if necessary */
734 if (strlen(temp_str) > 30) {
735 left_ptr = temp_str + 29;
736 while (g_ascii_isalpha(*left_ptr)) {
743 /* return the result */
744 result_str = g_strdup(temp_str);
749 /* parse dates by allowing gnome-vfs to do most of the work */
752 remove_commas(char *source_str)
757 while (*ptr != '\0') {
759 g_memmove(ptr, ptr+1, strlen(ptr));
767 is_number(char *input_str) {
770 cur_char = input_str;
772 if (!g_ascii_isdigit(*cur_char)) {
781 parse_date_string (const char* date_string, time_t *t)
784 char *filename, *linkname;
785 char *temp_str, *mapped_date;
793 /* remove commas from day, and flip the day and month */
794 date_parts = g_strsplit(date_string, " ", 0);
796 if (is_number(date_parts[0])) {
800 remove_commas(date_parts[0]);
803 temp_str = date_parts[offset];
804 date_parts[offset] = date_parts[offset + 1];
805 date_parts[offset + 1] = temp_str;
806 mapped_date = g_strjoinv(" ", date_parts);
808 temp_str = g_strdup_printf("-rw-rw-rw- 1 500 500 %s x", mapped_date);
809 g_strfreev(date_parts);
810 g_free (mapped_date);
812 result = gnome_vfs_parse_ls_lga (temp_str, &s, &filename, &linkname);
814 g_message("error parsing %s, %s", date_string, temp_str);
822 /* parse_header takes a message header an extracts the subject and id, and then
823 * parses the subject to get the file name and fragment sequence number
826 parse_header(const char* header_str, char** filename, char** folder_name, char** message_id,
827 int* message_size, int* part_number, int* total_parts, time_t *mod_date)
829 char* temp_str, *file_start;
830 char* left_paren, *right_paren, *slash_pos;
845 header_parts = g_strsplit(header_str, "\t", 0);
846 temp_str = g_strdup(header_parts[1]);
848 *message_id = g_strdup(header_parts[4]);
850 if (header_parts[6] != NULL) {
851 *message_size = atoi(header_parts[6]);
854 parse_date_string(header_parts[3], mod_date);
856 g_strfreev(header_parts);
858 /* find the parentheses and extra the part number and total part count */
861 left_paren = strchr(temp_str,'(');
862 right_paren = strchr(temp_str, ')');
864 /* if we could't find parentheses, try braces */
865 if (left_paren == NULL) {
866 left_paren = strchr(temp_str,'[');
867 right_paren = strchr(temp_str, ']');
870 while (!has_count && left_paren != NULL && right_paren != NULL) {
871 slash_pos = strchr(left_paren, '/');
874 if (slash_pos == NULL) {
875 slash_pos = strchr(left_paren, '-');
877 if (slash_pos == NULL) {
878 slash_pos = strstr(left_paren, " of ");
882 if (slash_pos != NULL) {
887 *part_number = atoi (left_paren + 1);
888 *total_parts = atoi (slash_pos + slash_bump);
890 left_paren = strchr(right_paren + 1, '(');
891 right_paren = strchr(right_paren + 1, ')');
901 /* isolate the filename and copy it to the result */
904 file_start = strrchr (temp_str, '-');
906 if (file_start == NULL) {
910 *filename = g_strdup (g_strstrip (file_start + 1));
912 *folder_name = filter_folder_name (temp_str);
915 *filename = g_strdup (temp_str);
923 /* create a new file fragment structure */
924 static nntp_fragment*
925 nntp_fragment_new(int fragment_number, char* fragment_id, int fragment_size)
927 nntp_fragment* new_fragment = (nntp_fragment*) g_malloc (sizeof (nntp_fragment));
928 new_fragment->fragment_number = fragment_number;
929 new_fragment->fragment_id = g_strdup (fragment_id);
930 new_fragment->fragment_size = fragment_size;
931 new_fragment->bytes_read = 0;
936 /* deallocate the passed in file fragment */
938 nntp_fragment_destroy(nntp_fragment* fragment)
940 g_free(fragment->fragment_id);
944 /* utility routine to map slashes to dashes in the passed in string so we never return
945 * a filename with slashes
948 map_slashes(char *str)
953 while (*current_ptr != '\0') {
954 if (*current_ptr == '/') {
962 /* allocate a file object */
964 nntp_file_new(char* file_name, char *folder_name, int total_parts)
967 nntp_file* new_file = (nntp_file*) g_malloc (sizeof (nntp_file));
969 if (strlen(map_slashes(file_name)) < 1) {
970 file_name = "(Empty)";
973 new_file->file_name = map_slashes(g_strdup(file_name));
974 new_file->folder_name = g_strdup(folder_name);
976 new_file->file_type = NULL;
977 new_file->part_list = NULL;
979 new_file->file_size = 0;
980 new_file->total_parts = total_parts;
981 new_file->is_directory = FALSE;
986 /* deallocate a file object */
988 nntp_file_destroy (nntp_file* file)
992 g_free(file->file_name);
993 g_free(file->folder_name);
995 current_part = file->part_list;
996 while (current_part != NULL) {
997 if (file->is_directory) {
998 nntp_file_destroy((nntp_file*) current_part->data);
1000 nntp_fragment_destroy((nntp_fragment*) current_part->data);
1002 current_part = current_part->next;
1005 g_list_free(file->part_list);
1010 /* look up a fragment in a file */
1011 static nntp_fragment*
1012 nntp_file_look_up_fragment (nntp_file *file, int fragment_number)
1014 nntp_fragment *fragment;
1015 GList *current_fragment = file->part_list;
1016 while (current_fragment != NULL) {
1017 fragment = (nntp_fragment*) current_fragment->data;
1018 if (fragment->fragment_number == fragment_number) {
1021 current_fragment = current_fragment->next;
1026 /* compute the total size of a file by adding up the size of its fragments, then scale down by 3/4 to account for
1027 * the uu or base64 encoding
1030 nntp_file_get_total_size (nntp_file *file)
1032 nntp_fragment *fragment;
1034 GList *current_fragment;
1037 current_fragment = file->part_list;
1038 while (current_fragment != NULL) {
1039 fragment = (nntp_fragment*) current_fragment->data;
1040 total_size += fragment->fragment_size - 800; /* subtract out average header size */
1042 current_fragment = current_fragment->next;
1045 return 3 * total_size / 4;
1048 /* add a fragment to a file */
1050 nntp_file_add_part (nntp_file *file, int fragment_number, char* fragment_id, int fragment_size)
1052 /* make sure we don't already have this fragment */
1053 /* note: this isn't good for threads, where more than one message has the same subject line,
1054 * so we really should collect them instead of discarding all but the first */
1056 if (nntp_file_look_up_fragment (file, fragment_number) == NULL) {
1057 nntp_fragment* fragment = nntp_fragment_new (fragment_number, fragment_id, fragment_size);
1058 file->part_list = g_list_append (file->part_list, fragment);
1063 /* look up a file object in the file list from its name; avoid lookup if same as last time (soon) */
1065 look_up_file (GList *file_list, char *filename, gboolean is_directory)
1068 GList* current_file = file_list;
1069 while (current_file != NULL) {
1070 file = (nntp_file*) current_file->data;
1071 if (g_ascii_strcasecmp( file->file_name, filename) == 0 &&
1072 file->is_directory == is_directory) {
1075 current_file = current_file->next;
1080 /* start reading the article associated with the passed in fragment */
1081 static GnomeVFSResult
1082 start_loading_article (NNTPConnection *conn, nntp_fragment *fragment)
1084 GnomeVFSResult result;
1088 /* issue the command to fetch the article */
1089 command_str = g_strdup_printf("BODY %s", fragment->fragment_id);
1090 result = do_control_write(conn, command_str);
1091 g_free(command_str);
1093 if (result != GNOME_VFS_OK) {
1097 /* read the response command, and check that it's the right number (eventually) */
1098 result = read_response_line (conn, &line);
1100 if (result != GNOME_VFS_OK) {
1104 conn->request_in_progress = TRUE;
1105 return GNOME_VFS_OK;
1108 /* utility routine to uudecode the passed in text. Translate every four 6-bit bytes to 3 8 bit ones */
1110 uu_decode_text(char *text_buffer, int text_len)
1112 int input_index, output_index;
1113 int byte_0, byte_1, byte_2, byte_3;
1115 input_index = 1; /* skip length byte */
1117 while (input_index < text_len) {
1118 byte_0 = text_buffer[input_index] - 32;
1119 byte_1 = text_buffer[input_index + 1] - 32;
1120 byte_2 = text_buffer[input_index + 2] - 32;
1121 byte_3 = text_buffer[input_index + 3] - 32;
1123 text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3);
1124 text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15);
1125 text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63);
1130 return output_index;
1133 /* utility to decode the passed in text using base 64 */
1136 base_64_map(char input_char)
1138 if (input_char >= 'A' && input_char <= 'Z') {
1139 return input_char - 'A';
1142 if (input_char >= 'a' && input_char <= 'z') {
1143 return 26 + input_char - 'a';
1146 if (input_char >= '0' && input_char <= '9') {
1147 return 52 + input_char - '0';
1150 if (input_char == '+') {
1154 if (input_char == '/') {
1158 if (input_char == '=') {
1162 /* not a valid character, so return -1 */
1167 base_64_decode_text(char* text_buffer, int text_len)
1169 int input_index, output_index;
1170 int byte_0, byte_1, byte_2, byte_3;
1172 input_index = 1; /* skip length byte */
1174 while (input_index < text_len) {
1175 byte_0 = base_64_map(text_buffer[input_index]);
1176 byte_1 = base_64_map(text_buffer[input_index + 1]);
1177 byte_2 = base_64_map(text_buffer[input_index + 2]);
1178 byte_3 = base_64_map(text_buffer[input_index + 3]);
1180 /* if we hit a return, we're done */
1181 if (text_buffer[input_index] < 32) {
1182 return output_index;
1185 /* if there are any unmappable characters, skip this line */
1186 if (byte_0 < 0 || byte_1 < 0 || byte_2 < 0 || byte_3 < 0) {
1190 /* shift the bits into place and output them */
1191 text_buffer[output_index] = ((byte_0 << 2) & 252) | ((byte_1 >> 4) & 3);
1192 text_buffer[output_index + 1] = ((byte_1 << 4) & 240) | ((byte_2 >> 2) & 15);
1193 text_buffer[output_index + 2] = ((byte_2 << 6) & 192) | (byte_3 & 63);
1198 return output_index;
1201 /* utility that returns true if all the characters in the passed in line are valide for uudecoding */
1203 line_in_decode_range(char* input_line)
1208 line_ptr = input_line;
1209 while (*line_ptr != '\0') {
1210 current_char = *line_ptr++;
1211 if (current_char < 32 || current_char > 95) {
1218 /* fill the buffer from the passed in article fragment. If the first line flag is set,
1219 * test to see if uudecoding is required.
1221 static GnomeVFSResult
1222 load_from_article (NNTPConnection *conn, nntp_fragment *fragment, gboolean first_line_flag)
1224 GnomeVFSResult result;
1230 /* loop, loading the article into the buffer */
1233 while (buffer_offset < conn->buffer_size - 1024) {
1234 result = read_response_line (conn, &line);
1235 if (first_line_flag && !conn->uu_decode_mode && !conn->base_64_decode_mode) {
1236 /* FIXME: should apply additional checks here for the permission flags, etc */
1237 if (strncmp(line, "begin ", 6) == 0) {
1238 conn->uu_decode_mode = TRUE;
1242 } else if (strncmp(line, "Content-Transfer-Encoding: base64", 33) == 0) {
1243 conn->base_64_decode_mode = TRUE;
1247 } else if (line[0] == 'M' && strlen(line) == 61 && line_in_decode_range(line)) {
1248 conn->uu_decode_mode = TRUE;
1254 if (line[0] != '.' && line[1] != '\r') {
1255 line_len = strlen(line);
1256 if (buffer_offset + line_len > conn->buffer_size) {
1257 g_message("Error! exceeded buffer! %d", buffer_offset + line_len);
1258 line_len = conn->buffer_size - buffer_offset;
1260 dest_ptr = (char*) conn->buffer + buffer_offset;
1261 g_memmove(dest_ptr, line, line_len);
1262 if (conn->uu_decode_mode) {
1263 line_len = uu_decode_text(dest_ptr, line_len);
1264 buffer_offset += line_len;
1265 fragment->bytes_read += line_len;
1266 } else if (conn->base_64_decode_mode) {
1267 line_len = base_64_decode_text(dest_ptr, line_len);
1268 buffer_offset += line_len;
1269 fragment->bytes_read += line_len;
1271 buffer_offset += line_len + 1;
1272 dest_ptr += line_len;
1274 fragment->bytes_read += line_len + 1;
1278 conn->request_in_progress = FALSE;
1283 conn->amount_in_buffer = buffer_offset;
1284 conn->buffer_offset = 0;
1286 return GNOME_VFS_OK;
1289 /* load data from the current file fragment, advancing to a new one if necessary */
1290 static GnomeVFSResult
1291 load_file_fragment(NNTPConnection *connection)
1293 nntp_fragment *fragment;
1294 GnomeVFSResult result;
1295 gboolean first_line_flag;
1297 first_line_flag = FALSE;
1298 if (!connection->request_in_progress) {
1299 if (connection->current_fragment == NULL) {
1300 connection->current_fragment = connection->current_file->part_list;
1301 first_line_flag = TRUE;
1303 connection->current_fragment = connection->current_fragment->next;
1304 if (connection->current_fragment == NULL) {
1305 connection->eof_flag = TRUE;
1306 return GNOME_VFS_ERROR_EOF;
1309 fragment = (nntp_fragment *) connection->current_fragment->data;
1310 result = start_loading_article(connection, fragment);
1313 if (connection->current_fragment == NULL) {
1314 connection->eof_flag = TRUE;
1315 return GNOME_VFS_ERROR_EOF;
1318 fragment = (nntp_fragment *) connection->current_fragment->data;
1319 result = load_from_article(connection, fragment, first_line_flag);
1323 /* calculate the size of the data remaining in the buffer */
1325 bytes_in_buffer(NNTPConnection *connection)
1327 return connection->amount_in_buffer - connection->buffer_offset;
1330 /* utility routine to move bytes from the fragment to the application */
1332 copy_bytes_from_buffer(NNTPConnection *connection,
1333 gpointer destination_buffer,
1335 GnomeVFSFileSize *bytes_read)
1339 size_to_move = bytes_in_buffer(connection);
1340 if (size_to_move == 0) {
1344 if (bytes_to_read < size_to_move) {
1345 size_to_move = bytes_to_read;
1347 /* move the bytes from the buffer */
1348 g_memmove(destination_buffer, ((char*) connection->buffer) + connection->buffer_offset, size_to_move);
1350 /* update the counts */
1351 connection->buffer_offset += size_to_move;
1352 *bytes_read += size_to_move;
1353 return size_to_move;
1356 /* read a byte range from the active connection */
1357 static GnomeVFSResult
1358 nntp_file_read(NNTPConnection *connection,
1360 GnomeVFSFileSize num_bytes,
1361 GnomeVFSFileSize *bytes_read)
1364 GnomeVFSResult result;
1366 /* loop, loading fragments and copying data until the request is fulfilled or
1367 * we're out of fragments
1369 bytes_to_read = num_bytes;
1371 while (bytes_to_read > 0) {
1372 bytes_to_read -= copy_bytes_from_buffer(connection, ((char*) buffer) + *bytes_read, bytes_to_read, bytes_read);
1373 if (bytes_to_read > bytes_in_buffer(connection)) {
1374 if (connection->eof_flag) {
1375 /* don't return EOF here as it will cause the last part to be discarded */
1376 return GNOME_VFS_OK;
1378 result = load_file_fragment(connection);
1381 return GNOME_VFS_OK;
1384 /* deallocate the passed in file list */
1386 free_nntp_file_list (GList *file_list)
1388 GList* current_file;
1389 if (file_list == NULL) {
1393 current_file = file_list;
1394 while (current_file != NULL) {
1395 nntp_file_destroy ((nntp_file*) current_file->data);
1396 current_file = current_file->next;
1398 g_list_free (file_list);
1401 /* utility to obtain authorization info from gnome-vfs */
1403 get_auth_info (NNTPConnection *conn, char** user, char** password)
1406 GnomeVFSModuleCallbackAuthenticationIn in_args;
1407 GnomeVFSModuleCallbackAuthenticationOut out_args;
1412 memset (&in_args, 0, sizeof (in_args));
1413 memset (&out_args, 0, sizeof (out_args));
1415 in_args.uri = gnome_vfs_uri_to_string(conn->uri, 0);
1416 res = gnome_vfs_module_callback_invoke (GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION,
1417 &in_args, sizeof (in_args),
1418 &out_args, sizeof (out_args));
1419 g_free(in_args.uri);
1421 *user = out_args.username;
1422 *password = out_args.password;
1425 /* key routine to load the newsgroup overview and return a list of nntp_file objects.
1426 * It maintains a cache to avoid reloading.
1428 * FIXME: for now, we use a single element cache, but eventually we want to cache multiple newsgroups
1429 * also, we eventually want to reload it if enough time has passed
1431 static GnomeVFSResult
1432 get_files_from_newsgroup (NNTPConnection *conn, const char* newsgroup_name, GList** result_file_list)
1434 GnomeVFSResult result;
1435 char *group_command, *tmpstring;
1436 int first_message, last_message, total_messages;
1441 /* see if we can load it from the cache */
1442 if (current_newsgroup_name != NULL && g_ascii_strcasecmp (newsgroup_name, current_newsgroup_name) == 0) {
1443 *result_file_list = current_newsgroup_files;
1444 return GNOME_VFS_OK;
1447 *result_file_list = NULL;
1449 /* we don't have it in the cache, so load it from the server */
1450 if (current_newsgroup_name != NULL) {
1451 free_nntp_file_list (current_newsgroup_files);
1452 g_free (current_newsgroup_name);
1453 current_newsgroup_name = NULL;
1456 group_command = g_strdup_printf ("GROUP %s", newsgroup_name);
1458 result = do_basic_command (conn, group_command);
1459 g_free (group_command);
1461 if (result != GNOME_VFS_OK || conn->response_code != 211) {
1462 /* if we're anonymous, prompt for a password and try that */
1463 if (conn->anonymous) {
1464 get_auth_info(conn, &user, &pass);
1467 conn->anonymous = FALSE;
1468 tmpstring = g_strdup_printf ("AUTHINFO user %s", user);
1469 result = do_basic_command (conn, tmpstring);
1472 if (IS_300 (conn->response_code)) {
1473 tmpstring = g_strdup_printf ("AUTHINFO pass %s", pass);
1474 result = do_basic_command (conn, tmpstring);
1477 group_command = g_strdup_printf ("GROUP %s", newsgroup_name);
1478 result = do_basic_command (conn, group_command);
1479 g_free (group_command);
1487 if (result != GNOME_VFS_OK || conn->response_code != 211) {
1488 g_message ("couldnt set group to %s, code %d", newsgroup_name, conn->response_code);
1489 return GNOME_VFS_ERROR_NOT_FOUND; /* could differentiate error better */
1493 sscanf (conn->response_message, "%d %d %d", &total_messages, &first_message, &last_message);
1495 /* read in the header and build the file list for the connection */
1496 if ((last_message - first_message) > MAX_MESSAGE_COUNT) {
1497 first_message = last_message - MAX_MESSAGE_COUNT;
1500 command_str = g_strdup_printf ("XOVER %d-%d", first_message, last_message);
1501 file_list = assemble_files_from_overview (conn, command_str);
1502 g_free (command_str);
1504 current_newsgroup_name = g_strdup (newsgroup_name);
1505 current_newsgroup_files = file_list;
1507 *result_file_list = file_list;
1508 return GNOME_VFS_OK;
1511 /* utility to strip leading and trailing slashes from a string. It frees the input string and
1515 strip_slashes (char* source_string)
1517 char *temp_str, *result_str;
1520 temp_str = source_string;
1521 if (temp_str[0] == '/') {
1524 last_offset = strlen(temp_str) - 1;
1525 if (temp_str[last_offset] == '/') {
1526 temp_str[last_offset] = '\0';
1529 result_str = g_strdup(temp_str);
1530 g_free(source_string);
1534 /* parse the uri to extract the newsgroup name and the file name */
1536 extract_newsgroup_and_filename(GnomeVFSURI *uri, char** newsgroup, char **directory, char **filename)
1538 char *dirname, *slash_pos;
1540 *filename = gnome_vfs_unescape_string(gnome_vfs_uri_extract_short_name (uri), "");
1543 dirname = strip_slashes(gnome_vfs_uri_extract_dirname(uri));
1545 *newsgroup = gnome_vfs_unescape_string (dirname, "");
1546 slash_pos = strchr (*newsgroup, '/');
1547 if (slash_pos != NULL) {
1549 *directory = g_strdup (slash_pos + 1);
1554 /* fetch the nntp_file object from the passed in uri */
1556 nntp_file_from_uri (NNTPConnection *conn, GnomeVFSURI *uri)
1558 GnomeVFSResult result;
1559 char *newsgroup_name, *file_name, *directory_name;
1563 /* extract the newsgroup name */
1564 extract_newsgroup_and_filename(uri, &newsgroup_name, &directory_name, &file_name);
1566 /* load the (cached) file list */
1567 result = get_files_from_newsgroup (conn, newsgroup_name, &file_list);
1568 if (file_list == NULL) {
1571 /* parse the uri into the newsgroup and file */
1572 if (directory_name != NULL) {
1573 file = look_up_file (file_list, directory_name, TRUE);
1575 file = look_up_file (file->part_list, file_name, FALSE);
1578 file = look_up_file (file_list, file_name, FALSE);
1582 g_free(newsgroup_name);
1584 g_free(directory_name);
1589 /* determine if a file has all of it's fragments */
1591 has_all_fragments (nntp_file *file)
1593 return g_list_length (file->part_list) >= file->total_parts;
1596 /* add a file fragment to the file list, creating a new file object if necessary */
1597 static GList *add_file_fragment(GList* file_list, char* filename, char* folder_name, char* fragment_id, int fragment_size,
1598 int part_number, int part_total, time_t mod_date)
1600 nntp_file *base_file;
1602 /* don't use part 0 */
1603 if (part_number == 0) {
1607 /* get the file object associated with the filename if any */
1608 base_file = look_up_file(file_list, filename, FALSE);
1609 if (base_file == NULL) {
1610 base_file = nntp_file_new(filename, folder_name, part_total);
1611 base_file->mod_date = mod_date;
1612 file_list = g_list_append(file_list, base_file);
1615 /* add the fragment to the file */
1616 nntp_file_add_part(base_file, part_number, fragment_id, fragment_size);
1620 /* remove any partial files from the file list */
1621 static GList *remove_partial_files (GList *file_list)
1626 GList* current_file = file_list;
1628 while (current_file != NULL) {
1629 next_file = current_file->next;
1630 file = (nntp_file*) current_file->data;
1631 if (!has_all_fragments (file)) {
1632 file_list = g_list_remove_link (file_list, current_file);
1633 nntp_file_destroy (file);
1635 current_file = next_file;
1641 /* loop through the file list to update the size fields */
1643 update_file_sizes(GList *file_list)
1646 GList* current_file = file_list;
1648 while (current_file != NULL) {
1649 file = (nntp_file*) current_file->data;
1650 file->file_size = nntp_file_get_total_size(file);
1651 current_file = current_file->next;
1655 /* the following set of routines are used to group the files into psuedo-directories by using
1656 * a hash table containing lists of nntp_file objects.
1659 /* add the passed in file's foldername to the hash table */
1661 add_file_to_folder (GHashTable *folders, nntp_file *file)
1663 GList *folder_contents;
1665 if (file->folder_name == NULL) {
1669 folder_contents = g_hash_table_lookup (folders, file->folder_name);
1670 if (folder_contents != NULL) {
1671 g_list_append(folder_contents, file);
1673 folder_contents = g_list_append(NULL, file);
1674 g_hash_table_insert (folders, g_strdup(file->folder_name), folder_contents);
1678 /* remove non-singleton files that are contained in folders from the file list */
1680 remove_file_from_list (gpointer key, gpointer value, gpointer callback_data)
1682 GList *element_list;
1684 GList** file_list_ptr;
1686 file_list_ptr = (GList**) callback_data;
1687 element_list = (GList*) value;
1689 /* if there is more than one element in the list, remove all of them from the file list */
1690 if (element_list != NULL && g_list_length(element_list) > 1) {
1691 while (element_list != NULL) {
1692 file = (nntp_file*) element_list->data;
1693 *file_list_ptr = g_list_remove(*file_list_ptr, element_list->data);
1694 element_list = element_list->next;
1700 remove_contained_files(GHashTable *folders, GList** file_list_ptr)
1702 /* iterate through the passed in hash table to find the files to remove */
1703 g_hash_table_foreach (folders, remove_file_from_list, file_list_ptr);
1706 /* utility routine to calculate a folder's mod-date by inspecting the dates of it's constituents */
1708 calculate_folder_mod_date (nntp_file *folder)
1710 time_t latest_mod_date;
1711 GList *current_file;
1712 nntp_file* current_file_data;
1714 latest_mod_date = 0;
1715 current_file = folder->part_list;
1716 while (current_file != NULL) {
1717 current_file_data = (nntp_file*) current_file->data;
1718 if (current_file_data->mod_date > latest_mod_date) {
1719 latest_mod_date = current_file_data->mod_date;
1721 current_file = current_file->next;
1723 folder->mod_date = latest_mod_date;
1726 /* generate folder objects from the entries in the hash table */
1728 generate_folder_from_element (gpointer key, gpointer value, gpointer callback_data)
1730 GList *element_list;
1731 int number_of_elements;
1732 nntp_file *new_folder;
1733 char* key_as_string;
1734 GList** file_list_ptr;
1736 file_list_ptr = (GList**) callback_data;
1737 element_list = (GList*) value;
1738 key_as_string = (char*) key;
1740 number_of_elements = g_list_length(element_list);
1741 if (number_of_elements > 1) {
1742 if (strlen(key_as_string) == 0) {
1743 key_as_string = "Unknown Title";
1746 new_folder = nntp_file_new (key_as_string, NULL, number_of_elements);
1747 new_folder->is_directory = TRUE;
1748 new_folder->file_type = g_strdup ("x-directory/normal");
1749 new_folder->part_list = g_list_copy (element_list);
1751 calculate_folder_mod_date (new_folder);
1753 *file_list_ptr = g_list_append (*file_list_ptr, new_folder);
1758 generate_folders(GHashTable *folders, GList** file_list_ptr)
1760 /* iterate through the passed in hash table to generate a folder for each non-singleton entry */
1761 g_hash_table_foreach (folders, generate_folder_from_element, file_list_ptr);
1764 /* helper routine to deallocate hash table elements */
1766 destroy_element (gpointer key, gpointer value, gpointer data)
1769 g_list_free((GList*) value);
1773 /* deallocate the data structures associate with the folder hash table */
1775 destroy_folder_hash(GHashTable *folders)
1777 /* iterate through the hash table to destroy the lists it contains, before destroying
1780 g_hash_table_foreach_remove (folders, destroy_element, NULL);
1781 g_hash_table_destroy(folders);
1784 /* walk through the file list and try to generate folders to group files with similar subjects */
1786 assemble_folders (GList *file_list)
1788 GList *current_item;
1789 nntp_file *current_file;
1790 GHashTable *folders;
1791 GList* file_list_ptr;
1793 /* need indirection for file list head */
1794 file_list_ptr = file_list;
1796 /* make a pass through the files grouping the ones with matching foldernames. Use a hash
1797 * table for fast lookups
1799 folders = g_hash_table_new (g_str_hash, g_str_equal);
1800 current_item = file_list_ptr;
1802 while (current_item != NULL) {
1803 current_file = (nntp_file*) current_item->data;
1804 if (current_file->folder_name != NULL) {
1805 add_file_to_folder (folders, current_file);
1807 current_item = current_item->next;
1810 /* make a pass through the folder list to remove contained files from the file list */
1811 remove_contained_files (folders, &file_list_ptr);
1813 /* generate folders from the hash table (not singletons) */
1814 generate_folders (folders, &file_list_ptr);
1816 /* deallocate resources now that we're done */
1817 destroy_folder_hash (folders);
1819 return file_list_ptr;
1822 /* this is the main loop for assembling the files. It issues the passed in
1823 * overview command and then reads the headers one at a time, processing the
1824 * fragments into a list of files
1827 assemble_files_from_overview (NNTPConnection *conn, char *command)
1829 GnomeVFSResult result;
1832 int part_number, total_parts;
1833 char* filename, *folder_name, *message_id;
1839 /* issue the overview command to the server */
1840 result = do_control_write (conn, command);
1842 if (result != GNOME_VFS_OK) {
1846 /* read the response command, and check that it's the right number (eventually) */
1847 result = read_response_line (conn, &line);
1850 if (result != GNOME_VFS_OK) {
1855 result = read_response_line (conn, &line);
1856 if (line[0] != '.' && line[1] != '\r') {
1857 if (parse_header (line, &filename, &folder_name, &message_id, &message_size, &part_number, &total_parts, &mod_date)) {
1858 file_list = add_file_fragment (file_list, filename, folder_name, message_id, message_size,
1859 part_number, total_parts, mod_date);
1861 g_free (folder_name);
1862 g_free (message_id);
1870 file_list = remove_partial_files (file_list);
1871 update_file_sizes (file_list);
1872 file_list = assemble_folders (file_list);
1877 /* newsgroup files aren't local */
1879 do_is_local (GnomeVFSMethod *method,
1880 const GnomeVFSURI *uri)
1886 prepare_to_read_file (NNTPConnection *conn, nntp_file *file)
1888 conn->current_file = file;
1889 conn->current_fragment = NULL;
1890 conn->buffer_offset = 0;
1891 conn->amount_in_buffer = 0;
1892 conn->eof_flag = FALSE;
1893 conn->uu_decode_mode = FALSE;
1894 conn->base_64_decode_mode = FALSE;
1896 nntp_connection_reset_buffer (conn);
1899 static GnomeVFSResult
1900 do_open (GnomeVFSMethod *method,
1901 GnomeVFSMethodHandle **method_handle,
1903 GnomeVFSOpenMode mode,
1904 GnomeVFSContext *context)
1906 GnomeVFSResult result;
1907 NNTPConnection *conn;
1909 const char* basename;
1911 /* we can save allocating a connection to search for the .directory file */
1912 basename = gnome_vfs_uri_extract_short_name(uri);
1913 if (strcmp(basename, ".directory") == 0) {
1914 return GNOME_VFS_ERROR_NOT_FOUND;
1917 result = nntp_connection_acquire (uri, &conn, context);
1918 if (result != GNOME_VFS_OK)
1921 /* make sure we have the file */
1922 file = nntp_file_from_uri(conn, uri);
1924 nntp_connection_release (conn);
1925 return GNOME_VFS_ERROR_NOT_FOUND;
1927 prepare_to_read_file (conn, file);
1930 if (result == GNOME_VFS_OK) {
1931 *method_handle = (GnomeVFSMethodHandle *) conn;
1933 *method_handle = NULL;
1934 nntp_connection_release (conn);
1939 static GnomeVFSResult
1940 do_close (GnomeVFSMethod *method,
1941 GnomeVFSMethodHandle *method_handle,
1942 GnomeVFSContext *context)
1944 NNTPConnection *conn = (NNTPConnection *) method_handle;
1946 nntp_connection_release (conn);
1948 return GNOME_VFS_OK;
1951 static GnomeVFSResult
1952 do_read (GnomeVFSMethod *method,
1953 GnomeVFSMethodHandle *method_handle,
1955 GnomeVFSFileSize num_bytes,
1956 GnomeVFSFileSize *bytes_read,
1957 GnomeVFSContext *context)
1959 NNTPConnection *conn = (NNTPConnection * )method_handle;
1960 return nntp_file_read(conn, buffer, num_bytes, bytes_read);
1963 static GnomeVFSResult
1964 do_get_file_info (GnomeVFSMethod *method,
1966 GnomeVFSFileInfo *file_info,
1967 GnomeVFSFileInfoOptions options,
1968 GnomeVFSContext *context)
1970 const char* host_name;
1971 const char* temp_str, *first_slash;
1973 GnomeVFSURI *parent = gnome_vfs_uri_get_parent (uri);
1974 GnomeVFSResult result;
1976 host_name = gnome_vfs_uri_get_host_name(uri);
1977 if (host_name == NULL) {
1978 return GNOME_VFS_ERROR_INVALID_HOST_NAME;
1981 /* if it's the top level newsgroup, treat it like a directory */
1982 temp_str = gnome_vfs_uri_get_path(uri);
1983 first_slash = strchr(temp_str + 1, '/');
1984 if (parent == NULL || first_slash == NULL) {
1985 /* this is a request for info about the root directory */
1986 file_info->name = g_strdup("/");
1987 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
1988 file_info->mime_type = g_strdup ("x-directory/normal");
1989 file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
1990 GNOME_VFS_PERM_OTHER_READ;
1991 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
1992 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE |
1993 GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS;
1995 return GNOME_VFS_OK;
1997 GnomeVFSMethodHandle *method_handle;
2000 result = do_open_directory (method, &method_handle, parent,
2002 gnome_vfs_uri_unref (parent);
2004 if (result != GNOME_VFS_OK) {
2008 name = gnome_vfs_uri_extract_short_name (uri);
2009 while (result == GNOME_VFS_OK) {
2010 result = do_read_directory (method, method_handle,
2011 file_info, context);
2012 if (result == GNOME_VFS_OK) {
2013 if (file_info->name && !strcmp (file_info->name,
2017 method, method_handle,
2019 return GNOME_VFS_OK;
2022 gnome_vfs_file_info_clear (file_info);
2025 do_close_directory(method, method_handle, context);
2028 return GNOME_VFS_ERROR_NOT_FOUND;
2031 static GnomeVFSResult
2032 do_get_file_info_from_handle (GnomeVFSMethod *method,
2033 GnomeVFSMethodHandle *method_handle,
2034 GnomeVFSFileInfo *file_info,
2035 GnomeVFSFileInfoOptions options,
2036 GnomeVFSContext *context)
2038 return do_get_file_info (method,
2039 ((NNTPConnection *)method_handle)->uri,
2040 file_info, options, context);
2043 static GnomeVFSResult
2044 do_open_directory (GnomeVFSMethod *method,
2045 GnomeVFSMethodHandle **method_handle,
2047 GnomeVFSFileInfoOptions options,
2048 GnomeVFSContext *context)
2050 const char *newsgroup_server;
2051 char *newsgroup_name;
2052 char *directory_name, *mapped_dirname;
2054 NNTPConnection *conn;
2055 GnomeVFSResult result;
2059 newsgroup_server = gnome_vfs_uri_get_host_name (uri);
2060 newsgroup_name = gnome_vfs_uri_extract_dirname (uri);
2061 directory_name = g_strdup (gnome_vfs_uri_extract_short_name (uri));
2063 if (strcmp (newsgroup_name, "/") == 0 || strlen(newsgroup_name) == 0) {
2064 g_free (newsgroup_name);
2065 newsgroup_name = directory_name;
2066 directory_name = NULL;
2069 if (newsgroup_name == NULL) {
2070 g_free(directory_name);
2071 return GNOME_VFS_ERROR_NOT_FOUND;
2074 newsgroup_name = strip_slashes (newsgroup_name);
2075 result = nntp_connection_acquire (uri, &conn, context);
2076 if (result != GNOME_VFS_OK) {
2077 g_free (newsgroup_name);
2078 g_free (directory_name);
2082 /* get the files from the newsgroup */
2083 result = get_files_from_newsgroup (conn, newsgroup_name, &file_list);
2084 if (result != GNOME_VFS_OK) {
2085 g_free (newsgroup_name);
2086 g_free (directory_name);
2087 nntp_connection_release(conn);
2091 if (directory_name == NULL) {
2092 conn->next_file = file_list;
2094 if (file_list == NULL) {
2097 mapped_dirname = gnome_vfs_unescape_string (directory_name, "");
2098 file = look_up_file (file_list, mapped_dirname, TRUE);
2099 g_free (mapped_dirname);
2103 g_message ("couldnt find file %s", directory_name);
2104 return GNOME_VFS_ERROR_NOT_FOUND;
2106 if (file->is_directory) {
2107 conn->next_file = file->part_list;
2109 conn->next_file = NULL;
2113 if (result != GNOME_VFS_OK) {
2114 g_message ("couldnt set group!");
2115 nntp_connection_release (conn);
2117 g_free (newsgroup_name);
2118 g_free (directory_name);
2121 *method_handle = (GnomeVFSMethodHandle *) conn;
2123 g_free (newsgroup_name);
2124 g_free (directory_name);
2129 static GnomeVFSResult
2130 do_close_directory (GnomeVFSMethod *method,
2131 GnomeVFSMethodHandle *method_handle,
2132 GnomeVFSContext *context)
2134 NNTPConnection *conn = (NNTPConnection *) method_handle;
2136 nntp_connection_release (conn);
2138 return GNOME_VFS_OK;
2142 static GnomeVFSResult
2143 do_read_directory (GnomeVFSMethod *method,
2144 GnomeVFSMethodHandle *method_handle,
2145 GnomeVFSFileInfo *file_info,
2146 GnomeVFSContext *context)
2148 NNTPConnection *conn = (NNTPConnection *) method_handle;
2149 nntp_file *file_data;
2150 const char* mime_string;
2152 if (!conn->next_file)
2153 return GNOME_VFS_ERROR_EOF;
2155 /* fill out the information for the current file, and bump the pointer */
2156 gnome_vfs_file_info_clear(file_info);
2158 /* implement the size threshold by skipping files smaller than the threshold */
2159 file_data = (nntp_file*) conn->next_file->data;
2160 while (file_data->file_size < MIN_FILE_SIZE_THRESHOLD && !file_data->is_directory) {
2161 conn->next_file = conn->next_file->next;
2162 if (conn->next_file == NULL) {
2163 return GNOME_VFS_ERROR_EOF;
2165 file_data = (nntp_file*) conn->next_file->data;
2169 file_info->name = g_strdup(file_data->file_name);
2171 file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
2172 GNOME_VFS_PERM_USER_WRITE |GNOME_VFS_PERM_OTHER_READ;
2174 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
2175 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE |
2176 GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS |
2177 GNOME_VFS_FILE_INFO_FIELDS_MTIME;
2179 /* handle the case when it's a directory */
2180 if (file_data->is_directory) {
2181 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
2182 file_info->mime_type = g_strdup ("x-directory/normal");
2183 file_info->mtime = file_data->mod_date;
2184 file_info->permissions = GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_GROUP_READ |
2185 GNOME_VFS_PERM_USER_WRITE |
2186 GNOME_VFS_PERM_OTHER_READ | GNOME_VFS_PERM_USER_EXEC |
2187 GNOME_VFS_PERM_GROUP_EXEC | GNOME_VFS_PERM_OTHER_EXEC;
2190 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
2192 file_info->mtime = file_data->mod_date;
2194 /* figure out the mime type from the extension; if it's unrecognized, use "text/plain"
2195 * instead of "application/octet-stream"
2197 mime_string = gnome_vfs_mime_type_from_name(file_data->file_name);
2198 if (strcmp(mime_string, "application/octet-stream") == 0) {
2199 file_info->mime_type = g_strdup("text/plain");
2201 file_info->mime_type = g_strdup(mime_string);
2204 file_info->size = file_data->file_size;
2205 file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_SIZE;
2208 conn->next_file = conn->next_file->next;
2209 return GNOME_VFS_OK;
2212 static GnomeVFSResult
2213 do_check_same_fs (GnomeVFSMethod *method,
2216 gboolean *same_fs_return,
2217 GnomeVFSContext *context)
2219 *same_fs_return = nntp_connection_uri_equal (a,b);
2220 return GNOME_VFS_OK;
2223 static GnomeVFSMethod method = {
2224 sizeof (GnomeVFSMethod),
2232 NULL, /* truncate */
2237 do_get_file_info_from_handle,
2239 NULL, /* make_directory */
2240 NULL, /* remove directory */
2244 NULL, /* set_file_info */
2245 NULL, /* truncate */
2246 NULL, /* find_directory */
2247 NULL /* create_symbolic_link */
2251 vfs_module_init (const char *method_name,
2255 char *argv[] = {"vfs-nntp-method"};
2258 /* Ensure GConf is initialized. If more modules start to rely on
2259 * GConf, then this should probably be moved into a more
2263 if (!gconf_is_initialized ()) {
2264 /* auto-initializes OAF if necessary */
2265 gconf_init (argc, argv, NULL);
2273 vfs_module_shutdown (GnomeVFSMethod *method)