netscape-revival
lib/libmsg/newsgrp.c
/* -*- Mode: C; tab-width: 4 -*-
newsgrp.c --- commands relating to the newsgroup list window
Copyright © 1997 Netscape Communications Corporation, all rights reserved.
Created: Jamie Zawinski <jwz@netscape.com>, 25-May-95.
*/
#include "msg.h"
#include "mailsum.h"
#include "newsrc.h"
#include "msgundo.h"
/* for XP_GetString() */
#include "xpgetstr.h"
extern int MK_MSG_FILE_HAS_CHANGED;
extern int MK_MSG_OPEN_NEWS_HOST;
extern int MK_MIME_ERROR_WRITING_FILE;
extern int MK_MSG_ERROR_WRITING_MAIL_FOLDER;
extern int MK_MSG_ERROR_WRITING_NEWSRC;
extern int MK_MSG_MESSAGE_CANCELLED;
extern int MK_MSG_NEWSRC_UNPARSABLE;
extern int MK_MSG_NO_NEW_GROUPS;
extern int MK_MSG_N_NEW_GROUPS;
extern int MK_MSG_ONE_NEW_GROUP;
extern int MK_NNTP_TOO_MANY_ARTS;
extern int MK_OUT_OF_MEMORY;
extern int MK_UNABLE_TO_OPEN_NEWSRC;
extern int MK_NNTP_SERVER_NOT_CONFIGURED;
extern int XP_NEWS_PROMPT_ADD_NEWSGROUP;
extern int MK_MSG_REMOVE_HOST_CONFIRM;
#define MARK_SAVE_NEWS_MESSAGES_AS_READ
static int msg_sort_news_hosts (MWContext *context);
struct msg_collect_groups_data
{
char **names;
uint32 names_size;
uint32 names_fp;
};
static int
msg_collect_groups_1 (struct msg_collect_groups_data *data, MSG_Folder *list)
{
int status = 0;
if (!list) return 0;
while (list)
{
XP_ASSERT (list->flags & MSG_FOLDER_FLAG_NEWSGROUP);
if (list->flags & MSG_FOLDER_FLAG_DIRECTORY)
{
status = msg_collect_groups_1 (data, list->children);
if (status < 0) return status;
}
else if (list->data.newsgroup.group_name &&
(list->unread_messages < 0 ||
(list->unread_messages > 0 && list->total_messages <= 0)))
/* Do a GROUP on this if the `unread' count is negative, or if
the `unread' count is non-negative, and the `total' count is
non-positive (this can happen when opening an unsubscribed
group explicitly, because of the funky ordering in that case.)
*/
{
if ((data->names_fp + 3) >= data->names_size)
{
status = msg_GrowBuffer ((data->names_fp + 3),
sizeof(*data->names), 100,
(char **) &data->names,
&data->names_size);
if (status < 0) return status;
}
data->names [data->names_fp++] = list->data.newsgroup.group_name;
}
list = list->next;
}
return 0;
}
static char **
msg_collect_groups (MSG_Folder *host)
{
int status;
struct msg_collect_groups_data data;
XP_ASSERT (host &&
(host->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(host->flags & MSG_FOLDER_FLAG_DIRECTORY));
XP_MEMSET (&data, 0, sizeof(data));
status = msg_collect_groups_1 (&data, host->children);
if (status < 0 || data.names_fp == 0)
{
if (data.names) XP_FREE (data.names);
return 0;
}
else
{
data.names [data.names_fp] = 0;
return data.names;
}
}
struct MSG_Folder *
msg_NewsHostFolder (MWContext *context, MSG_NewsHost *host)
{
struct MSG_Folder *folder;
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return 0;
for (folder = context->msgframe->folders; folder; folder = folder->next)
{
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY));
if (folder->data.newshost.host == host)
return folder;
}
return 0;
}
struct MSG_NewsHost *
msg_NewsFolderHost (MWContext *context, MSG_Folder *folder)
{
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return 0;
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY));
if (! ((folder->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY)))
return 0;
XP_ASSERT (folder->data.newshost.host);
return folder->data.newshost.host;
}
char **
MSG_GetNewsRCList (MWContext *context,
const char *host_and_port, XP_Bool secure_p)
{
struct MSG_Folder *hfolder;
struct MSG_NewsHost *host;
int status;
if (context->type != MWContextNews)
return 0;
status = msg_EnsureNewsgroupSelected (context, host_and_port, 0, secure_p);
if (status < 0) return 0;
host = context->msgframe->data.news.current_host;
XP_ASSERT (host);
if (!host) return 0;
hfolder = msg_NewsHostFolder (context, host);
XP_ASSERT (hfolder);
if (!hfolder) return 0;
return msg_collect_groups (hfolder);
}
/* The NNTP module asks the message library whether all newsgroup lines
visible on the screen have their message counts displayed, and if not,
will iterate over them to update them. If this function returns false,
then MSG_ArticleCountKnown will be called for each subscribed group.
If that returns false, then MSG_DisplaySubscribedGroup will be called
soon after (as soon as we get a response from the news server.)
*/
XP_Bool
MSG_ArticleCountsKnown (MWContext *context, const char *host)
{
/* #### optimize this */
FE_EnableClicking (context);
return FALSE;
}
void
MSG_NewsGroupAndNumberOfID (MWContext *context, const char *message_id,
const char **group, uint32 *message_number)
{
struct MSG_ThreadEntry *msg;
char *id, *id2;
*group = 0;
*message_number = 0;
if (!message_id) return;
/* If they gave us a non-news context, go find one - this is so that
we get the same behavior when retrieving articles for the message
composition window as for the news window. Since this function
only looks in tables on the context and doesn't actually access
the network, this is ok.
*/
if (!context || context->type != MWContextNews)
context = XP_FindContextOfType (context, MWContextNews);
if (!context || context->type != MWContextNews)
return;
if (!context->msgframe->msgs ||
!context->msgframe->msgs->msgs ||
!context->msgframe->opened_folder)
return;
if (! (context->msgframe->opened_folder->flags & MSG_FOLDER_FLAG_NEWSGROUP))
return;
id = XP_STRDUP (message_id);
if (! id) return;
id2 = id;
if (*id2 == '<') id2++;
if (id2[XP_STRLEN(id2)-1] == '>')
id2[XP_STRLEN(id2)-1] = 0;
msg = (struct MSG_ThreadEntry *)
XP_Gethash (context->msgframe->msgs->message_id_table, id2, 0);
if (msg)
{
*group = context->msgframe->opened_folder->data.newsgroup.group_name;
*message_number = msg->data.news.article_number;
}
XP_FREE (id);
}
int
msg_ShowNewsGroups (MWContext *context,
MSG_NEWSGROUP_DISPLAY_STYLE group_display_type)
{
int status = 0;
struct MSG_Folder *hfolder;
XP_ASSERT (context && context->msgframe && context->type == MWContextNews);
if (!context || !context->msgframe || context->type != MWContextNews)
return -1;
if (context->msgframe->data.news.group_display_type == group_display_type)
return 0;
if (!context->msgframe->data.news.current_host)
/* no host currently selected */
return 0;
context->msgframe->max_depth = 0;
hfolder = msg_NewsHostFolder (context,
context->msgframe->data.news.current_host);
XP_ASSERT (!hfolder || (hfolder->flags & MSG_FOLDER_FLAG_NEWS_HOST));
/* close... */
if (hfolder && !(hfolder->flags & MSG_FOLDER_FLAG_ELIDED))
status = msg_ToggleFolderExpansion (context, hfolder, FALSE);
if (status < 0) return status;
context->msgframe->data.news.group_display_type = group_display_type;
/* then reopen... */
if (hfolder)
status = msg_ToggleFolderExpansion (context, hfolder, TRUE);
return status;
}
static void msg_new_groups_listed (URL_Struct *url, int status,
MWContext *context);
int
msg_CheckNewNewsGroups (MWContext *context)
{
int status = 0;
struct MSG_NewsHost *host;
char *url;
URL_Struct *url_struct;
XP_ASSERT (context && context->msgframe && context->type == MWContextNews);
if (!context || !context->msgframe || context->type != MWContextNews)
return -1;
host = context->msgframe->data.news.current_host;
if (!host) return 0;
/* If we're in "show all groups" mode, switch to "show subscribed groups"
mode. */
if (context->msgframe->data.news.group_display_type == MSG_ShowAll)
{
struct MSG_Folder *folder;
folder = msg_NewsHostFolder (context, host);
XP_ASSERT (folder);
if (folder)
{
/* Turn off "show all" mode; the other two are ok. */
if (context->msgframe->data.news.group_display_type
== MSG_ShowAll)
context->msgframe->data.news.group_display_type =
MSG_ShowSubscribedWithArticles;
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY));
/* Close it, then reopen it. */
if (!(folder->flags & MSG_FOLDER_FLAG_ELIDED))
status = msg_ToggleFolderExpansion (context, folder, FALSE);
if (status < 0) return status;
status = msg_ToggleFolderExpansion (context, folder, FALSE);
if (status < 0) return status;
}
}
url = msg_MakeNewsURL (context, host, "/?newgroups");
if (!url) return MK_OUT_OF_MEMORY;
url_struct = NET_CreateURLStruct (url, NET_DONT_RELOAD);
XP_FREE (url);
if (!url_struct) return MK_OUT_OF_MEMORY;
url_struct->pre_exit_fn = msg_new_groups_listed;
msg_GetURL (context, url_struct, FALSE);
/* Scroll to the end of the list, which is where the new groups are
going to show up. */
{
MSG_Folder *hfolder = msg_NewsHostFolder (context, host);
MSG_Folder *last;
for (last = hfolder->children; last && last->next; last = last->next)
;
if (last)
msg_RedisplayOneFolder (context, last, -1, -1, TRUE);
}
return 0;
}
static void
msg_remove_news_host_files (MWContext *context, MSG_NewsHost *host)
{
MSG_Folder* folder;
XP_ASSERT(context->type == MWContextNews && context->msgframe);
if (context->type != MWContextNews || !context->msgframe) return;
for (folder = context->msgframe->folders; folder; folder = folder->next)
{
MSG_NewsHost *host2;
XP_ASSERT(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST);
if (!(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST)) continue;
host2 = folder->data.newshost.host;
/* Skip over the folder of the target host. */
if (!host2 || host == host2)
continue;
/* If these two hosts share the same newsrc file, don't delete it! */
if (host->secure_p == host2->secure_p &&
!XP_FILENAMECMP (host->newsrc_file_name,
host2->newsrc_file_name))
return;
/* If these two hosts share the same newsgroups file, don't delete it! */
if (host->secure_p == host2->secure_p &&
!XP_FILENAMECMP (host->newsrc_file_name,
host2->newsrc_file_name))
return;
}
/* We made it through the list of all hosts without finding an overlap
among the files we're about to delete; so nuke them.
*/
XP_FileRemove(host->newsrc_file_name,
(host->secure_p ? xpSNewsRC : xpNewsRC));
XP_FileRemove(host->newsrc_file_name,
(host->secure_p ? xpSNewsgroups : xpNewsgroups));
}
int
msg_RemoveNewsHost (MWContext *context)
{
XP_Bool confirm_p;
struct MSG_NewsHost *host;
struct MSG_NewsHost **h;
MSG_Folder* folder;
MSG_Folder* prev;
char* buf;
XP_ASSERT(context->type == MWContextNews && context->msgframe);
if (context->type != MWContextNews || !context->msgframe) return -1;
RESTART:
prev = NULL;
for (folder = context->msgframe->folders;
folder;
prev = folder, folder = folder->next) {
if (!(folder->flags & MSG_FOLDER_FLAG_SELECTED)) continue;
XP_ASSERT(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST);
if (!(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST)) continue;
host = folder->data.newshost.host;
XP_ASSERT(host);
if (!host) continue;
buf = PR_smprintf(XP_GetString(MK_MSG_REMOVE_HOST_CONFIRM), host->name);
if (!buf) return MK_OUT_OF_MEMORY;
confirm_p = FE_Confirm(context, buf);
XP_FREE(buf);
if (!confirm_p) return 0;
msg_DisableUpdates(context);
msg_remove_news_host_files (context, host);
if (!(folder->flags & MSG_FOLDER_FLAG_ELIDED)) {
msg_ToggleFolderExpansion(context, folder, FALSE);
}
if (prev) prev->next = folder->next;
else context->msgframe->folders = folder->next;
folder->next = NULL;
XP_ASSERT(folder->children == NULL); /* If this fails, then we have
a memory leak... */
context->msgframe->num_selected_folders--;
msg_FreeOneFolder(context, folder);
for (h = &(context->msgframe->data.news.hosts) ; *h ; h = &((*h)->next)) {
if (*h == host) {
*h = host->next;
FREEIF(host->name);
FREEIF(host->newsrc_file_name);
XP_FREE(host);
host = NULL;
break;
}
}
XP_ASSERT(host == NULL); /* If this fails, we never found it, which is
scary. */
msg_RedisplayFolders(context);
msg_EnableUpdates(context);
goto RESTART; /* Need to go back to the beginning; otherwise,
if we have multiple selected, we can skip
some as a side effect of having deleted
one. */
}
return 0;
}
/* Constructs a canonical "news:" or "snews:" URL for the given host, being
careful to let the host name and port be unspecified defaults when
appropriate. `suffix' may be 0, a group name, or a message ID.
*/
char *
msg_MakeNewsURL (MWContext *context,
struct MSG_NewsHost *host, const char *suffix)
{
char *url;
const char *def;
const char *name;
const char *prefix;
uint32 port;
int length;
XP_ASSERT (host);
if (!host) return 0;
def = NET_GetNewsHost (context, FALSE);
name = host->name;
port = host->port;
XP_ASSERT (port != 0);
if (port == (uint32) (host->secure_p ? SECURE_NEWS_PORT : NEWS_PORT))
port = 0;
if (!port && def && !XP_STRCMP (def, name))
name = 0;
prefix = (host->secure_p ? "snews:" : "news:");
XP_ASSERT (name || port == 0);
if (!host && !suffix)
return (XP_STRDUP (prefix));
length =
(name ? XP_STRLEN(name) : 0) + (suffix ? XP_STRLEN(suffix) : 0) + 30;
url = (char *) XP_ALLOC (length);
if (!url) return 0;
XP_STRCPY (url, prefix);
if (name)
{
XP_STRCAT (url, "//");
XP_STRCAT (url, name);
if (port)
PR_snprintf (url + XP_STRLEN (url), length - XP_STRLEN(url),
":%lu", (unsigned long) port);
XP_STRCAT (url, "/");
}
if (suffix)
XP_STRCAT (url, suffix);
return url;
}
static void
msg_check_newsrc_file_date (MWContext *context)
{
XP_StatStruct st;
struct MSG_NewsHost *host;
XP_ASSERT (context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return;
if (!context->msgframe->data.news.newsrc_file ||
!context->msgframe->data.news.newsrc_file_date)
return;
host = context->msgframe->data.news.current_host;
XP_ASSERT (host);
if (!host) return;
if (!XP_Stat (host->newsrc_file_name, &st,
(host->secure_p ? xpSNewsRC : xpNewsRC)) &&
st.st_mtime > 0 &&
st.st_mtime != context->msgframe->data.news.newsrc_file_date)
{
XP_Bool discard_p = FALSE;
if (!context->msgframe->data.news.newsrc_changed_timer)
{
/* It's always ok to just reload it from disk if there were no
unsaved modifications in memory. */
discard_p = TRUE;
}
else
{
/* But if there were changes, ask first.
*/
char *buf;
char *name = WH_FileName (host->newsrc_file_name,
host->secure_p ? xpSNewsRC : xpNewsRC);
buf = PR_smprintf(
XP_GetString(MK_MSG_FILE_HAS_CHANGED),
(name ? name : "?"));
FREEIF(name);
if (!buf) return;
discard_p = !FE_Confirm (context, buf);
XP_FREE(buf);
}
if (discard_p)
{
/* They don't want us to overwrite it - so discard our changes.
We will reload the file the next time we are interested in it.
*/
msg_FreeNewsRCFile (context->msgframe->data.news.newsrc_file);
context->msgframe->data.news.newsrc_file = 0;
context->msgframe->data.news.newsrc_file_date = 0;
if (context->msgframe->data.news.newsrc_changed_timer) {
FE_ClearTimeout(context->msgframe->data.news.newsrc_changed_timer);
context->msgframe->data.news.newsrc_changed_timer = NULL;
}
}
}
}
static int
msg_save_newsrc_file(MWContext* context)
{
int status = 0;
XP_File out;
struct MSG_NewsHost *host;
host = context->msgframe->data.news.current_host;
XP_ASSERT (host);
if (!host) return -1;
msg_check_newsrc_file_date (context);
if (!context->msgframe->data.news.newsrc_file)
return 0;
out = XP_FileOpen (host->newsrc_file_name, xpTemporaryNewsRC,
XP_FILE_WRITE_BIN);
if (!out)
return MK_UNABLE_TO_OPEN_NEWSRC;
/* #### We should check here that the newsrc file is writable, and warn.
(open it for append, then close it right away?) Just checking
S_IWUSR is not enough.
*/
#ifdef XP_UNIX
/* Clone the permissions of the "real" newsrc file into the temp file,
so that when we rename the finished temp file to the real file, the
preferences don't appear to change. */
{
XP_StatStruct st;
if (XP_Stat (host->newsrc_file_name, &st,
host->secure_p ? xpSNewsRC : xpNewsRC)
== 0)
/* Ignore errors; if it fails, bummer. */
fchmod (fileno(out), (st.st_mode | S_IRUSR | S_IWUSR));
}
#endif /* XP_UNIX */
status = msg_WriteNewsRCFile (context,
context->msgframe->data.news.newsrc_file,
out);
if (XP_FileClose(out) != 0) {
if (status >= 0) status = MK_MSG_ERROR_WRITING_NEWSRC;
}
if (status < 0)
{
XP_FileRemove (host->newsrc_file_name, xpTemporaryNewsRC);
return status;
}
/* We just wrote to ~/.newsrc-HOST:PORT.tmp
now rename it to ~/.newsrc-HOST:PORT
*/
if (XP_FileRename (host->newsrc_file_name, xpTemporaryNewsRC,
host->newsrc_file_name,
host->secure_p ? xpSNewsRC : xpNewsRC)
< 0)
{
XP_FileRemove (host->newsrc_file_name, xpTemporaryNewsRC);
status = MK_MSG_ERROR_WRITING_NEWSRC;
}
context->msgframe->data.news.newsrc_file_date = 0;
if (status >= 0)
{
/* Successfully wrote and renamed the file.
Notice what it's current write-date is. */
XP_StatStruct st;
if (!XP_Stat (host->newsrc_file_name, &st,
(host->secure_p ? xpSNewsRC : xpNewsRC)))
context->msgframe->data.news.newsrc_file_date = st.st_mtime;
else
/* couldn't stat the file we just wrote?? */
XP_ASSERT (0);
}
return status;
}
int
msg_SaveNewsRCFile (MWContext *context)
{
XP_ASSERT (context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return 0;
if (!context->msgframe->data.news.newsrc_changed_timer ||
!context->msgframe->data.news.newsrc_file)
return 0;
FE_ClearTimeout(context->msgframe->data.news.newsrc_changed_timer);
context->msgframe->data.news.newsrc_changed_timer = NULL;
return msg_save_newsrc_file(context);
}
static void
msg_newsrc_timer(void* closure)
{
MWContext* context = (MWContext*) closure;
int status;
XP_ASSERT (context->type == MWContextNews && context->msgframe);
if (!context->msgframe || context->type != MWContextNews)
return;
context->msgframe->data.news.newsrc_changed_timer = NULL;
status = msg_save_newsrc_file(context);
if (status < 0) {
FE_Alert(context, XP_GetString(status));
}
}
void
msg_NoteNewsrcChanged(MWContext* context)
{
XP_ASSERT (context->type == MWContextNews && context->msgframe);
if (!context->msgframe || context->type != MWContextNews)
return;
if (context->msgframe->data.news.newsrc_changed_timer) {
FE_ClearTimeout(context->msgframe->data.news.newsrc_changed_timer);
}
context->msgframe->data.news.newsrc_changed_timer =
FE_SetTimeout(msg_newsrc_timer, context, 15 * 60 * 1000); /* ### Hard-coding. */
}
/* The newsgroup list window
(For now the message list window is in mailsum.c)
*/
static MSG_Folder *
msg_make_newsgroup_folder (const char *prefix, const char *suffix,
int32 descendant_group_count)
{
char *name;
MSG_Folder *f;
char name_suffix [50];
XP_ASSERT ((prefix && *prefix) || (suffix && *suffix));
name = (char *) XP_ALLOC ((prefix ? XP_STRLEN(prefix) : 0) +
(suffix ? XP_STRLEN(suffix) : 0) + 3);
if (!name) return 0;
f = XP_NEW (MSG_Folder);
if (!f)
{
XP_FREE (name);
return 0;
}
XP_MEMSET (f, 0, sizeof(*f));
f->flags |= MSG_FOLDER_FLAG_NEWSGROUP;
if (descendant_group_count > 0)
{
f->flags |= (MSG_FOLDER_FLAG_DIRECTORY|MSG_FOLDER_FLAG_ELIDED);
PR_snprintf (name_suffix, sizeof(name_suffix),
".* (%lu groups)", (unsigned long) descendant_group_count);
}
else
{
f->unread_messages = -1;
*name_suffix = 0;
}
*name = 0;
if (prefix && *prefix)
XP_STRCPY (name, prefix);
if (prefix && *prefix && suffix && *suffix && name[XP_STRLEN(name)-1] != '.')
XP_STRCAT (name, ".");
if (suffix && *suffix == '.')
suffix++;
if (suffix)
XP_STRCAT (name, suffix);
f->data.newsgroup.group_name = name;
f->name = (char *) XP_ALLOC (XP_STRLEN (name) + XP_STRLEN (name_suffix) + 1);
if (!f->name)
{
XP_FREE (f->data.newsgroup.group_name);
XP_FREE (f);
return 0;
}
XP_STRCPY (f->name, name);
if (*name_suffix)
XP_STRCAT (f->name, name_suffix);
return f;
}
static struct MSG_Folder *
msg_push_newsgroup_line (MWContext* context,
const char *parent_name,
const char *child_name,
int32 descendant_group_count,
void *closure)
{
struct MSG_Folder ***tail_ptrP = (struct MSG_Folder ***) closure;
struct MSG_Folder *f;
f = msg_make_newsgroup_folder (parent_name, child_name,
descendant_group_count);
if (!f) return 0;
if (context->msgframe->data.news.newsrc_file)
{
msg_NewsRCSet *set =
msg_GetNewsRCSet (context, f->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
if (set && msg_GetSubscribed(set))
f->flags |= MSG_FOLDER_FLAG_SUBSCRIBED;
}
/* #### MSG_FOLDER_FLAG_MODERATED */
**tail_ptrP = f;
*tail_ptrP = &f->next;
return f;
}
static int
msg_show_all_newsgroups_mapper (MWContext *context,
const char *parent_name,
const char *child_name,
XP_Bool group_p,
int32 descendant_group_count,
void *closure)
{
struct MSG_Folder *f;
XP_ASSERT (group_p || descendant_group_count > 0);
if (group_p)
{
f = msg_push_newsgroup_line (context, parent_name, child_name, 0,
closure);
if (!f) return MK_OUT_OF_MEMORY;
}
if (descendant_group_count > 0)
{
f = msg_push_newsgroup_line (context, parent_name, child_name,
descendant_group_count, closure);
if (!f) return MK_OUT_OF_MEMORY;
}
return 0;
}
static int
msg_show_subscribed_newsgroups_mapper (MWContext *context,
const char *group_name,
void *closure)
{
struct MSG_Folder ***tail_ptrP = (struct MSG_Folder ***) closure;
struct MSG_Folder *f = msg_make_newsgroup_folder (0, group_name, 0);
if (!f) return MK_OUT_OF_MEMORY;
f->flags |= MSG_FOLDER_FLAG_SUBSCRIBED;
/* #### MSG_FOLDER_FLAG_MODERATED */
**tail_ptrP = f;
*tail_ptrP = &f->next;
return 0;
}
static int
msg_compute_news_folder_name (MWContext *context, struct MSG_Folder *folder)
{
const char *def = NET_GetNewsHost (context, FALSE);
struct MSG_NewsHost *host;
const char *suffix;
char *new_name;
uint32 port;
int length;
XP_ASSERT (context->msgframe && context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return -1;
XP_ASSERT (folder && (folder->flags & MSG_FOLDER_FLAG_NEWS_HOST));
if (!folder || !(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST)) return -1;
host = msg_NewsFolderHost (context, folder);
if(!host) return -1;
port = host->port;
XP_ASSERT (port != 0);
if (port == (uint32) (host->secure_p ? SECURE_NEWS_PORT : NEWS_PORT))
port = 0;
if (def && port == 0 && !XP_STRCMP (host->name, def))
{
if (host->secure_p)
suffix = " (default news host; secure)";
else
suffix = " (default news host)";
}
else if (host->secure_p)
suffix = " (secure)";
else
suffix = "";
length = XP_STRLEN (host->name) + XP_STRLEN (suffix) + 20;
new_name = (char *) XP_ALLOC (length);
if (!new_name) return MK_OUT_OF_MEMORY;
XP_STRCPY (new_name, host->name);
if (port)
PR_snprintf (new_name + XP_STRLEN (new_name), length - XP_STRLEN(new_name),
":%lu", (unsigned long) port);
XP_STRCAT (new_name, suffix);
FREEIF (folder->name);
folder->name = new_name;
return 0;
}
struct MSG_Folder *
msg_make_news_folder_for_host (MWContext *context, struct MSG_NewsHost *host)
{
struct MSG_Folder *folder = 0;
int status;
XP_ASSERT (host && host->name && host->newsrc_file_name);
if (!host || !host->name || !host->newsrc_file_name)
return 0;
folder = XP_NEW (MSG_Folder);
if (!folder) return 0;
XP_MEMSET (folder, 0, sizeof(*folder));
folder->flags = (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED);
folder->data.newshost.host = host;
status = msg_compute_news_folder_name (context, folder);
if (status < 0) return 0;
return folder;
}
static struct MSG_NewsHost *
msg_make_host_and_folder_for_file (MWContext *context, const char *file,
const char *default_host);
/* Show all of the root-level groups on the given news host.
The host may be a host name, or the name of a newsrc file
(sans directory.)
*/
int
msg_ShowNewsHosts (MWContext *context)
{
/* If the "default news host" isn't set in prefs, then NET_GetNewsHost()
will pop up a warning dialog, then return NULL. */
const char *def = NET_GetNewsHost (context, TRUE);
char **newsrcs = XP_GetNewsRCFiles();
char **rest = newsrcs;
int status = 0;
XP_Bool got_default_p = FALSE;
/* Make entries in the folder list for each news host.
If a host is there already, don't lose.
If other hosts are there already, still don't lose.
*/
while (rest && *rest)
{
struct MSG_NewsHost *h;
#ifndef NDEBUG
{
/* Some versions of XP_ASSERT choke on embedded strings. Sigh. */
XP_Bool newsrc_name_ok = (!XP_STRCMP(*rest, "newsrc") ||
!XP_STRCMP(*rest, "snewsrc") ||
!XP_STRNCMP(*rest, "newsrc-", 7) ||
!XP_STRNCMP(*rest, "snewsrc-", 8));
XP_ASSERT(newsrc_name_ok);
}
#endif
if (! (!XP_STRCMP(*rest, "newsrc") ||
!XP_STRCMP(*rest, "snewsrc") ||
!XP_STRNCMP(*rest, "newsrc-", 7) ||
!XP_STRNCMP(*rest, "snewsrc-", 8)))
{
rest++;
continue;
}
h = msg_make_host_and_folder_for_file (context, *rest, def);
if (!h)
{
status = MK_OUT_OF_MEMORY;
goto FAIL;
}
if (!got_default_p)
got_default_p = (def &&
h->name &&
!h->secure_p &&
h->port == NEWS_PORT &&
!strcasecomp (def, h->name));
rest++;
}
/* If the default news host is configured, but it wasn't in the list of
newsrc files (there was no ".newsrc-HOST" for it, and no ".newsrc"),
add it to the list.
*/
if (def && !got_default_p)
{
struct MSG_NewsHost *h =
msg_make_host_and_folder_for_file (context, "newsrc", def);
if (! h)
status = MK_OUT_OF_MEMORY;
}
FAIL:
if (newsrcs)
{
for (rest = newsrcs; *rest; rest++)
XP_FREE (*rest);
XP_FREE (newsrcs);
}
if (status < 0)
msg_sort_news_hosts (context);
else
status = msg_sort_news_hosts (context);
return status;
}
static struct MSG_NewsHost *
msg_make_host_and_folder_for_file (MWContext *context, const char *file,
const char *default_host)
{
char *name;
uint32 port = 0;
XP_Bool secure_p;
int prefix_length;
struct MSG_NewsHost *h;
struct MSG_Folder *f;
name = XP_STRCHR (file, '-');
prefix_length = (name ? (name - file + 1) : XP_STRLEN (file));
if (name)
{
char *port_string;
name++;
name = XP_STRDUP (name);
if (!name)
return 0;
port_string = XP_STRCHR (name, ':');
if (port_string)
{
char dummy;
int n;
unsigned long pl = 0;
name [port_string - name] = 0;
port_string++;
port = 0;
n = sscanf (port_string, "%lu%c", &pl, &dummy);
port = pl;
XP_ASSERT (n == 1);
XP_ASSERT (port != 0);
}
}
else
{
if (!default_host)
{
/* no default news host configured, so no host to map
the file ".newsrc" to... ignore it until one shows up.
*/
return 0;
}
name = XP_STRDUP (default_host);
if (!name)
return 0;
port = 0;
}
secure_p = (*file == 's' || *file == 'S');
if (port == 0)
port = (secure_p ? SECURE_NEWS_PORT : NEWS_PORT);
/* Look for an existing news host that matches this.
*/
for (h = context->msgframe->data.news.hosts; h; h = h->next)
if (h->port == port &&
h->secure_p == secure_p &&
!XP_STRCMP (h->name, name))
break;
/* Create one if it isn't there.
*/
if (h)
{
XP_FREE (name);
name = 0;
}
else
{
h = XP_NEW (struct MSG_NewsHost);
if (! h)
{
XP_FREE (name);
name = 0;
return 0;
}
XP_MEMSET (h, 0, sizeof(*h));
h->name = name;
h->port = port;
h->secure_p = secure_p;
h->next = context->msgframe->data.news.hosts;
context->msgframe->data.news.hosts = h;
}
/* If there was a host already, there's probably a folder. */
if (h->newsrc_file_name)
f = msg_NewsHostFolder (context, h);
else
f = 0; /* otherwise, we just created the host. */
/* Store the (possibly new) newsrc file name into this host.
*/
FREEIF (h->newsrc_file_name);
h->newsrc_file_name = XP_STRDUP (file + prefix_length);
if (!f)
{
f = msg_make_news_folder_for_host (context, h);
if (!f)
return 0;
f->next = context->msgframe->folders;
context->msgframe->folders = f;
}
return h;
}
static int
msg_update_article_counts (MWContext *context, struct MSG_NewsHost *host)
{
char *newsrc_url;
URL_Struct *url;
XP_ASSERT (host);
if (!host) return -1;
newsrc_url = msg_MakeNewsURL (context, host, 0);
if (!newsrc_url) return MK_OUT_OF_MEMORY;
url = NET_CreateURLStruct (newsrc_url, NET_NORMAL_RELOAD);
XP_FREE (newsrc_url);
if (!url) return MK_OUT_OF_MEMORY;
url->internal_url = TRUE;
msg_GetURL (context, url, TRUE);
return 0;
}
static void msg_newsgroup_list_read (URL_Struct *url, int status,
MWContext *context);
int
msg_ShowNewsgroupsOfFolder (MWContext *context, MSG_Folder *folder,
XP_Bool update_article_counts_p)
{
int status = 0;
struct MSG_Folder **tail_ptr;
struct MSG_NewsHost *host;
XP_ASSERT (context->type == MWContextNews &&
folder &&
!folder->children &&
(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_NEWSGROUP)) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY) &&
(folder->flags & MSG_FOLDER_FLAG_ELIDED));
if (! (context->type == MWContextNews &&
folder &&
!folder->children &&
(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_NEWSGROUP)) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY) &&
(folder->flags & MSG_FOLDER_FLAG_ELIDED)))
return -1;
tail_ptr = &folder->children;
if (folder->flags & MSG_FOLDER_FLAG_NEWS_HOST)
{
/* Opening a new news host - figure out which one. */
struct MSG_NewsHost *host;
status = msg_SaveNewsRCFile (context);
if (status < 0) return status;
if (context->msgframe->data.news.newsrc_file)
{
msg_FreeNewsRCFile (context->msgframe->data.news.newsrc_file);
context->msgframe->data.news.newsrc_file = 0;
context->msgframe->data.news.newsrc_file_date = 0;
if (context->msgframe->data.news.newsrc_changed_timer) {
FE_ClearTimeout(context->msgframe->data.news.newsrc_changed_timer);
context->msgframe->data.news.newsrc_changed_timer = NULL;
}
}
host = msg_NewsFolderHost (context, folder);
XP_ASSERT (host);
context->msgframe->data.news.current_host = host;
}
host = context->msgframe->data.news.current_host;
XP_ASSERT (host && host->newsrc_file_name);
if (!context->msgframe->data.news.newsrc_file)
{
XP_File file = XP_FileOpen (host->newsrc_file_name,
(host->secure_p ? xpSNewsRC : xpNewsRC),
XP_FILE_READ_BIN);
if (file)
{
XP_StatStruct st;
context->msgframe->data.news.newsrc_file_date = st.st_mtime = 0;
XP_Stat (host->newsrc_file_name, &st,
(host->secure_p ? xpSNewsRC : xpNewsRC));
context->msgframe->data.news.newsrc_file =
msg_ReadNewsRCFile (context, file);
XP_FileClose (file);
if (!context->msgframe->data.news.newsrc_file)
return MK_MSG_NEWSRC_UNPARSABLE;
context->msgframe->data.news.newsrc_file_date = st.st_mtime;
}
else
{
/* We are opening a host which has no newsrc file.
If this is the default host, then we want to subscribe the
user to some default groups, and show them right away. To
do this, we need to create some dummy newsrc file data (but
don't need to create a newsrc file right away.)
If this is not the default host, then there's a reasonable
chance that it doesn't even have a full news feed, so we have
no way of knowing what groups are there. In that case, why
don't we just switch to "all groups" mode... This will download
the active file as the first action, giving the user that annoying
"this might take a while" dialog. Sigh.
*/
const char *def = NET_GetNewsHost (context, FALSE);
context->msgframe->data.news.newsrc_file_date = 0;
if (def && host->port == NEWS_PORT && !XP_STRCMP (def, host->name))
{
context->msgframe->data.news.newsrc_file =
msg_MakeDefaultNewsRCFileData (context, TRUE);
}
else
{
/* it's a non-default host with no newsrc. */
context->msgframe->data.news.group_display_type = MSG_ShowAll;
context->msgframe->data.news.newsrc_file =
msg_MakeDefaultNewsRCFileData (context, FALSE);
}
}
}
if (context->msgframe->data.news.group_display_type != MSG_ShowAll)
{
/* Populate the window with the contents of the newsrc file. */
status = msg_MapNewsRCGroupNames (context,
msg_show_subscribed_newsgroups_mapper,
(void *) &tail_ptr);
}
else
{
char* newsrc_file_name = host->newsrc_file_name;
time_t t;
if (newsrc_file_name == NULL || *newsrc_file_name == '\0') {
newsrc_file_name = (char*) NET_GetNewsHost(context, FALSE);
}
t = NET_NewsgroupsLastUpdatedTime (newsrc_file_name, host->secure_p);
if (!t && update_article_counts_p)
{
char *url = msg_MakeNewsURL (context, host, "*");
URL_Struct *url_struct;
if (!url) return MK_OUT_OF_MEMORY;
url_struct = NET_CreateURLStruct (url, NET_DONT_RELOAD);
XP_FREE (url);
if (!url_struct) return MK_OUT_OF_MEMORY;
url_struct->pre_exit_fn = msg_newsgroup_list_read;
status = msg_GetURL (context, url_struct, FALSE);
/* Downloading the list will call into us again to do this. */
update_article_counts_p = FALSE;
}
else if (t)
{
/* Populate the window with the root of the news hierarchy. */
status =
msg_MapNewsgroupNames (context,
newsrc_file_name,
host->secure_p,
((folder->flags & MSG_FOLDER_FLAG_NEWS_HOST)
? 0
: folder->data.newsgroup.group_name),
msg_show_all_newsgroups_mapper,
(void *) &tail_ptr);
}
/* Else, we don't have the newsgroup list on disk (t == 0) and we are
being called with update_article_counts_p == FALSE, meaning we can't
open a URL to go get them. So... we leave it in "show all groups"
mode, but with no groups showing (or at most, the one group which
was present in the URL if it was in "news://host/group" form.
*/
}
if (status < 0) return status;
/* Now go and fill in the article counts by talking to the server. */
if (update_article_counts_p)
status = msg_update_article_counts (context, host);
folder->flags &= (~MSG_FOLDER_FLAG_ELIDED);
return status;
}
/* Called as the pre-exit routine of the URL we loaded to download the
active file. Now that we have the active file, we can re-invoke
msg_ShowNewsgroupsOfFolder() and it will actually work...
*/
static void
msg_newsgroup_list_read (URL_Struct *url, int status, MWContext *context)
{
struct MSG_Folder *folder;
if (status < 0) return;
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return;
XP_ASSERT (context->msgframe->data.news.current_host);
if (!context->msgframe->data.news.current_host) return;
/* #### Assumes the url points at the current_host */
folder = msg_NewsHostFolder (context,
context->msgframe->data.news.current_host);
XP_ASSERT (folder &&
(folder->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
(folder->flags & MSG_FOLDER_FLAG_DIRECTORY));
if (!folder) return;
XP_ASSERT (!folder->children);
if (folder->children) return;
folder->flags |= MSG_FOLDER_FLAG_ELIDED;
msg_ShowNewsgroupsOfFolder (context, folder, TRUE);
msg_RedisplayFolders (context);
}
static struct MSG_Folder *
msg_find_newsgroup_folder_1 (const char *group, MSG_Folder *list)
{
if (!list) return 0;
while (list)
{
if (list->flags & MSG_FOLDER_FLAG_DIRECTORY)
{
struct MSG_Folder *f =
msg_find_newsgroup_folder_1 (group, list->children);
if (f) return f;
}
else if (!XP_STRCMP (group, list->data.newsgroup.group_name))
return list;
list = list->next;
}
return 0;
}
struct MSG_Folder *
msg_FindNewsgroupFolder (MWContext *context, const char *group)
{
struct MSG_Folder *folder;
XP_ASSERT (context->msgframe &&
context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
{
XP_ASSERT (0); /* can only open newsrc: urls in a news window */
return 0;
}
if (!context->msgframe->data.news.current_host)
return 0;
folder = msg_NewsHostFolder (context,
context->msgframe->data.news.current_host);
XP_ASSERT (folder);
if (! folder) return 0;
/* Find the folder line for this group. */
/* #### This could maybe stand to be more efficient. */
return msg_find_newsgroup_folder_1 (group, folder->children);
}
static struct MSG_Folder *
msg_find_news_folder (const char *group_name, struct MSG_Folder *folders)
{
while (folders)
{
if (!(folders->flags & MSG_FOLDER_FLAG_NEWS_HOST) &&
!XP_STRCMP (group_name, folders->data.newsgroup.group_name))
return folders;
if (folders->children)
{
struct MSG_Folder *f =
msg_find_news_folder (group_name, folders->children);
if (f) return f;
}
folders = folders->next;
}
return 0;
}
static int
msg_host_sort_fn (const void *a, const void *b)
{
register struct MSG_Folder *ma = *(struct MSG_Folder **) a;
register struct MSG_Folder *mb = *(struct MSG_Folder **) b;
XP_ASSERT (ma && mb);
return (strcasecomp (ma->name, mb->name));
}
static int
msg_sort_news_hosts (MWContext *context)
{
int32 i, count = 0;
int status = 0;
struct MSG_Folder **folders, *rest;
XP_ASSERT (context->msgframe && context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return -1;
for (rest = context->msgframe->folders; rest; rest = rest->next)
count++;
if (count == 0)
{
msg_RedisplayFolders (context);
return 0;
}
folders = (MSG_Folder **) XP_ALLOC ((count + 1) * sizeof(*folders));
if (!folders) return MK_OUT_OF_MEMORY;
for (i = 0, rest = context->msgframe->folders; rest; i++, rest = rest->next)
{
folders[i] = rest;
/* In case the default host has changed, recompute the folder line. */
status = msg_compute_news_folder_name (context, rest);
if (status < 0) return status;
}
folders[i] = 0;
qsort ((char *) folders, count, sizeof (struct MSG_Folder *),
msg_host_sort_fn);
for (i = 0; i < count; i++)
folders[i]->next = folders[i+1];
folders[i] = 0;
context->msgframe->folders = folders[0];
XP_FREE (folders);
msg_RedisplayFolders (context);
return 0;
}
static int
msg_ensure_newsgroup_listed (MWContext *context,
const char *host_and_port,
const char *group_name,
XP_Bool secure_p,
XP_Bool sort_p,
XP_Bool select_host_p,
MSG_Folder **hfolderP,
MSG_Folder **gfolderP)
{
char *host_name = NULL;
char *colon, *at;
uint32 port = 0;
struct MSG_NewsHost *host;
struct MSG_Folder *hfolder = NULL;
struct MSG_Folder *gfolder = NULL;
XP_Bool created_host_p = FALSE;
int length;
XP_ASSERT (context->msgframe && context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return -1;
if (gfolderP) *gfolderP = 0;
if (hfolderP) *hfolderP = 0;
/* There may be authinfo at the front of the host - it could be of
the form "user:password@host:port", so take off everything before
the first at-sign.
*/
if (host_and_port) {
at = XP_STRCHR (host_and_port, '@');
if (at) host_and_port = at + 1;
host_name = XP_STRDUP (host_and_port);
if (!host_name) return MK_OUT_OF_MEMORY;
}
/* Find the host that corresponds to this URL. */
if (!host_name || !*host_name)
{
const char* tmp;
FREEIF (host_name);
tmp = NET_GetNewsHost (context, FALSE);
if (!tmp) return MK_NNTP_SERVER_NOT_CONFIGURED;
host_name = XP_STRDUP(tmp);
if (!host_name) return MK_OUT_OF_MEMORY;
}
colon = XP_STRCHR (host_name, ':');
if (colon)
{
*colon = 0;
sscanf (colon+1, " %lu ", &port);
}
if (port == 0)
port = (uint32) (secure_p ? SECURE_NEWS_PORT : NEWS_PORT);
for (host = context->msgframe->data.news.hosts; host; host = host->next)
if (host->port == port && !XP_STRCMP (host->name, host_name))
break;
if (host)
{
XP_FREE (host_name);
host_name = 0;
}
else
{
/* No such host? Add one! */
host = XP_NEW (struct MSG_NewsHost);
if (! host) return MK_OUT_OF_MEMORY;
XP_MEMSET (host, 0, sizeof(*host));
host->name = host_name;
host->port = port;
host->secure_p = secure_p;
length = XP_STRLEN (host_name) + 20;
host->newsrc_file_name = (char *) XP_ALLOC (length);
if (!host->newsrc_file_name)
{
XP_FREE (host);
return MK_OUT_OF_MEMORY;
}
/* Generate a newsrc file name for this host.
Sometimes the default news host can have a newsrc file name of ""
which corresponds to "~/.newsrc". All other hosts have newsrc
files with host names and possibly ports in them. The default
newsrc can be of the latter form too.
So, when we're in a situation where there is no newsrc file for a
host, we always create them in the latter form (even if it turns
out to be the default host.) This means that, in the absence of
any newsrc files, ".newsrc" will never be created, but ".newsrc-news"
will be. The user can rename it later if they care.
If the port being used is the default one for the protocol, we don't
put that on the file name. A pre-existing file could have a name of
".newsrc-news:119", and we would use it, but we would never generate
such a file unless it existed already.
*/
*host->newsrc_file_name = 0;
XP_STRCPY (host->newsrc_file_name, host_name);
XP_ASSERT (host->port != 0);
if (host->port != (uint32) (host->secure_p ? SECURE_NEWS_PORT : NEWS_PORT))
PR_snprintf (host->newsrc_file_name
+ XP_STRLEN (host->newsrc_file_name),
length - XP_STRLEN (host->newsrc_file_name),
":%lu", (unsigned long) host->port);
host->next = context->msgframe->data.news.hosts;
context->msgframe->data.news.hosts = host;
created_host_p = TRUE;
}
hfolder = msg_NewsHostFolder (context, host);
XP_ASSERT (created_host_p || hfolder);
if (!hfolder)
{
/* No such folder? Make one!
*/
struct MSG_Folder *rest;
hfolder = msg_make_news_folder_for_host (context, host);
for (rest = context->msgframe->folders; rest && rest->next;
rest = rest->next)
;
if (rest)
rest->next = hfolder;
else
context->msgframe->folders = hfolder;
created_host_p = TRUE;
}
/* If the host on which we are loading this newsgroup is not the host
that is currently selected, close that one and open this one. */
if (select_host_p &&
context->msgframe->data.news.current_host != host)
msg_ToggleFolderExpansion (context, hfolder, FALSE);
if (!group_name)
goto DONE;
/* Now find the folder of this group.
*/
gfolder = msg_find_news_folder (group_name, context->msgframe->folders);
if (!gfolder)
{
/* No such folder? Make one!
(Must be an unsubscribed group.)
*/
struct MSG_Folder *rest;
gfolder = msg_make_newsgroup_folder (0, group_name, 0);
if (!gfolder) return MK_OUT_OF_MEMORY;
/* #### if this unsubscribed group is listed in the .newsrc file, it
would be nice to insert it in the list in the same relative
position, instead of at the end. */
for (rest = hfolder->children; rest && rest->next; rest = rest->next)
;
if (rest)
rest->next = gfolder;
else
hfolder->children = gfolder;
if (!created_host_p)
/* #### redisplay more efficiently */
msg_RedisplayFolders (context);
}
XP_ASSERT (gfolder);
if (!gfolder) return -1;
DONE:
if (created_host_p && sort_p)
msg_sort_news_hosts (context);
if (hfolderP) *hfolderP = hfolder;
if (gfolderP) *gfolderP = gfolder;
return 0;
}
int
msg_EnsureNewsgroupSelected (MWContext *context,
const char *host_and_port,
const char *group_name,
XP_Bool secure_p)
{
struct MSG_Folder *gfolder = 0;
struct MSG_Folder *hfolder = 0;
int status;
status = msg_ensure_newsgroup_listed (context, host_and_port,
group_name, secure_p, TRUE, TRUE,
&hfolder, &gfolder);
if (status < 0) return status;
XP_ASSERT (hfolder);
if (!hfolder) return -1;
context->msgframe->data.news.current_host =
msg_NewsFolderHost (context, hfolder);
if (group_name)
{
XP_ASSERT (gfolder);
if (!gfolder) return -1;
if (context->msgframe->opened_folder != gfolder) {
msg_ClearThreadList(context);
context->msgframe->opened_folder = gfolder;
}
if (!(gfolder->flags & MSG_FOLDER_FLAG_SELECTED) ||
context->msgframe->num_selected_folders != 1)
{
msg_UnselectAllFolders(context);
gfolder->flags |= MSG_FOLDER_FLAG_SELECTED;
context->msgframe->num_selected_folders = 1;
msg_RedisplayOneFolder(context, gfolder, -1, -1, TRUE);
}
}
return 0;
}
int
msg_UpdateNewsFolderMessageCount (MWContext *context,
struct MSG_Folder *folder,
uint32 unread_messages,
XP_Bool remove_if_empty)
{
if (!folder) return -1;
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews)
return -1;
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_MAIL)));
folder->unread_messages = unread_messages;
if (remove_if_empty &&
unread_messages == 0 &&
(context->msgframe->data.news.group_display_type ==
MSG_ShowSubscribedWithArticles) &&
/* Don't remove it if it's opened */
folder != context->msgframe->opened_folder &&
/* Don't remove it if it was just added by `Check New Groups' */
!(folder->flags & MSG_FOLDER_FLAG_NEW_GROUP))
{
/* We now know that this group has no articles, and is uninteresting.
We can remove it from the list. */
XP_ASSERT (!folder->children &&
!(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_ELIDED |
MSG_FOLDER_FLAG_DIRECTORY)));
msg_RemoveFolderLine (context, folder);
XP_ASSERT (!folder->next);
FREEIF (folder->name);
FREEIF (folder->data.newsgroup.group_name);
XP_FREE (folder);
return 0;
}
/* #### moderated_p */
msg_RedisplayOneFolder (context, folder, -1, -1, FALSE);
return 0;
}
int
MSG_DisplaySubscribedGroup (MWContext *context, const char *group,
uint32 oldest_message,
uint32 youngest_message,
uint32 total_messages,
XP_Bool nowvisiting)
{
struct MSG_Folder *folder = msg_FindNewsgroupFolder (context, group);
struct msg_NewsRCSet *newsrc;
uint32 unread_messages;
if (! folder)
return 0;
FE_EnableClicking (context);
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED)));
folder->data.newsgroup.last_message = youngest_message;
newsrc = msg_GetNewsRCSet (context, folder->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
if (newsrc)
{
/* First, mark all of the articles now known to be expired as read. */
int status = 0;
if (oldest_message > 1)
status = msg_MarkRangeAsRead (newsrc, 0, oldest_message - 1);
if (status < 0)
return status;
/* This isn't really an important enough change to warrant causing
the newsrc file to be saved; we haven't gathered any information
that won't also be gathered for free next time.
else if (status > 0)
msg_NoteNewsrcChanged(context);
*/
/* Now search the newsrc line and figure out how many of these
messages are marked as unread. */
unread_messages = msg_NewsRCUnreadInRange (newsrc, oldest_message,
youngest_message);
/*XP_ASSERT (unread_messages >= 0); it's unsigned */
if (unread_messages > total_messages)
/* This can happen when the newsrc file shows more unread than exist
in the group (total_messages is not necessarily `end - start'.) */
unread_messages = total_messages;
}
else
{
unread_messages = total_messages;
}
folder->total_messages = total_messages;
return msg_UpdateNewsFolderMessageCount(context, folder, unread_messages,
!nowvisiting &&
(context->msgframe->
data.news.group_display_type
== MSG_ShowSubscribedWithArticles));
}
int
MSG_DisplayNewNewsGroup (MWContext *context, const char *host_and_port,
XP_Bool secure_p, const char *group_name,
int32 oldest_message, int32 youngest_message)
{
struct MSG_Folder *hfolder = 0;
struct MSG_Folder *gfolder = 0;
int status;
XP_ASSERT (context->msgframe && context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return -1;
status = msg_ensure_newsgroup_listed (context, host_and_port,
group_name, secure_p, TRUE, TRUE,
&hfolder, &gfolder);
if (status < 0) return status;
XP_ASSERT (hfolder && gfolder);
if (!hfolder || !gfolder) return -1;
XP_ASSERT ((gfolder->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(gfolder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED)));
gfolder->flags |= MSG_FOLDER_FLAG_NEW_GROUP;
gfolder->data.newsgroup.last_message = youngest_message;
gfolder->total_messages = youngest_message - oldest_message;
if (gfolder->unread_messages < 0)
gfolder->unread_messages = gfolder->total_messages;
return msg_UpdateNewsFolderMessageCount(context, gfolder,
gfolder->unread_messages,
FALSE);
}
/* Called as the pre-exit routine of the URL we loaded to download the
new newsgroups. Now that we have them, we can tell the user what
we did.
*/
static void
msg_new_groups_listed (URL_Struct *url, int status, MWContext *context)
{
struct MSG_Folder *hfolder;
struct MSG_Folder *rest;
int32 count = 0;
char *fmt;
char *buf;
if (status < 0) return;
hfolder = msg_NewsHostFolder (context,
context->msgframe->data.news.current_host);
for (rest = hfolder->children; rest; rest = rest->next)
{
XP_ASSERT ((rest->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(rest->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED)));
if (rest->flags & MSG_FOLDER_FLAG_NEW_GROUP)
{
/* The NEW_GROUP flag was added when this group was inserted
into the list; now that we're done, count up the groups
that have that flag, and remove the flag so that the
count is right the next time around (mark/sweep :-)) */
rest->flags &= ~MSG_FOLDER_FLAG_NEW_GROUP;
count++;
}
}
if (count <= 0)
fmt = XP_GetString (MK_MSG_NO_NEW_GROUPS);
else if (count == 1)
fmt = XP_GetString (MK_MSG_ONE_NEW_GROUP);
else
fmt = XP_GetString (MK_MSG_N_NEW_GROUPS);
XP_ASSERT (fmt);
if (fmt)
{
buf = PR_smprintf (fmt, count);
if (!buf) return;
FE_Alert (context, buf);
XP_FREE (buf);
}
}
/* When the user changes their default news host in preferences,
the FE should call this on any existing news window to ensure
that that host is in the list.
*/
int
MSG_NewsHostChanged (MWContext *context, const char *host_and_port)
{
int status;
XP_ASSERT (context && context->msgframe && context->type == MWContextNews);
if (!context || !context->msgframe || context->type != MWContextNews) {
return -1;
}
msg_InterruptContext(context, FALSE);
status = msg_ensure_newsgroup_listed (context, host_and_port, 0,
FALSE, FALSE, FALSE, 0, 0);
if (status < 0) return status;
status = msg_ShowNewsHosts (context);
return status;
}
static void
msg_open_news_host_cb (MWContext *context, char *url, void *closure)
{
URL_Struct* url_struct;
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return;
if (!url) return;
url_struct = NET_CreateURLStruct(url, NET_NORMAL_RELOAD);
XP_FREE (url);
msg_GetURL (context, url_struct, FALSE);
}
int
msg_OpenNewsHost (MWContext *context)
{
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return -1;
return FE_PromptForNewsHost (context, XP_GetString(MK_MSG_OPEN_NEWS_HOST),
msg_open_news_host_cb, 0);
}
int
msg_AddNewsGroup(MWContext* context)
{
char* groupname;
char* url;
URL_Struct *url_struct;
groupname = FE_Prompt(context, XP_GetString(XP_NEWS_PROMPT_ADD_NEWSGROUP),
"");
if (!groupname) return 0; /* User cancelled. */
url = msg_MakeNewsURL(context, context->msgframe->data.news.current_host,
groupname);
if (!url) return MK_OUT_OF_MEMORY;
url_struct = NET_CreateURLStruct (url, NET_NORMAL_RELOAD);
XP_FREE(url);
if (!url_struct) return MK_OUT_OF_MEMORY;
msg_GetURL(context, url_struct, FALSE);
return 0;
}
static void
msg_cancel_done (URL_Struct *url, int status, MWContext *context)
{
if (status >= 0)
FE_Alert (context, XP_GetString (MK_MSG_MESSAGE_CANCELLED));
/* else, the FE exit routine will present the error message. */
}
int
msg_CancelMessage (MWContext *context)
{
/* Get the message ID of the current message, and open a URL of the form
"news://host/message@id?cancel" and mknews.c will do all the work. */
struct MSG_ThreadEntry *msg;
struct MSG_NewsHost *host;
URL_Struct *url_struct;
char *id;
char *url;
char *suffix;
XP_ASSERT (context->msgframe && context->type == MWContextNews);
if (!context->msgframe || context->type != MWContextNews)
return -1;
msg = context->msgframe->displayed_message;
if (!msg) return -1; /* ??? */
host = context->msgframe->data.news.current_host;
XP_ASSERT (host);
if (!host) return -1;
id = context->msgframe->msgs->string_table [msg->id];
if (!id) return -1; /* ??? */
id = NET_Escape (id, URL_XALPHAS);
if (!id) return MK_OUT_OF_MEMORY;
suffix = (char *) XP_ALLOC (XP_STRLEN (id) + 30);
if (!suffix) return MK_OUT_OF_MEMORY;
XP_STRCPY (suffix, id);
XP_STRCAT (suffix, "?cancel");
XP_FREE (id);
url = msg_MakeNewsURL (context, host, suffix);
XP_FREE (suffix);
if (!url) return MK_OUT_OF_MEMORY;
/* use NET_NORMAL_RELOAD so we don't fail to
cancel messages that are in the cache */
url_struct = NET_CreateURLStruct (url, NET_NORMAL_RELOAD);
XP_FREE (url);
if (!url_struct) return MK_OUT_OF_MEMORY;
url_struct->internal_url = TRUE;
url_struct->pre_exit_fn = msg_cancel_done;
return msg_GetURL (context, url_struct, FALSE);
}
/* Updating the newsrc
*/
int
msg_MarkNewsArticleRead (MWContext *context,
const char *message_id,
const char *xref)
{
struct MSG_ThreadEntry *msg = 0;
struct msg_NewsRCSet *newsrc = 0;
struct MSG_Folder *folder = 0;
XP_Bool marked_read = FALSE;
int status = 0;
if (!message_id ||
!context->msgframe ||
context->type != MWContextNews ||
!context->msgframe->msgs ||
!context->msgframe->msgs->message_id_table)
return 0;
if (!context->msgframe->data.news.newsrc_changed_timer)
{
/* Only stat the file at first change, not every change. */
msg_check_newsrc_file_date (context);
if (!context->msgframe->data.news.newsrc_file)
/* #### should probably reload data now? */
return 0;
}
msg_undo_StartBatch(context);
folder = context->msgframe->opened_folder;
/* Even if the file doesn't exist on disk, we should have memory data
for it. When connecting to a new news host, we don't actually write
a newsrc file on disk until we have article data to put in it. */
XP_ASSERT (context->msgframe->data.news.newsrc_file);
if (folder && context->msgframe->data.news.newsrc_file)
{
XP_ASSERT ((folder->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED)));
newsrc = msg_GetNewsRCSet (context, folder->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
}
if (folder && !newsrc && context->msgframe->data.news.newsrc_file)
{
/* There wasn't a newsrc entry for this group. This probably means
that this group is both unsubscribed, and not listed in the newsrc
file. So let's add one. This will cause (at the next save) there
to be one more line in the newsrc file, for this group in the
unsubscribed state.
*/
newsrc = msg_AddNewsRCSet (folder->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
}
if (newsrc)
{
/* Find the ThreadEntry with this ID. */
char *id = XP_STRDUP (message_id);
char *id2;
if (! id) {
status = MK_OUT_OF_MEMORY;
goto FAIL;
}
id2 = id;
if (*id2 == '<') id2++;
if (id2[XP_STRLEN(id2)-1] == '>')
id2[XP_STRLEN(id2)-1] = 0;
msg = (struct MSG_ThreadEntry *)
XP_Gethash (context->msgframe->msgs->message_id_table, id2, 0);
XP_FREE (id);
if (msg)
{
status = msg_MarkArtAsRead (newsrc, msg->data.news.article_number);
if (status < 0)
goto FAIL;
else if (status > 0)
{
marked_read = TRUE;
msg_NoteNewsrcChanged(context);
/* Update the summary line. */
msg_ChangeFlag(context, msg, MSG_FLAG_READ, 0);
}
}
}
if (msg)
{
context->msgframe->displayed_message = msg;
msg_EnsureSelected(context, msg); /* Select if not already. */
msg_RedisplayOneMessage(context, msg, -1, 0, TRUE); /* Scroll to it. */
}
else
{
/* This can happen if we were reading a group normally, and then clicked
on a "references" link, or a "news:" link, to some random message for
which we do not have a ThreadEntry. */
msg_UnselectAllMessages (context);
}
/* Now let's go through that xref field, and mark the crossposts
as read as well. */
if (xref)
{
const char *rest = xref;
const char *group_start = 0, *group_end = 0;
while (XP_IS_SPACE (*rest))
rest++;
group_start = rest;
for (;;) {
char* name;
uint32 n;
switch (*rest) {
case ':':
group_end = rest;
rest++;
break;
case ',':
case ' ':
case 0:
n = 0;
#ifdef BUG_22516
if (group_end) n = XP_ATOI(group_end + 1);
#else
if (group_end) sscanf ( group_end+1, "%lu", &n);
#endif /* BUG_22516 */
if (n > 0) {
MSG_Folder* folder;
name = (char *) XP_ALLOC (group_end - group_start + 1);
if (! name) {
status = MK_OUT_OF_MEMORY;
goto FAIL;
}
XP_MEMCPY (name, group_start, group_end - group_start);
name[group_end - group_start] = 0;
folder = msg_FindNewsgroupFolder(context, name);
if (folder) {
msg_ChangeFlagAny(context, folder, n, MSG_FLAG_READ, 0);
} else {
newsrc =
msg_GetNewsRCSet(context, name,
context->msgframe->data.news.newsrc_file);
if (newsrc) {
status = msg_MarkArtAsRead (newsrc, n);
if (status < 0) {
goto FAIL;
} else if (status > 0) {
msg_NoteNewsrcChanged(context);
}
}
}
XP_FREE (name);
}
if (*rest == 0) goto DONE;
rest++;
group_start = rest;
group_end = 0;
break;
default:
rest++;
break;
}
}
}
DONE:
status = 0;
FAIL:
msg_undo_EndBatch(context);
return status;
}
static void
msg_subscribe_group_1(MWContext* context, MSG_Folder* folder,
int on_or_off_or_toggle)
{
msg_NewsRCSet* set;
XP_ASSERT (context->type == MWContextNews);
if (context->type != MWContextNews) return;
XP_ASSERT (folder &&
(folder->flags & MSG_FOLDER_FLAG_NEWSGROUP) &&
!(folder->flags & (MSG_FOLDER_FLAG_NEWS_HOST |
MSG_FOLDER_FLAG_MAIL |
MSG_FOLDER_FLAG_DIRECTORY |
MSG_FOLDER_FLAG_ELIDED)));
if (!folder) return;
if (! (folder->flags & MSG_FOLDER_FLAG_NEWSGROUP)) return;
set = msg_GetNewsRCSet(context, folder->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
if (!set) {
set = msg_AddNewsRCSet(folder->data.newsgroup.group_name,
context->msgframe->data.news.newsrc_file);
}
if (set) {
if (on_or_off_or_toggle == 0)
msg_SetSubscribed(set, !msg_GetSubscribed(set));
else if (on_or_off_or_toggle == -1)
msg_SetSubscribed(set, FALSE);
else if (on_or_off_or_toggle == 1)
msg_SetSubscribed(set, TRUE);
else
XP_ASSERT(0);
msg_NoteNewsrcChanged(context);
if (msg_GetSubscribed(set)) {
folder->flags |= MSG_FOLDER_FLAG_SUBSCRIBED;
} else {
folder->flags &= ~MSG_FOLDER_FLAG_SUBSCRIBED;
}
msg_RedisplayOneFolder(context, folder, -1, -1, FALSE);
}
}
void
msg_ToggleSubscribedGroup(MWContext* context, MSG_Folder* folder)
{
msg_subscribe_group_1(context, folder, 0);
}
static int
msg_subscribe_selected_groups_1(MWContext* context, MSG_Folder* folder,
XP_Bool subscribe_p)
{
int status;
for ( ; folder ; folder = folder->next) {
if ((folder->flags & MSG_FOLDER_FLAG_SELECTED) &&
(folder->flags & MSG_FOLDER_FLAG_NEWSGROUP)) {
msg_subscribe_group_1(context, folder, (subscribe_p ? 1 : -1));
}
status = msg_subscribe_selected_groups_1(context, folder->children,
subscribe_p);
if (status < 0) return status;
}
return 0;
}
int
msg_SubscribeSelectedGroups (MWContext *context, XP_Bool subscribe_p)
{
XP_ASSERT (context->type == MWContextNews && context->msgframe);
if (context->type != MWContextNews || !context->msgframe) return -1;
return msg_subscribe_selected_groups_1(context, context->msgframe->folders,
subscribe_p);
}
/* Saving news messages
*/
struct msg_news_save_state
{
char *file_name;
char **urls;
char **urls_remaining;
XP_File out_fd;
XP_Bool existed_p;
XP_Bool wrote_any_p;
XP_Bool summary_valid_p;
int numwrote;
Net_GetUrlExitFunc *final_exit;
#ifdef MANGLE_INTERNAL_ENVELOPE_LINES
char *obuffer;
uint32 obuffer_size;
uint32 obuffer_fp;
#endif /* MANGLE_INTERNAL_ENVELOPE_LINES */
};
static void msg_save_next_news_exit (URL_Struct *, int, MWContext *);
static int
msg_save_next_news_pop (MWContext *context, struct msg_news_save_state *state,
XP_Bool first_time_p)
{
URL_Struct *url_struct;
char *url = *state->urls_remaining++;
state->numwrote++;
url_struct = NET_CreateURLStruct (url, NET_DONT_RELOAD);
if (!url_struct) return MK_OUT_OF_MEMORY;
if (first_time_p)
{
XP_ASSERT (!state->final_exit);
FE_SetWindowLoading (context, url_struct, &state->final_exit);
XP_ASSERT (state->final_exit);
}
url_struct->fe_data = (void *) state;
state->wrote_any_p = FALSE;
NET_GetURL (url_struct, FO_CACHE_AND_APPEND_TO_FOLDER,
context, msg_save_next_news_exit);
return 0;
}
static unsigned int
msg_append_to_folder_stream_write_ready (void *closure)
{
return MAX_WRITE_READY;
}
/* #### move this to a header file -- defined in compose.c */
extern int32 msg_do_fcc_handle_line(char* line, uint32 length, void* closure);
static int
msg_append_to_folder_stream_write (void *closure,
const char *block, int32 length)
{
struct msg_news_save_state *state = (struct msg_news_save_state *) closure;
/* We implicitly assume that the data we're getting is already following
the local line termination conventions; the NNTP module has already
converted from CRLF to the local linebreak for us. */
if (!state->wrote_any_p)
{
const char *envelope = msg_GetDummyEnvelope();
int32 L;
#ifdef FIXED_SEPERATORS
if (state->existed_p)
/* If the file existed, preceed it by a linebreak: the BSD mailbox file
format wants a *blank* line before all "From " lines except the
first. This assumes that the previous message in the file was
properly closed, that is, that the last line in the file ended with
a linebreak. */
L = LINEBREAK_LEN;
if (L != XP_FileWrite (LINEBREAK, L, state->out_fd))
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
state->wrote_any_p = TRUE;
#endif /* FIXED_SEPERATORS */
state->existed_p = TRUE;
L = XP_STRLEN (envelope);
if (L != XP_FileWrite (envelope, L, state->out_fd))
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
state->wrote_any_p = TRUE;
/* Now write out an X-Mozilla-Status header to the folder. */
{
uint16 flags = 0;
char buf[X_MOZILLA_STATUS_LEN + 50];
#ifdef MARK_SAVE_NEWS_MESSAGES_AS_READ
/* #### should only do this if the message is currently marked
as read (it usually is, but not always.) */
flags |= MSG_FLAG_READ;
#endif /* MARK_SAVE_NEWS_MESSAGES_AS_READ */
/* #### how to tell if the news message was MSG_FLAG_MARKED? */
/* #### for consistency, it would be better if we put the
X-Mozilla-Status header at the *end* of the header block
rather than the beginning. */
XP_SPRINTF(buf, X_MOZILLA_STATUS ": %04x" LINEBREAK, flags);
L = XP_STRLEN (buf);
if (L != XP_FileWrite (buf, L, state->out_fd))
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
}
}
#ifdef MANGLE_INTERNAL_ENVELOPE_LINES
/* Since we're writing a mail folder, we must follow the Usual Mangling
Conventions.
*/
{
int n = msg_LineBuffer (block, length,
&state->obuffer,
&state->obuffer_size,
&state->obuffer_fp,
TRUE, msg_do_fcc_handle_line,
state->out_fd);
if (n < 0) /* write failed */
return MK_MIME_ERROR_WRITING_FILE;
}
#else /* !MANGLE_INTERNAL_ENVELOPE_LINES */
if (XP_FileWrite (block, length, state->out_fd) < length)
return MK_MIME_ERROR_WRITING_FILE;
#endif /* !MANGLE_INTERNAL_ENVELOPE_LINES */
return 1;
}
static void
msg_append_to_folder_stream_complete (void *closure)
{
/* Nothing to do here - the URL exit method does our cleanup. */
}
static void
msg_append_to_folder_stream_abort (void *closure, int status)
{
/* Nothing to do here - the URL exit method does our cleanup. */
}
NET_StreamClass *
msg_MakeAppendToFolderStream (int format_out, void *closure,
URL_Struct *url, MWContext *context)
{
NET_StreamClass *stream;
TRACEMSG(("Setting up attachment stream. Have URL: %s\n", url->address));
stream = XP_NEW (NET_StreamClass);
if (!stream) return 0;
XP_MEMSET (stream, 0, sizeof (NET_StreamClass));
stream->name = "Folder Append Stream";
stream->complete = msg_append_to_folder_stream_complete;
stream->abort = msg_append_to_folder_stream_abort;
stream->put_block = msg_append_to_folder_stream_write;
stream->is_write_ready = msg_append_to_folder_stream_write_ready;
stream->data_object = url->fe_data;
stream->window_id = context;
TRACEMSG(("Returning stream from msg_MakeAppendToFolderStream"));
return stream;
}
static void
msg_save_next_news_exit (URL_Struct *url, int status, MWContext *context)
{
struct msg_news_save_state *state =
(struct msg_news_save_state *) url->fe_data;
XP_ASSERT (state);
if (!state) return;
if (status < 0 || !*state->urls_remaining)
{
FAIL:
#ifdef MANGLE_INTERNAL_ENVELOPE_LINES
if (status >= 0 && state->obuffer_fp > 0)
{
/* Flush out any partial line. */
msg_do_fcc_handle_line(state->obuffer, state->obuffer_fp, state);
}
FREEIF(state->obuffer);
#endif /* !MANGLE_INTERNAL_ENVELOPE_LINES */
if (state->urls)
{
char **rest = state->urls;
while (*rest)
XP_FREE (*rest++);
XP_FREE (state->urls);
}
if (XP_FileClose(state->out_fd) != 0) {
if (status >= 0) status = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
}
if (state->final_exit)
state->final_exit (url, status, context);
if (state->summary_valid_p)
msg_SetSummaryValid (state->file_name, state->numwrote,
#ifdef MARK_SAVE_NEWS_MESSAGES_AS_READ
0
#else /* !MARK_SAVE_NEWS_MESSAGES_AS_READ */
state->numwrote
#endif /* !MARK_SAVE_NEWS_MESSAGES_AS_READ */
);
FREEIF (state->file_name);
XP_FREE (state);
}
else
{
status = msg_save_next_news_pop (context, state, FALSE);
if (status < 0) goto FAIL;
}
}
int
msg_SaveSelectedNewsMessages (MWContext *context, const char *file_name)
{
struct msg_news_save_state *state;
XP_StatStruct st;
state = XP_NEW (struct msg_news_save_state);
if (!state) return MK_OUT_OF_MEMORY;
XP_MEMSET (state, 0, sizeof(*state));
if (!XP_Stat (file_name, &st, xpMailFolder))
state->existed_p = (st.st_size > 0);
if (state->existed_p && !msg_ConfirmMailFile (context, file_name))
return -1; /* #### NOT_A_MAIL_FILE */
state->out_fd = XP_FileOpen (file_name, xpMailFolder, XP_FILE_APPEND_BIN);
if (!state->out_fd)
{
XP_FREE (state);
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
}
state->urls = msg_GetSelectedMessageURLs (context);
if (!state->urls)
{
XP_FileClose (state->out_fd);
XP_FREE (state);
return 0;
}
state->urls_remaining = state->urls;
if (state->existed_p)
state->summary_valid_p = msg_IsSummaryValid (file_name, &st);
state->file_name = XP_STRDUP (file_name);
return msg_save_next_news_pop (context, state, TRUE);
}
int
MSG_AddToKnownArticles(MWContext* context, const char* host_and_port,
XP_Bool secure_p, const char* group_name,
int32 first, int32 last)
{
struct MSG_NewsKnown* k;
XP_ASSERT(context->type == MWContextNews && context->msgframe);
if (context->type != MWContextNews || context->msgframe == NULL) return -1;
k = &(context->msgframe->data.news.knownarts);
if (k->host_and_port == NULL ||
XP_STRCMP(k->host_and_port, host_and_port) != 0 ||
k->secure_p != secure_p ||
k->group_name == NULL ||
XP_STRCMP(k->group_name, group_name) != 0 ||
!k->set) {
msg_ClearThreadList(context);
FREEIF(k->host_and_port);
k->host_and_port = XP_STRDUP(host_and_port);
k->secure_p = secure_p;
FREEIF(k->group_name);
k->group_name = XP_STRDUP(group_name);
msg_FreeNewsrcSet(k->set);
k->set = msg_MakeNewsrcSet();
if (!k->host_and_port || !k->group_name || !k->set) {
return MK_OUT_OF_MEMORY;
}
}
return msg_MarkRangeAsRead(k->set, first, last);
}
/* Figure out what articles to get from the news server. Takes into account
what articles we already know about (if we're already displaying this
group), and what articles have already been read (if we're only looking for
unread articles). */
int
MSG_GetRangeOfArtsToDownload(MWContext* context, const char* host_and_port,
XP_Bool secure_p, const char* group_name,
int32 first_possible,
int32 last_possible,
int32 maxextra,
int32* first,
int32* last)
{
struct MSG_NewsKnown* k;
msg_NewsRCSet *set;
int status = 0;
XP_Bool emptyGroup_p = FALSE;
XP_ASSERT(first && last);
if (!first || !last) return -1;
*first = 0;
*last = 0;
XP_ASSERT(context && context->msgframe && context->type == MWContextNews);
if (!context || !context->msgframe || context->type != MWContextNews) {
return -1;
}
if (maxextra <= 0 || last_possible < first_possible || last_possible < 1) {
emptyGroup_p = TRUE;
}
k = &(context->msgframe->data.news.knownarts);
if (k->host_and_port == NULL ||
XP_STRCMP(k->host_and_port, host_and_port) != 0 ||
k->secure_p != secure_p ||
k->group_name == NULL ||
XP_STRCMP(k->group_name, group_name) != 0 ||
!k->set) {
/* We're displaying some other group. Clear out that display, and set up
everything to return the proper first chunk. */
status = msg_EnsureNewsgroupSelected(context, host_and_port, group_name,
secure_p);
if (status < 0) return status;
if (emptyGroup_p)
return 0;
status = MSG_AddToKnownArticles(context, host_and_port, secure_p,
group_name, 0, 0);
if (status < 0) return status;
if (!context->msgframe->all_messages_visible_p) {
set = msg_GetNewsRCSet(context, group_name,
context->msgframe->data.news.newsrc_file);
if (set) status = msg_CopySet(k->set, set);
if (status < 0) return status;
if (first_possible > 1) {
msg_MarkRangeAsRead(k->set, 1, first_possible - 1);
/* Help makes later calls to msg_FirstUnreadArt be more useful. */
}
}
}
else
{
if (emptyGroup_p)
return 0;
}
k->first_possible = first_possible;
k->last_possible = last_possible;
/* Determine if we want to get articles towards the end of the range,
or towards the beginning. If there are new articles at the end
we haven't read, we always want to get those first. Otherwise, it
depends on the shouldGetOldest flag. */
if (k->shouldGetOldest && msg_NewsRCIsRead(k->set, last_possible)) {
status = msg_FirstUnreadRange(k->set, first_possible, last_possible,
first, last);
if (status < 0) return status;
if (*first > 0 && *last - *first >= maxextra) {
*last = *first + maxextra - 1;
}
} else {
status = msg_LastUnreadRange(k->set, first_possible, last_possible,
first, last);
if (status < 0) return status;
if (*first > 0 && *last - *first >= maxextra) {
*first = *last - maxextra + 1;
}
}
return 0;
}