src/mui/YAMApplication.c
/***************************************************************************
YAM - Yet Another Mailer
Copyright (C) 1995-2000 Marcel Beck
Copyright (C) 2000-2022 YAM Open Source Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
YAM Official Support Site : http://www.yam.ch
YAM OpenSource project : http://sourceforge.net/projects/yamos/
$Id$
Superclass: MUIC_Application
Description: Application subclass handles all "global" stuff.
***************************************************************************/
#include "YAMApplication_cl.h"
#include <string.h>
#include <proto/dos.h>
#include <proto/icon.h>
#include <proto/muimaster.h>
#include <proto/rexxsyslib.h>
#include <proto/timer.h>
#if defined(__amigaos4__)
#include <proto/application.h>
#endif
#include <mui/NList_mcc.h>
#include <mui/NListtree_mcc.h>
#include <workbench/icon.h>
#include "YAM.h"
#include "YAM_error.h"
#include "YAM_find.h"
#include "YAM_global.h"
#include "YAM_mainFolder.h"
#include "mui/AboutWindow.h"
#include "mui/AddressBookListtree.h"
#include "mui/AddressBookWindow.h"
#include "mui/AddressMatchPopupWindow.h"
#include "mui/ConfigWindow.h"
#include "mui/InfoWindow.h"
#include "mui/MainMailListGroup.h"
#include "mui/ReadMailGroup.h"
#include "mui/SearchMailWindow.h"
#include "mui/StringRequestWindow.h"
#include "mui/TransferControlGroup.h"
#include "mui/TransferWindow.h"
#include "AddressBook.h"
#include "AppIcon.h"
#include "Busy.h"
#include "Config.h"
#include "FileInfo.h"
#include "FolderList.h"
#include "Locale.h"
#include "MailList.h"
#include "MailServers.h"
#include "MUIObjects.h"
#include "UpdateCheck.h"
#include "Requesters.h"
#include "Rexx.h"
#include "Threads.h"
#include "gitrev.h"
#include "Debug.h"
#define EMAILCACHENAME "PROGDIR:.emailcache"
/* CLASSDATA
struct Data
{
Object *aboutWindow;
Object *transferWindow;
struct ABook emailCache;
char *emailCacheName;
char compileInfo[SIZE_DEFAULT];
BOOL iconified;
};
*/
/* INCLUDE
#include "YAM_main.h"
#include "AddressBook.h"
#include "mui/AddressBookWindow.h"
#include "mui/PreselectionWindow.h"
*/
/* Private functions */
/// LoadEMailCache
static void LoadEMailCache(const char *name, struct ABook *cache)
{
FILE *fh;
ENTER();
InitABook(cache, "cache");
if((fh = fopen(name, "r")) != NULL)
{
int i = 0;
char *line = NULL;
size_t size = 0;
setvbuf(fh, NULL, _IOFBF, SIZE_FILEBUF);
// we limit the reading to a maximum of 100 so that this code can't read endlessly
while(GetLine(&line, &size, fh) >= 0 && i++ < 100)
{
char *addr;
char *end;
if((addr = strchr(line, '<')) != NULL && (end = strchr(addr, '>')) != NULL)
{
struct ABookNode *abn;
if((abn = CreateABookNode(ABNT_USER)) != NULL)
{
if(addr != line)
{
addr[-1] = '\0';
// copy the real name
strlcpy(abn->RealName, UnquoteString(line, FALSE), sizeof(abn->RealName));
}
end[0] = '\0';
strlcpy(abn->Address, addr+1, sizeof(abn->Address));
AddABookNode(&cache->rootGroup, abn, NULL);
}
}
else
{
D(DBF_STARTUP, "Error with '%s', parsing line: '%s'", name, line);
}
}
fclose(fh);
free(line);
}
else
{
E(DBF_STARTUP, "Error opening file '%s' for reading", name);
}
LEAVE();
}
///
struct SaveEMailCacheStuff
{
FILE *fh;
LONG savedEntries;
};
/// SaveEMailCacheEntry
static BOOL SaveEMailCacheEntry(const struct ABookNode *abn, UNUSED ULONG flags, void *userData)
{
BOOL result;
struct SaveEMailCacheStuff *stuff = (struct SaveEMailCacheStuff *)userData;
ENTER();
if(stuff->savedEntries < C->EmailCache)
{
// the cache contains user entries only, thus we don't have to care about the type here
if(abn->RealName[0] != '\0')
fprintf(stuff->fh, "%s <%s>\n", UnquoteString(abn->RealName, FALSE), abn->Address);
else
fprintf(stuff->fh, "<%s>\n", abn->Address);
// count the number of saved entries
stuff->savedEntries++;
// continue to save entries
result = TRUE;
}
else
{
// too many entries, abort
result = FALSE;
}
RETURN(result);
return result;
}
///
/// SaveEMailCache
static void SaveEMailCache(const char *name, struct ABook *cache)
{
FILE *fh;
ENTER();
if((fh = fopen(name, "w")) != NULL)
{
struct SaveEMailCacheStuff stuff;
stuff.fh = fh;
stuff.savedEntries = 0;
IterateABook(cache, 0, SaveEMailCacheEntry, &stuff);
fclose(fh);
}
else
{
E(DBF_STARTUP, "Error opening file '%s' for writing", name);
}
LEAVE();
}
///
/// MatchRealName
// check whether a given string matches any part of a real name
static BOOL MatchRealName(const char *realName, const char *text, LONG textLen, LONG *matchPart)
{
BOOL match = FALSE;
char *name;
ENTER();
// check if we have a realname at all
if(realName[0] != '\0')
{
LONG part = 0;
// first try to match the whole realname string
// completly
if(Strnicmp(realName, text, textLen) == 0)
match = TRUE;
else if((name = strdup(realName)) != NULL)
{
char *n = name;
char *p;
// if this didn't work out we see if we can seperate the realname
// into a first/lastname and see if only parts of it matches
do
{
// break up the name in single parts delimited by spaces, quotes and commas
if((p = strpbrk(n, " \",'")) != NULL)
*p++ = '\0';
if(n[0] != '\0')
{
// now check if this part of the name matches
if(Strnicmp(n, text, textLen) == 0)
{
// yes!!
match = TRUE;
break;
}
part++;
}
// advance to the next name part
n = p;
}
while(p != NULL);
free(name);
}
// remember which part of the name this is if there is any interest in it
if(matchPart != NULL)
*matchPart = part;
}
RETURN(match);
return match;
}
///
struct FindAllABMatchesStuff
{
const char *text;
size_t textlen;
Object *list;
};
/// FindAllABMatchesEntry
static BOOL FindAllABMatchesEntry(const struct ABookNode *abn, UNUSED ULONG flags, void *userData)
{
struct FindAllABMatchesStuff *stuff = (struct FindAllABMatchesStuff *)userData;
ENTER();
if(abn->type == ABNT_USER || abn->type == ABNT_LIST)
{
struct MatchedABookEntry e = { -1, -1, NULL, NULL };
if(Strnicmp(abn->Alias, stuff->text, stuff->textlen) == 0)
{
e.MatchField = 0;
e.MatchString = (char *)abn->Alias;
}
else if(MatchRealName(abn->RealName, stuff->text, stuff->textlen, &e.RealNameMatchPart) == TRUE)
{
e.MatchField = 1;
e.MatchString = (char *)abn->RealName;
}
// don't match addresses in recipient lists
// for lists the address field represents the reply address and this should never match
else if(abn->type == ABNT_USER && Strnicmp(abn->Address, stuff->text, stuff->textlen) == 0)
{
e.MatchField = 2;
e.MatchString = (char *)abn->Address;
}
if(e.MatchField != -1) /* one of the fields matches, so let's insert it in the MUI list */
{
e.MatchEntry = (struct ABookNode *)abn;
DoMethod(stuff->list, MUIM_NList_InsertSingle, &e, MUIV_NList_Insert_Sorted);
}
}
RETURN(TRUE);
return TRUE;
}
///
/// FindAllABMatches
// tries to find all matching addressbook entries and add them to the list
static void FindAllABMatches(const struct ABook *abook, const char *text, Object *list)
{
struct FindAllABMatchesStuff stuff;
ENTER();
stuff.text = text;
stuff.textlen = strlen(text);
stuff.list = list;
IterateABook(abook, 0, FindAllABMatchesEntry, &stuff);
LEAVE();
}
///
/// FlushIndex
// flushes (saves/expungs) the loaded index of a folder. You have to
// make sure yourself to lock the folder list accordingly when you
// use this function. The 'minAccessTime' variable can be used to
// prevent this function from expunging the index in case the
// folder was last accessed >= minAccessTime. Use 0 to expunge it
// at all times.
static void FlushIndex(struct Folder *folder, time_t minAccessTime)
{
ENTER();
// make sure the folder index is saved
if(isModified(folder))
MA_SaveIndex(folder);
// flush the index if
// - the index is loaded at all, and
// - the minimum access time has been exceeded
// - the folder is not the currently active one
if(folder != NULL &&
folder->LoadedMode == LM_VALID &&
(minAccessTime == 0 || minAccessTime >= folder->lastAccessTime) &&
folder != GetCurrentFolder())
{
if(minAccessTime != 0)
D(DBF_FOLDER, "flush index of folder '%s' due to lastAccessTime (%d) < minAccessTime (%d)", folder->Name, folder->lastAccessTime, minAccessTime);
else
D(DBF_FOLDER, "flush index of folder '%s'", folder->Name);
if(ClearFolderMails(folder, FALSE) == TRUE)
{
folder->LoadedMode = LM_FLUSHED;
clearFlag(folder->Flags, FOFL_FREEXS);
}
}
LEAVE();
}
///
/* Public Methods */
/// DECLARE(FindEmailMatches)
DECLARE(FindEmailMatches) // STRPTR matchText, Object *list
{
GETDATA;
ENTER();
if(IsStrEmpty(msg->matchText) == FALSE)
{
// We first try to find matches in the Addressbook
// and add them to the MUI list
FindAllABMatches(&G->abook, msg->matchText, msg->list);
// If the user has selected the EmailCache feature we also have to check this
// list and add matches to the MUI List too
if(C->EmailCache > 0)
FindAllABMatches(&data->emailCache, msg->matchText, msg->list);
}
RETURN(0);
return 0;
}
///
/// FindEMailCacheMatchEntry
/// DECLARE(FindEmailCacheMatch)
// Method that search in the email cache and return the found entry if not more than one
DECLARE(FindEmailCacheMatch) // STRPTR matchText
{
GETDATA;
struct ABookNode *foundentry = NULL;
ENTER();
if(C->EmailCache > 0 && IsStrEmpty(msg->matchText) == FALSE)
{
if(SearchABook(&data->emailCache, msg->matchText, ASM_REALNAME|ASM_ADDRESS|ASM_USER, &foundentry) > 1)
foundentry = NULL;
}
RETURN(foundentry);
return (IPTR)foundentry;
}
///
/// DECLARE(AddToEmailCache)
// method that parses a string for addresses and add them to the emailcache if enabled
DECLARE(AddToEmailCache) // struct Person *person
{
GETDATA;
ENTER();
// if the emailcache feature is turned off or
// the supplied person doesn't have a address, lets exit immediatly
if(C->EmailCache == 0 || !msg->person->Address[0])
{
RETURN(-1);
return -1;
}
// We first check the Addressbook if this Person already exists in the AB and if
// so we cancel this whole operation.
if(FindPersonInABook(&G->abook, msg->person) == NULL)
{
struct ABookNode *abn;
// Ok, it doesn't exists in the AB, now lets check the cache list
// itself
if((abn = FindPersonInABook(&data->emailCache, msg->person)) != NULL)
{
// if we find the same entry already in the list we just move it
// up to the top
MoveABookNode(&data->emailCache.rootGroup, abn, NULL);
// the cache was modified
data->emailCache.modified = TRUE;
}
// if we didn't find the person already in the list
// we have to add it after the last node
if(abn == NULL)
{
// create a new entry
if((abn = CreateABookNode(ABNT_USER)) != NULL)
{
char *p;
// copy the data into the new cache entry
strlcpy(abn->RealName, msg->person->RealName, sizeof(abn->RealName));
strlcpy(abn->Address, msg->person->Address, sizeof(abn->Address));
// strip any single quotes from the real name
p = abn->RealName;
while((p = strchr(p, '\'')) != NULL)
{
// move all characters one backward including the trailing NUL byte
memmove(p, p+1, strlen(p)+1);
}
// we always add new items to the top because this is a FILO
AddABookNode(&data->emailCache.rootGroup, abn, NULL);
// the cache was modified
data->emailCache.modified = TRUE;
}
}
}
RETURN(0);
return 0;
}
///
/* Overloaded Methods */
/// OVERLOAD(OM_NEW)
OVERLOAD(OM_NEW)
{
char filebuf[SIZE_PATHFILE];
char verbuf[CBD_TITLELEN];
// prepare a string pointer array with all the
// names of the used classes within. This array is only usefull if MUI v20
// is used and the user wants to alter the MUI settings of the application
static const char *const Classes[] = { "BetterString.mcc",
"NBalance.mcc",
"NList.mcc",
"NListtree.mcc",
"NListviews.mcc",
"TextEditor.mcc",
"TheBar.mcc",
"Urltext.mcc",
NULL
};
// now we load the standard icons like (check.info, new.info etc)
// but we also try to take care of different icon.library versions.
AddPath(filebuf, G->ProgDir, G->ProgName, sizeof(filebuf));
if(LIB_VERSION_IS_AT_LEAST(IconBase, 44, 0) == TRUE)
{
G->HideIcon = GetIconTags(filebuf,
ICONGETA_FailIfUnavailable, FALSE,
TAG_DONE);
}
else
{
G->HideIcon = GetDiskObjectNew(filebuf);
}
// set up the version string for the Commodity title
// the string MUST include the "$VER:" cookie, because this one will be stripped
// by MUI. However, to avoid any problems with two version cookies in the final
// executable we set up this one here in a bit more obfuscated fashion.
snprintf(verbuf, sizeof(verbuf), "%sVER: YAM %s (%s)", "$", yamver, yamversiondate);
if((obj = (Object *)DoSuperNew(cl, obj,
MUIA_Application_Author, "YAM Open Source Team",
MUIA_Application_Base, "YAM",
MUIA_Application_Title, "YAM",
MUIA_Application_Version, verbuf,
MUIA_Application_Copyright, yamcopyright,
MUIA_Application_Description, tr(MSG_APP_DESCRIPTION),
MUIA_Application_UseRexx, FALSE,
MUIA_Application_UsedClasses, Classes,
MUIA_Application_HelpFile, "http://yam.ch/wiki",
MUIA_Application_DiskObject, G->HideIcon,
TAG_MORE, inittags(msg))) != NULL)
{
GETDATA;
struct DateTime dt;
struct TagItem *tags = inittags(msg), *tag;
// now we generate some static default for our whole application
dt.dat_Stamp.ds_Days = yamversiondays;
dt.dat_Stamp.ds_Minute = 0;
dt.dat_Stamp.ds_Tick = 0;
dt.dat_Format = FORMAT_DEF;
dt.dat_Flags = 0L;
dt.dat_StrDay = NULL;
dt.dat_StrDate = data->compileInfo;
dt.dat_StrTime = NULL;
DateToStr(&dt);
data->compileInfo[31] = '\0'; // make sure that the string is really terminated at LEN_DATSTRING.
// now we add the compiler information as YAM can be
// compiled with different versions and types of compilers
snprintf(data->compileInfo, sizeof(data->compileInfo), "%s (%s, " GIT_REVSTR ")", data->compileInfo,
yamcompiler);
data->emailCacheName = (STRPTR)EMAILCACHENAME;
while((tag = NextTagItem((APTR)&tags)) != NULL)
{
switch(tag->ti_Tag)
{
case ATTR(EMailCacheName) : data->emailCacheName = (char *)tag->ti_Data; break;
}
}
LoadEMailCache(data->emailCacheName, &data->emailCache);
if(GetTagData(ATTR(Hidden), FALSE, inittags(msg)) != FALSE)
set(obj, MUIA_Application_Iconified, TRUE);
DoMethod(obj, MUIM_Notify, MUIA_Application_DoubleStart, TRUE, MUIV_Notify_Self, 1, METHOD(PopUp));
}
return (IPTR)obj;
}
///
/// OVERLOAD(OM_DISPOSE)
OVERLOAD(OM_DISPOSE)
{
GETDATA;
// save the email cache if it was modified
if(data->emailCache.modified == TRUE)
SaveEMailCache(data->emailCacheName, &data->emailCache);
// lets free the EMailCache List ourself in here, to make it a bit cleaner.
ClearABook(&data->emailCache);
// then we call the supermethod to let
// MUI free the rest for us.
return DoSuperMethodA(cl, obj, msg);
}
///
/// OVERLOAD(OM_GET)
OVERLOAD(OM_GET)
{
GETDATA;
IPTR *store = ((struct opGet *)msg)->opg_Storage;
switch(((struct opGet *)msg)->opg_AttrID)
{
case ATTR(CompileInfo) : *store = (IPTR)data->compileInfo; return TRUE;
}
return DoSuperMethodA(cl, obj, msg);
}
///
/// OVERLOAD(OM_SET)
OVERLOAD(OM_SET)
{
GETDATA;
struct TagItem *tags = inittags(msg), *tag;
while((tag = NextTagItem((APTR)&tags)) != NULL)
{
switch(tag->ti_Tag)
{
// if the Application is going to be (un)iconified we
// have to take care of certain tasks.
case MUIA_Application_Iconified:
{
// in case we have an applicationID we make sure
// we notify application.library that YAM was uniconified
#if defined(__amigaos4__)
if(G->applicationID != 0)
{
struct TagItem hiddenTags[] =
{
{ APPATTR_Hidden, tag->ti_Data },
{ TAG_DONE, 0 }
};
// adapt the tag values for the different interface versions
if(IApplication->Data.Version >= 2)
{
if(APPATTR_Hidden < TAG_USER)
hiddenTags[0].ti_Tag += TAG_USER;
}
else
{
if(APPATTR_Hidden >= TAG_USER)
hiddenTags[0].ti_Tag -= TAG_USER;
}
SetApplicationAttrsA(G->applicationID, hiddenTags);
}
#endif
D(DBF_STARTUP, "application was %s", tag->ti_Data ? "iconified" : "uniconified");
data->iconified = tag->ti_Data;
if(data->iconified == TRUE)
DoMethod(obj, METHOD(FlushFolderIndexes), TRUE);
}
break;
case MUIA_Application_Active:
{
D(DBF_STARTUP, "application is %s", tag->ti_Data ? "active" : "inactive");
set(obj, MUIA_Application_Sleep, !tag->ti_Data);
}
break;
}
}
return DoSuperMethodA(cl, obj, msg);
}
///
/// OVERLOAD(MUIM_Application_ShowHelp)
OVERLOAD(MUIM_Application_ShowHelp)
{
char *helpFile = ((struct MUIP_Application_ShowHelp *)msg)->name;
ULONG result = 0;
ENTER();
// as we use the ShowHelp method to construct the correct URL to
// our online documentation we don't call DoSuperMethod() here but
// do all on ourselve.
// if helpFile is NULL we use the global one
if(helpFile == NULL)
helpFile = (char *)xget(obj, MUIA_Application_HelpFile);
// check that helpFile is definitly not null
if(helpFile != NULL)
{
// construct the URL from the HelpFile and the HelpNode
char *url = NULL;
char *helpNode = ((struct MUIP_Application_ShowHelp *)msg)->node;
const char *langCode = tr(MSG_DOC_LANGUAGE_CODE);
int rc;
if(helpNode != NULL)
{
if(IsStrEmpty(langCode))
rc = asprintf(&url, "%s/Documentation/%s", helpFile, helpNode);
else
rc = asprintf(&url, "%s/%s:Documentation/%s", helpFile, langCode, helpNode);
}
else
{
if(IsStrEmpty(langCode))
rc = asprintf(&url, "%s/Documentation", helpFile);
else
rc = asprintf(&url, "%s/%s:Documentation", helpFile, langCode);
}
if(rc != -1)
{
D(DBF_GUI, "opening help URL '%s'", url);
GotoURL(url, FALSE);
free(url);
}
else
E(DBF_GUI, "asprintf() failed");
}
else
W(DBF_GUI, "HelpFile is NULL");
RETURN(result);
return result;
}
///
/// OVERLOAD(MUIM_Application_PushMethod)
OVERLOAD(MUIM_Application_PushMethod)
{
struct MUIP_Application_PushMethod *pm = (struct MUIP_Application_PushMethod *)msg;
IPTR rc;
ENTER();
// MUI 3.8 does not yet support delayed execution of pushed methods. Hence the
// delay time must be stripped. Otherwise it will be treated as an aribitrarily
// large number of parameters.
if(LIB_VERSION_IS_AT_LEAST(MUIMasterBase, 20, 0) == FALSE)
pm->count &= 0x0000000f;
rc = DoSuperMethodA(cl, obj, msg);
RETURN(rc);
return rc;
}
///
/// DECLARE(UpdateCheck)
DECLARE(UpdateCheck) // ULONG quiet
{
ENTER();
// stop a possibly running timer
// the update check will reinitiate it if necessary
StopTimer(TIMER_UPDATECHECK);
// perform the update check and update our open GUI
// elements accordingly.
CheckForUpdates(msg->quiet);
RETURN(0);
return 0;
}
///
/// DECLARE(ShowError)
// show an error message and free() the pointer
// NOTE: the message must have been allocated by malloc() or similar!
DECLARE(ShowError) // char *message
{
ENTER();
ER_NewError(msg->message);
free(msg->message);
RETURN(0);
return 0;
}
///
/// DECLARE(ShowWarning)
// show a warning message and free() the pointer
// NOTE: the message must have been allocated by malloc() or similar!
DECLARE(ShowWarning) // char *message
{
ENTER();
ER_NewWarning(msg->message);
free(msg->message);
RETURN(0);
return 0;
}
///
/// DECLARE(BusyBegin)
DECLARE(BusyBegin) // ULONG type
{
return (IPTR)BusyBegin(msg->type);
}
///
/// DECLARE(BusyText)
DECLARE(BusyText) // APTR handle, const char *text, const char *param
{
ENTER();
BusyText(msg->handle, msg->text, msg->param);
RETURN(0);
return 0;
}
///
/// DECLARE(BusyProgress)
DECLARE(BusyProgress) // APTR handle, int progress, int max
{
return BusyProgress(msg->handle, msg->progress, msg->max);
}
///
/// DECLARE(BusyEnd)
DECLARE(BusyEnd) // APTR handle
{
ENTER();
BusyEnd(msg->handle);
RETURN(0);
return 0;
}
///
/// DECLARE(AppendToLogfile)
// NOTE: the log message must have been allocated by malloc() or similar!
DECLARE(AppendToLogfile) // const int mode, const int id, char *logMessage
{
ENTER();
AppendToLogfile((enum LFMode)msg->mode, msg->id, msg->logMessage);
free(msg->logMessage);
RETURN(0);
return 0;
}
///
/// DECLARE(ChangeFolder)
DECLARE(ChangeFolder) // struct Folder *folder, ULONG setActive
{
ENTER();
MA_ChangeFolder(msg->folder, msg->setActive);
RETURN(0);
return 0;
}
///
/// DECLARE(DisplayStatistics)
DECLARE(DisplayStatistics) // struct Folder *folder, ULONG updateAppIcon
{
ENTER();
DisplayStatistics(msg->folder, msg->updateAppIcon);
RETURN(0);
return 0;
}
///
/// DECLARE(CreateTransferGroup)
DECLARE(CreateTransferGroup) // APTR thread, const char *title, struct Connection *connection, ULONG flags
{
GETDATA;
Object *group = NULL;
ENTER();
// create a new transfer window if we don't have one yet
if(data->transferWindow == NULL)
{
BOOL activate;
D(DBF_GUI, "creating new transfer window");
if(isFlagSet(msg->flags, TWF_ACTIVATE))
activate = TRUE;
else if (isFlagSet(msg->flags, TWF_OPEN) || isFlagSet(msg->flags, TWF_FORCE_OPEN))
activate = TRUE;
else
activate = FALSE;
if((data->transferWindow = TransferWindowObject,
MUIA_Window_Activate, activate,
End) != NULL)
{
// enable the menu item
set(G->MA->GUI.MI_TRANSFERS, MUIA_Menuitem_Enabled, TRUE);
}
}
if(data->transferWindow != NULL)
{
D(DBF_GUI, "creating new transfer control group, title '%s'", msg->title);
if((group = (Object *)DoMethod(data->transferWindow, MUIM_TransferWindow_CreateTransferControlGroup, msg->title)) != NULL)
{
BOOL open;
// tell the control group about the thread and the connection being used
xset(group, MUIA_TransferControlGroup_Thread, msg->thread,
MUIA_TransferControlGroup_Connection, msg->connection);
// respect the user's settings for the transfer window
if(isFlagSet(msg->flags, TWF_FORCE_OPEN))
open = TRUE;
else if(C->TransferWindow != TWM_HIDE && isFlagSet(msg->flags, TWF_OPEN))
open = TRUE;
else
open = FALSE;
if(open == TRUE)
{
D(DBF_GUI, "visible transfer window is requested");
// open the window only once
if(xget(data->transferWindow, MUIA_Window_Open) == FALSE)
SafeOpenWindow(data->transferWindow);
}
}
}
RETURN((IPTR)group);
return (IPTR)group;
}
///
/// DECLARE(DeleteTransferGroup)
DECLARE(DeleteTransferGroup) // Object *transferGroup
{
GETDATA;
ENTER();
if(msg->transferGroup != NULL)
{
D(DBF_GUI, "removing transfer control group %08lx", msg->transferGroup);
if(xget(data->transferWindow, MUIA_TransferWindow_NumberOfControlGroups) == 1)
{
// we are about to remove the last group, just dispose the window instead
D(DBF_GUI, "closing transfer window");
set(data->transferWindow, MUIA_Window_Open, FALSE);
DoMethod(obj, OM_REMMEMBER, data->transferWindow);
MUI_DisposeObject(data->transferWindow);
data->transferWindow = NULL;
// disable the menu item
set(G->MA->GUI.MI_TRANSFERS, MUIA_Menuitem_Enabled, FALSE);
}
else
{
// remove the group and keep the others
DoMethod(data->transferWindow, MUIM_TransferWindow_DeleteTransferControlGroup, msg->transferGroup);
}
}
RETURN(0);
return 0;
}
///
/// DECLARE(ShowTransferWindow)
DECLARE(ShowTransferWindow)
{
GETDATA;
ENTER();
if(data->transferWindow != NULL && xget(data->transferWindow, MUIA_Window_Open) == FALSE)
SafeOpenWindow(data->transferWindow);
RETURN(0);
return 0;
}
///
/// DECLARE(SetStatusTo)
DECLARE(SetStatusTo) // struct Mail *mail, int addflags, int clearflags
{
ENTER();
MA_ChangeMailStatus(msg->mail, msg->addflags, msg->clearflags);
RETURN(0);
return 0;
}
///
/// DECLARE(StartMacro)
DECLARE(StartMacro) // enum Macro num, const char *param
{
ULONG rc = FALSE;
ENTER();
if(msg->num == MACRO_PROMPT_USER)
{
if(G->RexxHost != NULL)
{
struct FileReqCache *frc;
char scname[SIZE_COMMAND];
AddPath(scname, G->ProgDir, "rexx", sizeof(scname));
if((frc = ReqFile(ASL_REXX, G->MA->GUI.WI, tr(MSG_MA_EXECUTESCRIPT_TITLE), REQF_NONE, scname, "")))
{
AddPath(scname, frc->drawer, frc->file, sizeof(scname));
// only RexxSysBase v45+ seems to support properly quoted
// strings via the new RXFF_SCRIPT flag
if(LIB_VERSION_IS_AT_LEAST(RexxSysBase, 45, 0) == TRUE && MyStrChr(scname, ' ') != NULL)
{
char command[SIZE_COMMAND];
snprintf(command, sizeof(command), "\"%s\"", scname);
if(SendRexxCommand(G->RexxHost, command, 0) != NULL)
rc = TRUE;
}
else
{
if(SendRexxCommand(G->RexxHost, scname, 0) != NULL)
rc = TRUE;
}
}
}
else
E(DBF_REXX, "no RexxHost, cannot execute Arexx scripts");
}
else
{
struct RxHook *macro = &C->RX[msg->num];
if(IsStrEmpty(macro->Script) == FALSE)
{
char command[SIZE_LARGE];
char *s = macro->Script;
char *p;
command[0] = '\0';
// now we check if the script command contains
// the '%p' placeholder and if so we go and replace
// it with our parameter
while((p = strstr(s, "%p")) != NULL)
{
strlcat(command, s, MIN(p-s+1, (LONG)sizeof(command)));
if(msg->param != NULL)
strlcat(command, msg->param, sizeof(command));
s = p+2;
}
// add the rest
strlcat(command, s, sizeof(command));
// check if the script in question is an amigados
// or arexx script
if(macro->IsAmigaDOS == TRUE)
{
struct BusyNode *busy;
// now execute the command
busy = BusyBegin(BUSY_TEXT);
BusyText(busy, tr(MSG_MA_EXECUTINGCMD), "");
LaunchCommand(command, macro->WaitTerm ? 0 : LAUNCHF_ASYNC, macro->UseConsole ? OUT_STDOUT : OUT_NIL);
BusyEnd(busy);
rc = TRUE;
}
else if(G->RexxHost != NULL) // make sure that rexx it available
{
BPTR fh;
// prepare the command string
// only RexxSysBase v45+ seems to support properly quoted
// strings via the new RXFF_SCRIPT flag
if(LIB_VERSION_IS_AT_LEAST(RexxSysBase, 45, 0) == FALSE)
UnquoteString(command, FALSE);
// make sure to open the output console handler
if((fh = Open(macro->UseConsole ? "CON:////YAM ARexx Window/AUTO" : "NIL:", MODE_NEWFILE)) != ZERO)
{
struct RexxMsg *sentrm;
// execute the Arexx command
if((sentrm = SendRexxCommand(G->RexxHost, command, fh)) != NULL)
{
// if the user wants to wait for the termination
// of the script, we do so...
SHOWVALUE(DBF_REXX, macro->WaitTerm);
if(macro->WaitTerm == TRUE)
{
struct BusyNode *busy;
struct RexxMsg *rm;
BOOL waiting = TRUE;
busy = BusyBegin(BUSY_TEXT);
BusyText(busy, tr(MSG_MA_EXECUTINGCMD), "");
do
{
WaitPort(G->RexxHost->port);
while((rm = (struct RexxMsg *)GetMsg(G->RexxHost->port)) != NULL)
{
if((rm->rm_Action & RXCODEMASK) != RXCOMM)
ReplyMsg((struct Message *)rm);
else if(rm->rm_Node.mn_Node.ln_Type == NT_REPLYMSG)
{
struct RexxMsg *org = (struct RexxMsg *)rm->rm_Args[15];
if(org != NULL)
{
if(rm->rm_Result1 != 0)
ReplyRexxCommand(org, 20, ERROR_NOT_IMPLEMENTED, NULL);
else
ReplyRexxCommand(org, 0, 0, (char *)rm->rm_Result2);
}
if(rm == sentrm)
{
if(rm->rm_Result1 == 0)
rc = TRUE;
else
ER_NewError(tr(MSG_ER_AREXX_EXECUTION_ERROR), rm->rm_Args[0], rm->rm_Result1);
waiting = FALSE;
}
FreeRexxCommand(rm);
--G->RexxHost->replies;
}
else if(rm->rm_Args[0] != 0)
DoRXCommand(G->RexxHost, rm);
else
ReplyMsg((struct Message *)rm);
}
}
while(waiting);
BusyEnd(busy);
}
rc = TRUE;
D(DBF_REXX, "finished");
}
else
{
Close(fh);
ER_NewError(tr(MSG_ER_ErrorARexxScript), command);
}
}
else
ER_NewError(tr(MSG_ER_ErrorConsole));
}
else
E(DBF_REXX, "no RexxHost, cannot execute Arexx script '%ld'", macro->Script);
}
}
RETURN(rc);
return rc;
}
///
/// DECLARE(MoveCopyMail)
DECLARE(MoveCopyMail) // struct Mail *mail, struct Folder *tobox, const char *originator, ULONG flags
{
ENTER();
MA_MoveCopy(msg->mail, msg->tobox, msg->originator, msg->flags);
RETURN(0);
return 0;
}
///
/// DECLARE(DeleteMail)
DECLARE(DeleteMail) // struct Mail *mail, ULONG flags
{
ENTER();
MA_DeleteSingle(msg->mail, msg->flags);
RETURN(0);
return 0;
}
///
/// DECLARE(FilterMail)
DECLARE(FilterMail) // const struct MinList *filterList, struct Mail *mail
{
return FI_FilterSingleMail(msg->filterList, msg->mail, NULL, NULL);
}
///
/// DECLARE(FilterNewMails)
DECLARE(FilterNewMails) // const struct MailList *mailList, struct FilterResult *filterResult
{
ENTER();
FilterMails(msg->mailList, APPLY_AUTO, msg->filterResult);
// Now we jump to the first new mail we received if the number of messages has changed
// after the mail transfer
if(C->JumpToIncoming == TRUE)
DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_JumpToFirstNewMailOfFolder, GetCurrentFolder());
// only call the DisplayStatistics() function if the actual folder wasn't already the INCOMING
// one or we would have refreshed it twice
if(GetCurrentFolder() != NULL && !isIncomingFolder(GetCurrentFolder()))
DisplayStatistics((struct Folder *)-1, TRUE);
else
UpdateAppIcon();
RETURN(0);
return 0;
}
///
/// DECLARE(UpdateAppIcon)
DECLARE(UpdateAppIcon)
{
ENTER();
UpdateAppIcon();
RETURN(0);
return 0;
}
///
/// DECLARE(NewMailAlert)
// Notifies user when new mail is available
DECLARE(NewMailAlert) // struct MailServerNode *msn, struct DownloadResult *downloadResult, struct FilterResult *filterResult, const ULONG flags
{
GETDATA;
ENTER();
SHOWSTRING(DBF_NET, msg->msn->description);
SHOWVALUE(DBF_NET, msg->downloadResult->downloaded);
SHOWVALUE(DBF_NET, msg->filterResult->Spam);
SHOWVALUE(DBF_GUI, msg->flags);
// show the statistics only if we downloaded some mails at all,
// and not all of them were spam mails
if(msg->downloadResult->downloaded > 0 && msg->downloadResult->downloaded > msg->filterResult->Spam)
{
if(msg->msn->notifyByRequester == TRUE && isFlagClear(msg->flags, RECEIVEF_AREXX))
{
char buffer[SIZE_LARGE];
// make sure the application isn't iconified
if(data->iconified == TRUE)
PopUp();
snprintf(buffer, sizeof(buffer), tr(MSG_POP3_NEW_MAIL_NOTIFY_REQ), msg->msn->description, msg->downloadResult->downloaded, msg->downloadResult->onServer-msg->downloadResult->deleted, msg->downloadResult->dupeSkipped);
if(C->SpamFilterEnabled == TRUE)
{
// include the number of spam classified mails
snprintf(&buffer[strlen(buffer)], sizeof(buffer)-strlen(buffer), tr(MSG_TR_FILTER_STATS_SPAM),
msg->filterResult->Checked,
msg->filterResult->Redirected,
msg->filterResult->Forwarded,
msg->filterResult->Replied,
msg->filterResult->Executed,
msg->filterResult->Moved,
msg->filterResult->Deleted,
msg->filterResult->Spam);
}
else
{
snprintf(&buffer[strlen(buffer)], sizeof(buffer)-strlen(buffer), tr(MSG_TR_FilterStats),
msg->filterResult->Checked,
msg->filterResult->Redirected,
msg->filterResult->Forwarded,
msg->filterResult->Replied,
msg->filterResult->Executed,
msg->filterResult->Moved,
msg->filterResult->Deleted);
}
// show the info window.
InfoWindowObject,
MUIA_Window_Title, tr(MSG_TR_NewMail),
MUIA_Window_RefWindow, G->MA->GUI.WI,
MUIA_Window_Activate, isFlagSet(msg->flags, RECEIVEF_USER),
MUIA_InfoWindow_Body, buffer,
End;
}
#if defined(__amigaos4__)
if(msg->msn->notifyByOS41System == TRUE)
{
D(DBF_GUI, "appID is %ld, application.lib is V%ld.%ld (needed V%ld.%ld)", G->applicationID, ApplicationBase->lib_Version, ApplicationBase->lib_Revision, 53, 7);
// Notify() is V53.2+, but 53.7 fixes some serious issues
if(G->applicationID > 0 && LIB_VERSION_IS_AT_LEAST(ApplicationBase, 53, 7) == TRUE)
{
// 128 chars is the current maximum :(
char message[128];
char imagePath[SIZE_PATHFILE];
int count = msg->downloadResult->downloaded - msg->filterResult->Spam;
// distinguish between single and multiple mails
if(count == 1)
snprintf(message, sizeof(message), tr(MSG_POP3_NEW_MAIL_NOTIFY_OS4_ONE), msg->msn->description);
else
snprintf(message, sizeof(message), tr(MSG_POP3_NEW_MAIL_NOTIFY_OS4_MANY), msg->msn->description, count);
AddPath(imagePath, G->ThemesDir, "default/notify", sizeof(imagePath));
// We require 53.7+. From this version on proper tag values are used, hence there
// is no need to distinguish between v1 and v2 interfaces here as we have to do for
// other application.lib functions.
Notify(G->applicationID,
APPNOTIFY_Title, (uint32)"YAM",
APPNOTIFY_PubScreenName, (uint32)"FRONT",
APPNOTIFY_Text, (uint32)message,
APPNOTIFY_CloseOnDC, TRUE,
APPNOTIFY_BackMsg, (uint32)"POPUP",
APPNOTIFY_ImageFile, (uint32)imagePath,
TAG_DONE);
}
}
#endif // __amigaos4__
if(msg->msn->notifyByCommand == TRUE)
LaunchCommand(msg->msn->notifyCommand, 0, OUT_STDOUT);
if(msg->msn->notifyBySound == TRUE)
PlaySound(msg->msn->notifySound);
}
RETURN(0);
return 0;
}
///
/// DECLARE(ChangeSelected)
DECLARE(ChangeSelected) // const struct Folder *folder, const ULONG forceUpdate
{
ENTER();
if(msg->folder == GetCurrentFolder())
MA_ChangeSelected(msg->forceUpdate);
RETURN(0);
return 0;
}
///
/// DECLARE(CreatePasswordWindow)
DECLARE(CreatePasswordWindow) // APTR thread, const char *title, const char *body, ULONG maxLength
{
GETDATA;
Object *window;
ENTER();
if((window = StringRequestWindowObject,
MUIA_Window_Title, msg->title,
MUIA_Window_RefWindow, G->MA->GUI.WI,
MUIA_StringRequestWindow_Body, msg->body,
MUIA_StringRequestWindow_YesText, tr(MSG_Okay),
MUIA_StringRequestWindow_NoText, tr(MSG_Cancel),
MUIA_StringRequestWindow_MaxLength, msg->maxLength,
MUIA_StringRequestWindow_Secret, TRUE,
MUIA_StringRequestWindow_Thread, msg->thread,
End) != NULL)
{
// make sure the application isn't iconified
if(data->iconified == TRUE)
PopUp();
set(window, MUIA_Window_Open, TRUE);
}
RETURN((IPTR)window);
return (IPTR)window;
}
///
/// DECLARE(CreatePreselectionWindow)
DECLARE(CreatePreselectionWindow) // APTR thread, const char *title, LONG sizeLimit, enum PreselectionWindowMode mode, struct MinList *mailList
{
GETDATA;
Object *window;
ENTER();
if((window = PreselectionWindowObject,
MUIA_Window_Title, msg->title,
MUIA_PreselectionWindow_Thread, msg->thread,
MUIA_PreselectionWindow_Mode, msg->mode,
MUIA_PreselectionWindow_Mails, msg->mailList,
MUIA_PreselectionWindow_SizeLimit, msg->sizeLimit,
End) != NULL)
{
// make sure the application isn't iconified
if(data->iconified == TRUE)
PopUp();
set(window, MUIA_Window_Open, TRUE);
}
RETURN((IPTR)window);
return (IPTR)window;
}
///
/// DECLARE(DisposeWindow)
DECLARE(DisposeWindow) // Object *window
{
ENTER();
if(msg->window != NULL)
{
set(msg->window, MUIA_Window_Open, FALSE);
DoMethod(obj, OM_REMMEMBER, msg->window);
MUI_DisposeObject(msg->window);
}
RETURN(0);
return 0;
}
///
/// DECLARE(GotoURL)
// invoke the GotoURL() function with the given parameters
// NOTE: the error message must have been allocated by malloc() or similar!
DECLARE(GotoURL) // char *url, ULONG newWindow
{
ENTER();
GotoURL(msg->url, (BOOL)msg->newWindow);
free(msg->url);
RETURN(0);
return 0;
}
///
/// DECLARE(PopUp)
DECLARE(PopUp)
{
ENTER();
if(G->MA != NULL && G->MA->GUI.WI != NULL)
PopUp();
RETURN(0);
return 0;
}
///
/// DECLARE(MUIRequestA)
// call MUI requester
DECLARE(MUIRequestA) // Object *app, Object *win, LONG flags, const char *title, const char *gadgets, const char *reqtxt
{
LONG result;
ENTER();
result = YAMMUIRequestA(msg->app,
msg->win,
msg->flags,
msg->title,
msg->gadgets,
msg->reqtxt);
RETURN(result);
return result;
}
///
/// DECLARE(CertWarningRequest)
//
DECLARE(CertWarningRequest) // struct Connection *conn, struct Certificate *cert
{
BOOL result;
ENTER();
result = CertWarningRequest(msg->conn, msg->cert);
RETURN(result);
return result;
}
///
/// DECLARE(CleanupReadMailData)
// free the ReadMailData structure of a recently closed read window
DECLARE(CleanupReadMailData) // struct ReadMailData *rmData
{
GETDATA;
ENTER();
// only if this is not a close operation because the application
// is getting iconified we really cleanup our readmail data
if(msg->rmData == G->ActiveRexxRMData || data->iconified == FALSE)
{
// calls the CleanupReadMailData to clean everything else up
CleanupReadMailData(msg->rmData, TRUE);
}
RETURN(0);
return 0;
}
///
/// DECLARE(CleanupWriteMailData)
// free the WriteMailData structure of a recently closed write window
DECLARE(CleanupWriteMailData) // struct WriteMailData *wmData
{
GETDATA;
ENTER();
// only if this is not a close operation because the application
// is getting iconified we really cleanup our writemail data
if(msg->wmData == G->ActiveRexxWMData || data->iconified == FALSE)
{
// calls the CleanupWriteMailData to clean everything else up
CleanupWriteMailData(msg->wmData);
}
RETURN(0);
return 0;
}
///
/// DECLARE(OpenAboutWindow)
// show the about window
DECLARE(OpenAboutWindow)
{
GETDATA;
ENTER();
// create the about window object and open it
if(data->aboutWindow == NULL)
{
data->aboutWindow = AboutWindowObject, End;
if(data->aboutWindow != NULL)
DoMethod(data->aboutWindow, MUIM_Notify, MUIA_Window_Open, FALSE, obj, 4, MUIM_Application_PushMethod,obj, 1, MUIM_YAMApplication_CloseAboutWindow);
}
if(data->aboutWindow != NULL)
SafeOpenWindow(data->aboutWindow);
RETURN(0);
return 0;
}
///
/// DECLARE(CloseAboutWindow)
// close the about window
DECLARE(CloseAboutWindow)
{
GETDATA;
ENTER();
DoMethod(obj, MUIM_YAMApplication_DisposeWindow, data->aboutWindow);
data->aboutWindow = NULL;
RETURN(0);
return 0;
}
///
/// DECLARE(OpenConfigWindow)
DECLARE(OpenConfigWindow)
{
struct BusyNode *busy;
ENTER();
busy = BusyBegin(BUSY_TEXT);
BusyText(busy, tr(MSG_BUSY_OPENINGCONFIG), "");
if(G->ConfigWinObject == NULL)
{
if((CE = AllocConfig()) != NULL)
{
if(CopyConfig(CE, C) == TRUE)
{
G->ConfigWinObject = ConfigWindowObject, End;
}
}
}
if(G->ConfigWinObject != NULL)
{
SafeOpenWindow(G->ConfigWinObject);
}
else
{
// inform the user by chiming the bells about the failure
DisplayBeep(_screen(obj));
FreeConfig(CE);
CE = NULL;
}
BusyEnd(busy);
RETURN(0);
return 0;
}
///
/// DECLARE(CloseConfigWindow)
DECLARE(CloseConfigWindow)
{
ENTER();
DoMethod(obj, MUIM_YAMApplication_DisposeWindow, G->ConfigWinObject);
G->ConfigWinObject = NULL;
RETURN(0);
return 0;
}
///
/// DECLARE(OpenSearchMailWindow)
DECLARE(OpenSearchMailWindow) // struct Folder *folder
{
ENTER();
if(G->SearchMailWinObject == NULL)
G->SearchMailWinObject = SearchMailWindowObject, End;
if(G->SearchMailWinObject != NULL)
DoMethod(G->SearchMailWinObject, MUIM_SearchMailWindow_Open, msg->folder);
RETURN(0);
return 0;
}
///
/// DECLARE(OpenAddressBookWindow)
DECLARE(OpenAddressBookWindow) // enum AddressbookMode mode, LONG windowNumber, Object *recipientObj
{
BOOL result = FALSE;
ENTER();
if(G->ABookWinObject == NULL)
G->ABookWinObject = AddressBookWindowObject, End;
if(G->ABookWinObject != NULL)
{
DoMethod(G->ABookWinObject, MUIM_AddressBookWindow_Open, msg->mode, msg->windowNumber, msg->recipientObj);
result = TRUE;
}
RETURN(result);
return result;
}
///
/// DECLARE(EmptyTrashFolder)
DECLARE(EmptyTrashFolder) // ULONG quiet
{
struct Folder *trashFolder;
ENTER();
if((trashFolder = FO_GetFolderByType(FT_TRASH, NULL)) != NULL && MA_GetIndex(trashFolder) == TRUE)
{
struct BusyNode *busy;
struct MailNode *mnode;
int count;
int deleted;
busy = BusyBegin(BUSY_PROGRESS);
BusyText(busy, tr(MSG_BusyEmptyingTrash), "");
// clear the readmail group ahead of the deletion
if(C->EmbeddedReadPane == TRUE)
DoMethod(G->MA->GUI.MN_EMBEDDEDREADPANE, MUIM_ReadMailGroup_Clear, MUIF_NONE);
LockMailList(trashFolder->messages);
D(DBF_STARTUP, "emptying trash folder '%s' with %ld mails", trashFolder->Name, trashFolder->messages->count);
count = 0;
deleted = 0;
ForEachMailNode(trashFolder->messages, mnode)
{
struct Mail *mail = mnode->mail;
char mailfile[SIZE_PATHFILE];
BusyProgress(busy, ++count, trashFolder->Total);
AppendToLogfile(LF_VERBOSE, 21, tr(MSG_LOG_DeletingVerbose), AddrName(mail->From), mail->Subject, trashFolder->Name);
GetMailFile(mailfile, sizeof(mailfile), NULL, mail);
if(DeleteFile(mailfile) == DOSFALSE)
{
#if defined(DEBUG)
LONG error = IoErr();
E(DBF_STARTUP, "failed to delete file '%s', error %ld", mailfile, error);
#endif
}
else
{
deleted++;
}
}
// we only clear the trash folder if it wasn't empty anyway..
D(DBF_STARTUP, "deleted %ld mails from trash folder '%s'", deleted, trashFolder->Name);
if(deleted > 0)
{
ClearFolderMails(trashFolder, TRUE);
MA_ExpireIndex(trashFolder);
if(GetCurrentFolder() == trashFolder)
DoMethod(G->MA->GUI.PG_MAILLIST, MUIM_MainMailListGroup_DisplayMailsOfFolder, trashFolder);
AppendToLogfile(LF_NORMAL, 20, tr(MSG_LOG_Deleting), deleted, trashFolder->Name);
if(msg->quiet == FALSE)
DisplayStatistics(trashFolder, TRUE);
MA_ChangeSelected(FALSE);
}
UnlockMailList(trashFolder->messages);
BusyEnd(busy);
}
else
{
W(DBF_STARTUP, "no trash folder found or failed to get its index");
}
RETURN(0);
return 0;
}
///
/// DECLARE(DeleteOldMails)
DECLARE(DeleteOldMails)
{
struct TimeVal today;
BOOL mailsDeleted = FALSE;
struct MailList *toBeDeletedList;
ENTER();
GetSysTimeUTC(&today);
// round the current time to full days
today.Seconds = (today.Seconds + 86399) & ~86400;
today.Microseconds = 0;
// we need a temporary "to be deleted" list of mails to avoid doubly locking a folder's mail list
if((toBeDeletedList = CreateMailList()) != NULL)
{
struct BusyNode *busy;
ULONG f;
struct FolderNode *fnode;
ULONG delFlags = (C->RemoveOnQuit == TRUE) ? DELF_AT_ONCE : 0;
struct Folder *currentFolder = GetCurrentFolder();
busy = BusyBegin(BUSY_PROGRESS_ABORT);
BusyText(busy, tr(MSG_BusyDeletingOld), "");
LockFolderListShared(G->folders);
f = 0;
ForEachFolderNode(G->folders, fnode)
{
struct Folder *folder = fnode->folder;
if(isGroupFolder(folder) == FALSE && folder->MaxAge > 0 && !isArchiveFolder(folder) && !isDraftsFolder(folder) && MA_GetIndex(folder) == TRUE)
{
struct MailNode *mnode;
struct TimeVal ageLimit;
// calculate the age limit for this folder
ageLimit.Seconds = today.Seconds - folder->MaxAge * 86400;
ageLimit.Microseconds = 0;
LockMailList(folder->messages);
// initialize the list of mails to be deleted
InitMailList(toBeDeletedList);
ForEachMailNode(folder->messages, mnode)
{
struct Mail *mail = mnode->mail;
if(CmpTime(TIMEVAL(&ageLimit), TIMEVAL(&mail->transDate)) < 0)
{
BOOL deleteMail;
// Delete any message from trash and spam folder automatically
// or if the message is read already (keep unread messages).
// "Marked" messages will never be deleted automatically.
if(isTrashFolder(folder) || isSpamFolder(folder))
{
// old mails in the trash and spam folders are deleted in any case
deleteMail = TRUE;
}
else if(!hasStatusNew(mail) && !hasStatusMarked(mail) && hasStatusRead(mail))
{
// delete old mails if they are read already, but respect marked mails
deleteMail = TRUE;
}
else if(folder->ExpireUnread == TRUE && !hasStatusMarked(mail))
{
// delete old mails if the folder's configuration allows us to do that, but
// respect marked mails
deleteMail = TRUE;
}
else
{
// keep the mail if it is either unread, marked or not yet old enough
deleteMail = FALSE;
}
// put the mail in the "to be deleted" list if it may be deleted
if(deleteMail == TRUE)
AddNewMailNode(toBeDeletedList, mail);
}
}
UnlockMailList(folder->messages);
if(IsMailListEmpty(toBeDeletedList) == FALSE)
{
// "mute" the main mail list for the current folder to avoid a redraw for
// every single deleted mail
if(folder == currentFolder)
set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, TRUE);
// no need to lock the "to be deleted" list as this is known in this function only.
// Iterate through the list "by foot" as we remove the nodes, ForEachMailNode() is
// not safe to call here!
while((mnode = TakeMailNode(toBeDeletedList)) != NULL)
{
// Finally delete the mail. Removing/freeing the mail from the folder's list of mails
// is in fact done by the MA_DeleteSingle() function itself.
MA_DeleteSingle(mnode->mail, delFlags|DELF_QUIET);
// remember that we deleted at least one mail
mailsDeleted = TRUE;
// delete the mail node itself
DeleteMailNode(mnode);
}
// "unmute" the main mail list again
if(folder == currentFolder)
set(G->MA->GUI.PG_MAILLIST, MUIA_NList_Quiet, FALSE);
DisplayStatistics(folder, FALSE);
}
}
// if BusyProgress() returns FALSE, then the user aborted
if(BusyProgress(busy, ++f, G->folders->count) == FALSE)
{
// abort the loop
break;
}
}
UnlockFolderList(G->folders);
// delete the "to be deleted" list
DeleteMailList(toBeDeletedList);
BusyEnd(busy);
}
// MA_DeleteSingle() does not update the trash folder treeitem if something was deleted from
// another folder, because it was advised to be quiet. So we must refresh the trash folder
// tree item manually here to get an up-to-date folder treeview.
if(mailsDeleted == TRUE)
{
struct Folder *trashFolder;
trashFolder = FO_GetFolderByType(FT_TRASH, NULL);
// only update the trash folder item if it is not the active one, as the active one
// will be updated below
if(GetCurrentFolder() != trashFolder)
DisplayStatistics(trashFolder, FALSE);
}
// and last but not least we update the appIcon also
DisplayStatistics(NULL, TRUE);
RETURN(0);
return 0;
}
///
/// DECLARE(DeleteSpamMails)
DECLARE(DeleteSpamMails) // ULONG quiet
{
ENTER();
if(GetCurrentFolder() != NULL && isGroupFolder(GetCurrentFolder()) == FALSE)
{
ULONG delFlags;
struct BusyNode *busy;
struct MailList *mlist;
delFlags = (msg->quiet == FALSE) ? DELF_CLOSE_WINDOWS : DELF_QUIET|DELF_CLOSE_WINDOWS;
// show an interruptable Busy gauge
busy = BusyBegin(BUSY_PROGRESS_ABORT);
BusyText(busy, tr(MSG_MA_BUSYEMPTYINGSPAM), "");
// get the complete mail list of the spam folder
if((mlist = MA_CreateFullList(GetCurrentFolder(), FALSE)) != NULL)
{
struct MailNode *mnode;
ULONG i;
i = 0;
ForEachMailNode(mlist, mnode)
{
struct Mail *mail = mnode->mail;
// if BusyProgress() returns FALSE, then the user aborted
if(BusyProgress(busy, ++i, mlist->count) == FALSE)
break;
// not every mail in the a folder *must* be spam
// so better check this
if(hasStatusSpam(mail))
{
// remove the spam mail from the folder and take care to
// remove it immediately in case this is the SPAM folder, otherwise
// the mail will be moved to the trash first. In fact, DeleteSingle()
// takes care of that itself.
MA_DeleteSingle(mail, delFlags);
}
}
if(msg->quiet == FALSE)
DisplayStatistics(GetCurrentFolder(), TRUE);
// finally free the mail list
DeleteMailList(mlist);
}
BusyEnd(busy);
}
RETURN(0);
return 0;
}
///
/// DECLARE(RebuildFolderIndex)
DECLARE(RebuildFolderIndex)
{
struct Folder *folder;
ENTER();
folder = GetCurrentFolder();
// on groups we don't allow any index rescanning operation
if(folder != NULL && isGroupFolder(folder) == FALSE)
{
// we start a rescan by expiring the current index and issueing
// a new MA_GetIndex(). That will also cause the GUI to refresh!
folder->LoadedMode = LM_UNLOAD;
MA_ExpireIndex(folder);
if(MA_GetIndex(folder) == TRUE)
{
// if we are still in the folder we wanted to rescan,
// we can refresh the list.
if(folder == GetCurrentFolder())
MA_ChangeFolder(NULL, FALSE);
}
}
RETURN(0);
return 0;
}
///
/// DECLARE(FlushFolderIndexes)
DECLARE(FlushFolderIndexes) // ULONG force
{
struct FolderNode *fnode;
ENTER();
D(DBF_FOLDER, "flush indexes %ld", msg->force);
LockFolderListShared(G->folders);
ForEachFolderNode(G->folders, fnode)
{
struct Folder *folder = fnode->folder;
if(isGroupFolder(folder) == FALSE)
{
if(msg->force == TRUE)
{
FlushIndex(folder, 0);
}
else
{
if(C->ExpungeIndexes == 0)
FlushIndex(folder, 1);
else
FlushIndex(folder, GetDateStamp() - C->ExpungeIndexes);
}
}
}
UnlockFolderList(G->folders);
RETURN(0);
return 0;
}
///
/// DECLARE(SaveLayout)
DECLARE(SaveLayout) // ULONG permanent
{
struct List *windowList;
ENTER();
if((windowList = (struct List *)xget(obj, MUIA_Application_WindowList)) != NULL)
{
Object *window;
Object *cstate = (Object *)windowList->lh_Head;
// trigger a snapshot action on all currently alive windows
while((window = NextObject(&cstate)) != NULL)
DoMethod(window, MUIM_Window_Snapshot, TRUE);
}
// finally save the layout of certain groups
SaveLayout(msg->permanent);
RETURN(0);
return 0;
}
///