Copyright (c) Hyperion Entertainment and contributors.

Notification

From AmigaOS Documentation Wiki
Revision as of 15:33, 10 July 2013 by Steven Solie (talk | contribs) (→‎MessageNotification.c)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

File Notification is a form of interprocess communication. An application can ask either a file system (like the RAM disk handler RAM:, df0:, df1:...) or DOS to inform it whenever changes are made to a specific file or directory, making it easy for the application to react to such changes.

The preferences control program, IPrefs, sets up notification on most of the preferences files in ENV:Sys. If the user alters any of these files (which he/she normally does with a preferences editor), the system will notify IPrefs about the change. IPrefs will react to this notification by attempting to alter the user's environment to reflect the preference change. For example, if the user opens the ScreenMode preferences editor and alters the Workbench environment so that the Workbench screen should be a Hires NTSC screen, ScreenMode writes a file called Screenmode.prefs to the ENV:Sys directory which happens to be in RAM:. Because IPrefs has set up notification on this file, the RAM disk file system or DOS will notify IPrefs of the change, IPrefs will read in the Screenmode.prefs file and will try to reset the Workbench screen so it is in Hires NTSC mode.

Notification allows very different applications to share common data files without knowing anything about each other. This has many possible uses in the Amiga's single user, multitasking environment. One possible use for notification is in a desktop publishing (DTP) package. The user can open the DTP package to layout a group of ILBMs, some structured drawings, and word processed text. When the user loads each of these, the DTP package sets up notification on each of their corresponding files. If the user loads an appropriate editor and changes any of the files on which the DTP package has set up notification, the DTP package will receive notification of these changes and can automatically re-import these files into the current DTP document without the user having to intervene. Another possible use for notification might be in a make utility. A make program for a compiler could set up notification on a set of source code and object files. If any of those files change, the make program will recompile and link the program, without the programmer having to intervene.

File System or DOS?

Starting with version 53.x of the DOS Library (dos.library), the notification feature is handled entirely by AmigaDOS. DOS notification is independent of the file system and is always available. Prior to this version, file system notification was available only if each individual file system implemented the feature. If a file system does not support notification the notification functions described below will always fail.

Using Notification

Setting up file notification on a file is easy. The StartNotify() function from dos.library starts notification on a file or directory:

BOOL StartNotify( struct NotifyRequest *notify );

