457 lines
14 KiB
C
457 lines
14 KiB
C
|
/*
|
||
|
* Windows Calendar
|
||
|
* Copyright (c) 1985 by Microsoft Corporation, all rights reserved.
|
||
|
* Written by Mark L. Chamberlin, consultant to Microsoft.
|
||
|
*
|
||
|
****** calfile.c
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "cal.h"
|
||
|
|
||
|
|
||
|
/**** CreateChangeFile *****/
|
||
|
VOID APIENTRY CreateChangeFile ()
|
||
|
{
|
||
|
/* If there is already a change file, delete it, ignoring any errors
|
||
|
since we will be creating a new one, and that's the important one.
|
||
|
*/
|
||
|
DeleteChangeFile ();
|
||
|
|
||
|
/* Set the end-of-data of the change file to block 0. */
|
||
|
vobkEODChange = 0;
|
||
|
|
||
|
/* By passing the drive letter as 0 we tell GetTempFileName
|
||
|
to decide where to put the temp file.
|
||
|
*/
|
||
|
if (!(vfChangeFile = FCreateTempFile (IDFILECHANGE, 0)))
|
||
|
{
|
||
|
/* Post error saying edits will not be recorded. */
|
||
|
OutputDebugString ("Message Box Is Broken\n");
|
||
|
|
||
|
//AlertBox (vszNoCreateChangeFile, (CHAR *)NULL,
|
||
|
//MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**** DeleteChangeFile - delete the change file if there is one. ****/
|
||
|
VOID APIENTRY DeleteChangeFile ()
|
||
|
{
|
||
|
if (vfChangeFile)
|
||
|
{
|
||
|
/* Ignore errors since callers don't care about them. */
|
||
|
vfChangeFile = FALSE;
|
||
|
FDeleteFile (IDFILECHANGE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**** FCreateTempFile ****/
|
||
|
|
||
|
BOOL APIENTRY FCreateTempFile (
|
||
|
INT idFile,
|
||
|
INT iDrive) /* 0 means let GetTempFileName decide where to put
|
||
|
the temp file.
|
||
|
Otherwise, this is the drive letter, and it should
|
||
|
also have the TF_FORCEDRIVE bit set to make sure that
|
||
|
the temp file is created on the specified drive.
|
||
|
*/
|
||
|
{
|
||
|
CHAR szFileSpec [CCHFILESPECMAX];
|
||
|
INT FileHandle;
|
||
|
|
||
|
/* Create a temp file with a unique name.
|
||
|
* 0 for the third parameter means GetTempFileName should
|
||
|
* produce a unique file name and create the file.
|
||
|
* GetTempFileName returns the random number it used, which Steve Wood
|
||
|
* guarantees is 0 iff the call fails (he does not allow the random
|
||
|
* number to be 0). If the file is created OK by GetTempFileName,
|
||
|
* open it to set up the reopen buffer.
|
||
|
*/
|
||
|
if (MGetTempFileName ((BYTE)iDrive, "CAL", 0, szFileSpec) == 0)
|
||
|
{
|
||
|
return (FALSE);
|
||
|
}
|
||
|
|
||
|
if ((FileHandle = MOpenFile (szFileSpec, &OFStruct [idFile],
|
||
|
OF_READWRITE )) != -1)
|
||
|
{
|
||
|
/* File is OK. Close it, and return TRUE. */
|
||
|
M_lclose (FileHandle);
|
||
|
return (TRUE);
|
||
|
}
|
||
|
|
||
|
/* GetTempFileName failed or OpenFile failed. */
|
||
|
return (FALSE);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**** FFreeUpDr - Free up the specified DR. ****/
|
||
|
|
||
|
BOOL APIENTRY FFreeUpDr (
|
||
|
DR *pdr, /* Pointer to the DR to be written out. */
|
||
|
DL *pdl) /* OUTPUT - DL indicating where the occupant was put. */
|
||
|
{
|
||
|
DL dlNew;
|
||
|
|
||
|
if (!pdr -> fDirty)
|
||
|
{
|
||
|
/* It's not dirty so don't change the date's location. */
|
||
|
*pdl = DLNOCHANGE;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
|
||
|
/* The DR is dirty. However, it may be empty, in which case
|
||
|
we tell the caller it is not on disk.
|
||
|
*/
|
||
|
if (pdr -> cbNotes + pdr -> cbTqr == 0)
|
||
|
{
|
||
|
*pdl = DLNIL;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
|
||
|
/* It's not empty. */
|
||
|
if (!vfChangeFile)
|
||
|
{
|
||
|
/* Tell the user that the edits are not being recorded.
|
||
|
We already warned the turkey when we couldn't create
|
||
|
the change file, but he didn't listen. Now just
|
||
|
throw away his edits.
|
||
|
*/
|
||
|
AlertBox (vszNoChangeFile,
|
||
|
(CHAR *)NULL, MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
|
||
|
*pdl = DLNOCHANGE;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
|
||
|
/* Set up the new DL using vobkEODChange before FWriteDrToFile
|
||
|
changes it.
|
||
|
*/
|
||
|
dlNew = DLFCHANGEFILEMASK | vobkEODChange;
|
||
|
if (FWriteDrToFile (TRUE, IDFILECHANGE, pdr))
|
||
|
{
|
||
|
/* The write was successful - tell the caller about the
|
||
|
new DL.
|
||
|
*/
|
||
|
*pdl = dlNew;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* An error occured while attempting to write the date. */
|
||
|
AlertBox (vszErrorWritingChanges, (CHAR *)NULL,
|
||
|
MB_APPLMODAL | MB_OK | MB_ICONHAND);
|
||
|
return (FALSE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**** FWriteDrToFile *****/
|
||
|
BOOL APIENTRY FWriteDrToFile (
|
||
|
BOOL fOpenClose, /* If TRUE, the file must be opened and then closed.
|
||
|
If FALSE, the file is already open and should be
|
||
|
left open.
|
||
|
*/
|
||
|
INT idFile, /* Which file to write to. */
|
||
|
DR *pdr) /* Which DR to write from. */
|
||
|
{
|
||
|
INT *pobkEOD;
|
||
|
INT cbkTransfer;
|
||
|
INT cbTransfer;
|
||
|
INT FileHandle;
|
||
|
BOOL fOk;
|
||
|
|
||
|
/* Set up a pointer to the appropriate EOD. */
|
||
|
pobkEOD = &vobkEODNew;
|
||
|
|
||
|
if (idFile == IDFILECHANGE)
|
||
|
pobkEOD = &vobkEODChange;
|
||
|
|
||
|
/* Try to reopen the file. */
|
||
|
if (!fOpenClose || FReopenFile (idFile, OF_PROMPT | OF_CANCEL | OF_REOPEN | OF_READWRITE))
|
||
|
{
|
||
|
/* Make a local copy of the file handle to save code below. */
|
||
|
FileHandle = hFile [idFile];
|
||
|
|
||
|
/* Calculate the minimum number of BKs we must
|
||
|
write out. Do this by taking the count of
|
||
|
bytes in use in the DR and adding the count
|
||
|
of bytes in a BK minus 1 in order to round up
|
||
|
to the next BK. Then divide by the count
|
||
|
of bytes in a BK to get the number of BKs
|
||
|
to be written.
|
||
|
*/
|
||
|
cbkTransfer = (pdr -> cbNotes + pdr -> cbTqr
|
||
|
+ CBDRHEAD + CBBK - 1) / CBBK;
|
||
|
|
||
|
/* Clear the reserved word. */
|
||
|
pdr -> wReserved = 0;
|
||
|
|
||
|
/* Seek to the current end of data, write the
|
||
|
current DR, and close the file.
|
||
|
*/
|
||
|
cbTransfer = CBBK * cbkTransfer;
|
||
|
fOk = M_llseek (FileHandle, (LONG)(CBBK*(*pobkEOD)), 0) != -1
|
||
|
&& FWriteFile (FileHandle, (BYTE *)pdr, cbTransfer);
|
||
|
|
||
|
if (FCondClose (fOpenClose, idFile) && fOk)
|
||
|
{
|
||
|
/* The DR has been successfully written to the file.
|
||
|
Update the EOD of the file.
|
||
|
*/
|
||
|
*pobkEOD += cbkTransfer;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (FALSE);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**** FReadDrFromFile ****/
|
||
|
|
||
|
BOOL APIENTRY FReadDrFromFile (
|
||
|
BOOL fOpenClose, /* If TRUE, the file must be opened and then closed.
|
||
|
If FALSE, the file is already open and should be
|
||
|
left open.
|
||
|
*/
|
||
|
DR *pdr, /* Where to read it into. */
|
||
|
DL dl) /* File location of date. */
|
||
|
{
|
||
|
INT idFile;
|
||
|
INT FileHandle;
|
||
|
WORD cbData;
|
||
|
OBK obk;
|
||
|
BOOL fOk;
|
||
|
|
||
|
/* Assume we will be reading from the original file. */
|
||
|
idFile = IDFILEORIGINAL;
|
||
|
|
||
|
/* Separate the block offset, and switch to the change file if the
|
||
|
change file flag is set in the DL.
|
||
|
*/
|
||
|
obk = dl & DLOBKMASK;
|
||
|
if (dl & DLFCHANGEFILEMASK)
|
||
|
idFile = IDFILECHANGE;
|
||
|
|
||
|
/* Try to reopen the file. */
|
||
|
if (fOpenClose && !FReopenFile (idFile, OF_PROMPT | OF_CANCEL | OF_REOPEN
|
||
|
| OF_READ))
|
||
|
return (FALSE);
|
||
|
|
||
|
|
||
|
|
||
|
/* Reopen was successful - seek to the beginning of the DR, and
|
||
|
read its header in order to know how much data there is.
|
||
|
*/
|
||
|
|
||
|
FileHandle = hFile [idFile];
|
||
|
if (M_llseek (FileHandle, (LONG)(CBBK * obk), 0) == -1
|
||
|
|| M_lread (FileHandle, (LPSTR)pdr, CBDRHEAD) != CBDRHEAD)
|
||
|
{
|
||
|
FCondClose (fOpenClose, idFile);
|
||
|
return (FALSE);
|
||
|
}
|
||
|
|
||
|
/* Header was successfully read. Now read in the rest. */
|
||
|
cbData = pdr -> cbNotes + pdr -> cbTqr;
|
||
|
fOk = (WORD)M_lread (FileHandle, (LPSTR)pdr + CBDRHEAD, cbData) == cbData;
|
||
|
|
||
|
/* Close the file. */
|
||
|
return (FCondClose (fOpenClose, idFile) && fOk);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**** FGetDateDr ****/
|
||
|
|
||
|
BOOL APIENTRY FGetDateDr (DT dtTarget)
|
||
|
{
|
||
|
DR *pdr;
|
||
|
INT itdd;
|
||
|
DD *pdd;
|
||
|
DL dlTarget;
|
||
|
WORD idrTarget;
|
||
|
WORD idrNew;
|
||
|
WORD idrKickOut;
|
||
|
DL dlKickOut;
|
||
|
DT dtKickOut;
|
||
|
HWND hwndFocus;
|
||
|
|
||
|
/* If this routine succeeds in getting the requested date, the
|
||
|
focus is left NULL. If it fails, the focus is set back to its
|
||
|
previous window. Remember who has the focus now, then set it
|
||
|
NULL to record the current edits into the current date. This MUST be
|
||
|
done before switching dates so the data goes into the correct
|
||
|
date. It's important to leave the focus NULL in the success case
|
||
|
so that the data doesn't get recorded again (into the wrong date)
|
||
|
when the caller changes the focus later.
|
||
|
*/
|
||
|
hwndFocus = GetFocus ();
|
||
|
CalSetFocus ((HWND)NULL);
|
||
|
|
||
|
/* See if this date is already in the tdd. */
|
||
|
if (!FSearchTdd (dtTarget, &itdd))
|
||
|
{
|
||
|
/* Not found, try to insert it. */
|
||
|
if (!FGrowTdd (itdd, 1))
|
||
|
{
|
||
|
/* Cannot grow tdd to include the new date. FGrowTdd has
|
||
|
already put up the error message.
|
||
|
*/
|
||
|
CalSetFocus (hwndFocus);
|
||
|
return (FALSE);
|
||
|
}
|
||
|
|
||
|
/* Put the date into the new entry, say it is not marked,
|
||
|
it has no alarms, it's not on disk, and it's not in memory.
|
||
|
*/
|
||
|
pdd = TddLock () + itdd;
|
||
|
pdd -> dt = dtTarget;
|
||
|
pdd -> fMarked = FALSE;
|
||
|
pdd -> cAlarms = 0;
|
||
|
pdd -> dl = DLNIL;
|
||
|
pdd -> idr = IDRNIL;
|
||
|
TddUnlock ();
|
||
|
}
|
||
|
|
||
|
/* At this point itdd is the index of the target date
|
||
|
within the tdd. See if the target date is already in memory.
|
||
|
*/
|
||
|
dlTarget = (pdd = TddLock () + itdd) -> dl;
|
||
|
idrTarget = pdd -> idr;
|
||
|
TddUnlock ();
|
||
|
if ((WORD)idrTarget != IDRNIL)
|
||
|
{
|
||
|
/* The target date is already in memory. Make the DR it
|
||
|
is stored in the current DR, and return TRUE.
|
||
|
*/
|
||
|
vidrCur = idrTarget;
|
||
|
return (TRUE);
|
||
|
}
|
||
|
|
||
|
/* Find a free DR to put the target date in. */
|
||
|
idrNew = IdrFree ();
|
||
|
|
||
|
/* In order to comply with the rule that there is always one
|
||
|
free DR, we have to kick out a date if the 2 DRs other
|
||
|
than idrNew are both in use. Here's how we decide which, if
|
||
|
any, date must be kicked out of memory:
|
||
|
- Look at each DR:
|
||
|
- If it's idrNew, or it's in use for today, skip it.
|
||
|
So we end up either kicking nothing out (if we find a second
|
||
|
free DR), or kicking out a date that's not today.
|
||
|
Since the same date can't be in two DRs
|
||
|
at the same time, we know that we will either find another
|
||
|
free DR or one that contains a date other than today, so this
|
||
|
loop will terminate.
|
||
|
Note - 9/2/85 - MLC - I originally kept today in memory at all
|
||
|
times because in month mode I always displayed the notes for
|
||
|
today, regardless of which day was selected. Some time ago I
|
||
|
changed month mode so it shows the notes for the selected date.
|
||
|
I decided to still keep today around since I assume the user
|
||
|
will be refering to it more than any other date, so it seemed
|
||
|
better than just keeping the two most recently accessed dates
|
||
|
around.
|
||
|
*/
|
||
|
idrKickOut = CDR;
|
||
|
do
|
||
|
{
|
||
|
idrKickOut--;
|
||
|
dtKickOut = PdrLock (idrKickOut) -> dt;
|
||
|
DrUnlock (idrKickOut);
|
||
|
}
|
||
|
while (idrKickOut == idrNew || dtKickOut == vftCur.dt);
|
||
|
|
||
|
if (dtKickOut != DTNIL)
|
||
|
{
|
||
|
/* We must kick out a date to free up a DR. */
|
||
|
pdr = PdrLock (idrKickOut);
|
||
|
if (!FFreeUpDr (pdr, &dlKickOut))
|
||
|
{
|
||
|
/* If we just created the DD for the target date,
|
||
|
it is still empty, so we will get rid of it if so.
|
||
|
*/
|
||
|
DrUnlock (idrKickOut);
|
||
|
DeleteEmptyDd (itdd);
|
||
|
CalSetFocus (hwndFocus);
|
||
|
return (FALSE);
|
||
|
}
|
||
|
pdr -> fDirty = FALSE;
|
||
|
DrUnlock (idrKickOut);
|
||
|
}
|
||
|
|
||
|
pdr = PdrLock (idrNew);
|
||
|
if (dlTarget == DLNIL)
|
||
|
{
|
||
|
/* No previous data for this date so create an empty DR for it. */
|
||
|
pdr -> cbNotes = pdr -> cbTqr = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!FReadDrFromFile (TRUE, pdr, dlTarget))
|
||
|
{
|
||
|
/* Mark the DR as still not in use. */
|
||
|
pdr -> dt = DTNIL;
|
||
|
DrUnlock (idrNew);
|
||
|
|
||
|
/* If we just created the DD for the target date,
|
||
|
it is still empty, so we will get rid of it if so.
|
||
|
*/
|
||
|
DeleteEmptyDd (itdd);
|
||
|
CalSetFocus (hwndFocus);
|
||
|
return (FALSE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Could be here if no previous data or
|
||
|
where date was successfully read from file.
|
||
|
Therefore, set the dt, since in the first case it has not
|
||
|
been set.
|
||
|
*/
|
||
|
pdr -> dt = dtTarget;
|
||
|
pdr -> fDirty = FALSE;
|
||
|
DrUnlock (idrNew);
|
||
|
(TddLock () + itdd) -> idr = idrNew;
|
||
|
TddUnlock ();
|
||
|
|
||
|
if (dtKickOut != DTNIL)
|
||
|
{
|
||
|
/* We kicked out a date. Mark that DR free. */
|
||
|
PdrLock (idrKickOut) -> dt = DTNIL;
|
||
|
DrUnlock (idrKickOut);
|
||
|
|
||
|
/* Search for the DD of the date we kicked out. It's OK to
|
||
|
ignore the return value of FSearchTdd since the date we
|
||
|
kicked out must be in the tdd.
|
||
|
*/
|
||
|
FSearchTdd (dtKickOut, &itdd);
|
||
|
|
||
|
/* Say the kicked out date is no longer in memory, and change
|
||
|
the DL if the date's location has changed.
|
||
|
*/
|
||
|
(pdd = TddLock () + itdd) -> idr = IDRNIL;
|
||
|
if (dlKickOut != DLNOCHANGE)
|
||
|
pdd -> dl = dlKickOut;
|
||
|
TddUnlock ();
|
||
|
|
||
|
/* Get rid of the DD of the kicked out date if it's "empty". */
|
||
|
DeleteEmptyDd (itdd);
|
||
|
}
|
||
|
|
||
|
vidrCur = idrNew;
|
||
|
return (TRUE);
|
||
|
}
|