StartNotify() returns DOSTRUE if the call is successful, or it returns DOSFALSE (for example, when the file's file system does not support notification). This function takes a pointer to an initialized NotifyRequest structure as its only argument (as defined in <dos/notify.h>):

struct NotifyRequest {
   UBYTE *nr_Name;  /* File/directory name which you want notification */
   UBYTE *nr_FullName; /* Used by DOS. Do not use */
   ULONG nr_UserData;  /* For applications use */
   ULONG nr_Flags;  /* Flags indicating Signal or Message notification */
 
   union {
      /* Used for Message notification */
      struct {
         struct MsgPort *nr_Port; /* Message port to receive messages on */
     } nr_Msg;
     /* Used for Signal notification */
     struct {
        struct Task *nr_Task;       /* The task to signal */
        UBYTE nr_SignalNum;         /* The signal number to use. */
        UBYTE nr_pad[3];
     } nr_Signal;
   } nr_stuff;
 
   ULONG nr_Reserved[4];          /* leave 0 for now */
 
   /* Used internally by handlers */
   ULONG nr_MsgCount;             /* number of outstanding messages */
   struct MsgPort *nr_Handler;    /* handler to send to (for EndNotify) */
};

This structure must not be altered by the application while notification is in effect!

The nr_Name field contains a pointer to the name of the file on which to set up notification. Currently, nr_Name has to be a file name and path containing a logical device name (for example df0:, work:, fonts:). The nr_FullName field is for the private use of the file system. Any other use of it is strictly prohibited. The nr_UserData field is available for an applications private use.

The nr_Flags field tells the file system which type of notification to set up, message or signal. When the file system uses message notification, it notifies an application by sending an Exec message. An application asks a file handler to notify it via an Exec message by setting the NRF_SEND_MESSAGE flag in nr_Flags. When the file system uses signal notification, it sets an Exec signal to notify an application. An application receives notification via a signal by setting the NRF_SEND_SIGNAL flag.

The nr_Flags field has two other flags, NRF_WAIT_REPLY and NRF_NOTIFY_INITIAL. The NRF_WAIT_REPLY tells the file handler not to send notification messages about a specific file/directory to an application if the application has not replied to a previous notification message about that specific file. This flag only applies to message notification. The NRF_NOTIFY_INITIAL flag tells the file handler to notify the application if the file exists when it sets up notification on the file. The flags for the nr_Flags field are defined in <dos/notify.h>.

The layout of the rest of the NotifyRequest structure depends on the type of notification. If the application is using message notification, it must supply the handler with a message port to send the notification messages. The NotifyRequest.nr_stuff.nr_Msg.nr_Port field contain the pointer to the message port that will receive the message notifications. If the application is using a signal for notification, it must supply a pointer to the task to signal and the number (not bit!) of the signal. In this case, the NotifyRequest.nr_stuff.nr_Signal.nr_Task field should contain the appropriate task pointer and the NotifyRequest.nr_stuff.nr_Signal.nr_SignalNum field should contain the signal number.

When a file handler uses message notification, it will send a NotifyMessage:

struct NotifyMessage {
   struct Message nm_ExecMessage;
   ULONG  nm_Class;               /* Class, will be NOTIFY_CLASS */
   UWORD  nm_Code;                /* Code, will be NOTIFY_CODE */
   struct NotifyRequest *nm_NReq; /* Point to NotifyRequest you supplied */
   ULONG  nm_DoNotTouch;          /* private */
   ULONG  nm_DoNotTouch2;         /* private */
};

Message notification is especially useful if you are monitoring more than one file. It quickly enables you to find out which file/directory caused this message by either comparing the NotifyRequest structure returned in nm_NReq with the one you sent in the StartNotify() function, or by reading the NotifyRequest's nr_UserData field. Because the NotifyMessage's nm_Class and nm_Code fields contain values that distinguish it from other types of messages, you can use an already allocated message port (from a window for example) to receive notification messages.

To end notification on a file, use the dos.library function EndNotify():

void EndNotify( struct NotifyRequest *notify );

An application must call this function for each of its successful StartNotify() calls. This function takes one parameter, a pointer to the NotifyRequest structure that the application used to initiate the notification. In the case of message notification, EndNotify() will remove all pending notify messages from your message port. After calling this function, it is safe for the application to change or free the NotifyRequest structure. The application may also remove the message port or free the signal bit.

Notification Using DOS Packets

Don't use DOS Packets
DOS Library now handles notification as of version 53.?? so that file systems are no longer burdened with implementing this functionality. The information below is being provided for reference and for users of previous versions of the DOS Library.

A file handler should send notification when it receives any of the following packets (from <dos/dosextens.h>) about the notification file or directory:

ACTION_RENAME_OBJECT
ACTION_RENAME_DISK
ACTION_CREATE_DIR
ACTION_DELETE_OBJECT
ACTION_WRITE
ACTION_FINDUPDATE
ACTION_FINDOUTPUT
ACTION_SET_FILE_SIZE
ACTION_SET_DATE

The first four packets will cause notification immediately. The second four packets will cause notification when the notification file is closed. The last packet, ACTION_SET_DATE, should cause notification immediately, but due to a bug in the V37 ROM file system, only the RAM disk's file handler (RAM:) will send notification.

Notice that some of the packets that trigger a notification are sent by a process when it is trying to create a new file or directory. A file system that supports notification should be able to set up notification on a file or directory that does not currently exist. A file system should send notification when it creates that file or directory.

When implementing notification in your application, there are several things to remember. Not every file system supports notification, in particular, most network file systems will not support notification. For this reason, no application should require notification to function.

Examples

SignalNotification.c

/*
 * SignalNotification.c
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <dos/notify.h>
#include <dos/rdargs.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
 
int main()
{
    struct RDArgs  *readargs;
    LONG            rargs[2];
    struct NotifyRequest *notifyrequest;
    TEXT           *filename;
    ULONG           signr, signal;
 
    /* See the DOS Autodocs for more information about ReadArgs() */
    if (readargs = IDOS->ReadArgs("FILENAME/A", rargs, NULL))
    {
        filename = (UBYTE *) (rargs[0]);
 
        /* Allocate a NotifyRequest structure */
        if (notifyrequest = IExec->AllocMem(sizeof(struct NotifyRequest), MEMF_CLEAR))
        {
            /* And allocate a signalbit */
            if ((signr = IExec->AllocSignal(-1L)) != -1)
            {
 
                /* Initialize notifcation request */
                notifyrequest->nr_Name = filename;
                notifyrequest->nr_Flags = NRF_SEND_SIGNAL | NRF_NOTIFY_INITIAL;
                /* Signal this task */
                notifyrequest->nr_stuff.nr_Signal.nr_Task =
                        (struct Task *) IExec->FindTask(NULL);
                /* with this signal bit */
                notifyrequest->nr_stuff.nr_Signal.nr_SignalNum = signr;
 
                if ((IDOS->StartNotify(notifyrequest)) == DOSTRUE)
                {
                    /* Loop until Ctrl-C to exit */
                    for (;;)
                    {
                        signal = IExec->Wait(1L << signr | SIGBREAKF_CTRL_C);
                        if (signal & (1L << signr))
                            IDOS->Printf("Notification signal!\n");
                        if (signal & SIGBREAKF_CTRL_C)
                        {
                            IDOS->EndNotify(notifyrequest);
                            IDOS->PrintFault(ERROR_BREAK, NULL);
                            break;
                        }
                    }
                }
                else
                    IDOS->PrintFault(ERROR_NOT_IMPLEMENTED, NULL);    /* most logical */
 
                IExec->FreeSignal(signr);
            }
            else
                IDOS->Printf("No signal available\n", NULL);
            IExec->FreeMem(notifyrequest, sizeof(struct NotifyRequest));
        }
        else
            IDOS->PrintFault(ERROR_NO_FREE_STORE, NULL);
 
        IDOS->FreeArgs(readargs);
    }
    else
        IDOS->PrintFault(IoErr(), NULL);
 
    return RETURN_OK;
}

MessageNotification.c

/*
 * MessageNotification.c
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/nodes.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <dos/notify.h>
#include <dos/rdargs.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
/* I'll keep track of the NotifyRequests in an Exec list */
struct NotifyNode
{
    struct Node     nn_Node;
    struct NotifyRequest nn_NotifyRequest;
    /* and whatever is useful. Maybe a FileInfoBlock structure... */
};
 
int main()
{
    struct RDArgs  *readargs;
    LONG            rargs[2];
    struct NotifyRequest *notifyrequest;
    struct NotifyMessage *notifymsg;
    struct List    *notifylist;
    struct MsgPort *notifyport;
    struct NotifyNode *nnode, *nextnode;
    TEXT          **filenames;
    ULONG           signal, notifysignal;
 
    /* See the DOS Autodocs for more information about ReadArgs() */
    if (readargs = IDOS->ReadArgs("FILENAME/A/M", rargs, NULL))
    {
        /* Pointer to array of filenames */
        filenames = (TEXT **) (rargs[0]);
 
        if (notifyport = IExec->AllocSysObjectTags(ASOT_PORT, NULL))
        {
            if (notifylist = IExec->AllocMem(sizeof(struct List), MEMF_CLEAR))
            {
                /* initialize list */
                IExec->NewList(notifylist);
 
                /* The list of filenames is terminated with a NULL */
                while (*filenames)
                {
                    /* Get a NotifyNode */
                    if (nnode = IExec->AllocMem(sizeof(struct NotifyNode), MEMF_CLEAR))
                    {
                        /*
                         * Use ln_Name to store qualified filename to
                         * monitor. Note that I keep using that pointer to
                         * the command argument line here. In a real life
                         * application you're better off copying the
                         * string.
                         */
                        nnode->nn_Node.ln_Name = (UBYTE *) * filenames++;
                        notifyrequest = &(nnode->nn_NotifyRequest);
 
                        /* Initialize notifcation request */
                        notifyrequest->nr_Name = nnode->nn_Node.ln_Name;
                        notifyrequest->nr_Flags = NRF_SEND_MESSAGE | NRF_WAIT_REPLY;
                        notifyrequest->nr_stuff.nr_Msg.nr_Port = notifyport;
                        /*
                         * I'm storing the address of the NotifyNode in
                         * nr_UserData. Not going to do anything with it
                         * here, but it would enable me to use the node
                         * immediately when I receive a NotifyMessage.
                         */
                        notifyrequest->nr_UserData = (ULONG) nnode;
                        /*
                         * only add the node to the list if notification is
                         * supported
                         */
                        if ((IDOS->StartNotify(notifyrequest)) == DOSTRUE)
                        {
                            IExec->AddTail(notifylist, (struct Node *) nnode);
                            IDOS->Printf("Notification on %s\n",
                                   nnode->nn_Node.ln_Name);
                        }
                        else
                        {
                            IDOS->Printf("Notification failed on %s\n",
                                   nnode->nn_Node.ln_Name);
                            IExec->FreeMem(nnode, sizeof(struct NotifyNode));
                        }
                    }
                    else
                    {
                        IDOS->PrintFault(ERROR_NO_FREE_STORE, NULL);
                        break;
                    }
                }
 
                /*
                 * Is list empty? If so get out of here. (Macro defined in
                 * <exec/lists.h>)
                 */
                if (!(IsListEmpty(notifylist)))
                {
                    /*
                     * No empty, so we've got outstanding NotifyRequests.
                     * Loop until Ctrl-C.
                     */
                    notifysignal = 1L << notifyport->mp_SigBit;
                    for (;;)
                    {
                        /* Wait for message port signals and break */
                        signal = IExec->Wait(notifysignal | SIGBREAKF_CTRL_C);
 
                        if (signal & notifysignal)
                        {
                            while (notifymsg =
                                    (struct NotifyMessage *) IExec->GetMsg(notifyport))
                            {
                                /*
                                 * Here is that node again, stuffed in
                                 * nr_UserData. Can immediately reference
                                 * its data, like comparing filesize with
                                 * stored filesize in node, or remove it
                                 * (after an EndNotify() ofcourse).
                                 */
                                IDOS->Printf("Notification message for %s, Node at 0x%lx\n",
                                       notifymsg->nm_NReq->nr_Name,
                                       notifymsg->nm_NReq->nr_UserData);
                                IExec->ReplyMsg((struct Message *) notifymsg);
                            }
                        }
 
                        if (signal & SIGBREAKF_CTRL_C)
                        {
                            /*
                             * Walk down the list, remove all
                             * NotifyRequests, free all nodes.
                             */
                            nnode = (struct NotifyNode *) notifylist->lh_Head;
                            while (nextnode =
                                    (struct NotifyNode *) nnode->nn_Node.ln_Succ)
                            {
                                notifyrequest = &(nnode->nn_NotifyRequest);
                                IDOS->Printf("Removing notifcation for %s\n",
                                       (LONG) notifyrequest->nr_Name);
                                /*
                                 * remove this request. EndNotify() will
                                 * also remove any messages on the message
                                 * port.
                                 */
                                IDOS->EndNotify(notifyrequest);
                                /*
                                 * Not really needed to Remove() the node,
                                 * never going to use this list again
                                 */
                                IExec->Remove((struct Node *) nnode);
                                IExec->FreeMem(nnode, sizeof(struct NotifyNode));
                                nnode = nextnode;
                            }
                            break;
                        }
                    }
                }
                IExec->FreeMem(notifylist, sizeof(struct List));
            }
            IDOS->FreeSysObject(ASOT_PORT, notifyport);
        }
        IDOS->FreeArgs(readargs);
    }
    else
        IDOS->PrintFault(IDOS->IoErr(), NULL);
 
    return RETURN_OK;
}