TweetFollow Us on Twitter

AppleShare IP Additions

Volume Number: 15 (1999)
Issue Number: 2
Column Tag: ExplainIt

Crafting AppleShare IP Web and File Server Additions

by Erik Sea
Edited by Peter N Lewis

Using the AppleShare IP Web & File Server Control API

Server Additions - AppleShare IP Web & File

You're probably quite familiar with Mac OS APIs, functions, and controls. You've probably also run across AppleShare, and you've likely also used an AppleShare IP File Server. But I'll bet you didn't know that key pieces of AppleShare functionality are accessible to developers through a number of APIs. In this article, we'll only talk about the Web & File Server, but you can find out about other APIs, such as the AppleShare Registry and User Authentication Modules, by perusing the AppleShare IP SDK.

In this article, I'll go through the process of writing a simple little application (called a "server addition") that allows the user to control the W&F Server, and displays some server statistics. As I proceed, I'll pursue the occasional diversion that may inspire you but isn't directly related to the server addition I'm concocting here.

To use the code presented in this article, you'll need an AppleShare IP Web & File Server, and a copy of the latest version of the AppleShare IP SDK, which is available on the Mac OS SDK CD and on the Apple Developer web pages. While a few of these calls work with Macintosh File Sharing, the basic peer-to-peer version of AppleShare that ships with Mac OS (sometimes called Personal File Sharing), this article is focussed on the much more extensive developer API suite that is available under the full W&F Server.

Give Me Server Control

Although it is technically correct to view the Web & File Server as an application, in reality, when the server is installed on a machine it alters and extends the behavior of several parts of Mac OS. Once W&F is on a machine, you can control and monitor the server using API calls, just as you can make Mac OS API calls, whether the server is "serving" or not (although most calls are not useful when the server is not running and will simply return errors). In ASIP 6.1, there are roughly 29 API calls you can make, ranging from starting the server, to sending messages to connected users. You can also ask the server to tell you when things happen, such as when a user has connected or disconnected. The AppleShare Web & File Server also supports WebStar plugins.

The Server Control API consists of various parameter blocks used to set and retrieve information from the server. While it would take a lot of space to detail each of the various calls and parameters in this article, I've provided a brief summary in Table 1. If you want to know more on any of these, see the AppleShare IP SDK.

Table 1: Server Control Calls & Command Constants

SCStartServer                = 0   // Start the server
SCShutDown                   = 2   // Shut down the server
SCCancelShutDown             = 3   // Stop a shut down in progress
SCDisconnect                 = 4   // Disconnect a list of users
SCPollServer                 = 5   // Status: starting/running/shutting down
SCGetExpFldr                 = 6   // Info about a shared folder or volume
SCGetSetupInfo               = 7   // Get configuration info
SCSetSetupInfo               = 8   // Change configuration info
SCSendMessage                = 9   // Send message to user or users
SCGetServerStatus            = 10   // Time of last server change
SCInstallServerEventProc     = 11   // Installs a server event handler
SCRemoveServerEventProc      = 12   // Removes a server event handler
SCGetServerEventProc         = 13   // Retrieve a server event handler
SCServerVersion              = 14   // Version of AppleShare
SCSetCopyProtect             = 16   // Make file copy protected
SCClrCopyProtect             = 17   // Make file unprotected
SCDisconnectVolUsers         = 18   // Disconnect users from volumes
SCGetUserNameRec             = 19   // Retrieve connected user information
SCGetUserMountInfo           = 20   // How a user is using a volume
SCWakeServer                 = 21   // Starts a server that has been paused
SCSleepServer                = 22   // Pauses the server
SCGetCacheStats              = 23   // Cache size, utilization, hitcount
SCResetCache                 = 31   // Empty the file cache
SCGetExtUserNameRec          = 35   // Additional user information
SCServiceStateInfo           = 38   // Individual service states (FTP, etc.)
SCGetPlugInInfo              = 41   // Info about user's installed plugins
SCGetPlugInMimeType          = 42   // MIME type associated with a plugin
SCSetHistorySampleTime       = 43   // Time slice for server load monitoring
SCGetServerActivityHistory   = 44   // Server load percentages

Interestingly, the W&F Server's user interface is actually a separate application from the server, and it communicates with the server using these very same calls. Not all of the information available from these calls is exposed in the current user interface, and the possibilities for writing a server addition that displays additional information to the user are fairly wide-ranging - you could even replace the server's user interface entirely if you choose!

The application I'll be presenting is necessarily simplistic - just a modal dialog with a few bits of information and controls in it - but it provides a good foundation on which you could write a more advanced application, with more information and control organized as you see fit. And I hope you'll make it pretty and modeless!

ServerControl Demo Application

The user interface of the finished application is as seen in Figure 1. There's a button to flush the file server's data cache, some status text, and some counters. At the bottom of the screen, we have a histogram of server activity (gathered over a one day period), which shows maximum, minimum, or average usage levels, depending on what the user chooses.


Figure 1. ServerControl Demo main window.

We'll be using a modal dialog, in order to simplify the code (so I don't fill space with UI handling that's not directly related to the task at hand).

Determining if the Server is Installed

The first thing we need to do when our addition starts, after initializing all the appropriate toolbox routines, is ensure that an AppleShare IP W&F Server is installed. The easy method is to make a gestalt call, which will return an error if W&F is not installed, and the version number if it is (actually, this technique only works with 6.0 or later, but we're going to require 6.0 anyway so this is not a limitation). The source for this is in Listing 1.

Listing 1: Checking for Server and Version

extern Boolean
AppleShareIsInstalled (void) {

   Boolean   isInstalled = true;
   SInt32      asipVersion;
   OSErr      err;

   err = Gestalt (gestaltASIPFSVersion, &asipVersion);
   
   if ((err != noErr) || 
         (asipVersion < kMinimumAppleShareVersion)) {
      isInstalled = false;
   } // if

   return isInstalled;

} // AppleShareIsInstalled

Setting up the Window

Once we know that the server is there, and it's a suitable version, we can bring in the window, and handle it until closed. There's actually a bit more to it, of course. In order to count logins and file accesses, we need a server event handler (actually, we'll be counting each open of a file fork, so, if a file has both a data fork and a resource fork, a single open could be counted twice). We also need to set up the user item that draws the histogram and to ask the server to record history information using an appropriate time slice. The recommended time slice value is one sample every 84 seconds, so that with 1024 data points the server retains a full day's information. Changing the value may interfere with other programs that expect the value to be 84 seconds, so don't deviate without cause.

For the most part, we'll use global variables for communication between the server event handler, the dialog user item, and the modal dialog filter. Since AppleShare IP is PowerPC-only, we can write the addition as a PowerPC application, which means that we don't have to do anything special to use global variables.

Listing 2 shows these basic elements. We'll talk more about server event handlers and the server control calls we use in a bit.

Listing 2: Main.c

#define      kAllocateStorage         NULL
#define      kPlaceInFront            ((WindowPtr) (-1L))

// Global variables...

SInt16       gActivityType         = kWindowAvgRadioButtonIdx;
   // We map the radio group state to the current on value...
SInt32       gActiveUserCount       = 0;
   // Number of users currently logged on...
SInt32       gLoginCount             = 0;
   // Cumulative counter of login events...
SInt32       gAccessCount          = 0;
   // Cumulative counter of access events...
UInt16       gServerState          = kStateNotRunningIdx;
   // Map the server state to the string we'll display...
Boolean       gResetEnabled         = false;
   // The Reset button should be drawn enabled...
UInt32       gLastTimeServerPolled   = 0;
   // TimeStamp of the last time the server was called...
ServerHistoryRec gHistoryData;
   // Historical data last returned by the server...
ServerEventQEntry gServerEventQEntry;
   // Event handler queue entry...

// Support routines...

static void
Initialize (void) {

   // Initialize the managers we need...

   InitGraf (&qd.thePort);
   InitFonts ();
   InitWindows ();
   InitMenus ();
   TEInit ();
   InitDialogs (NULL);
   InitCursor ();

   // Initialize the data structures we'll use...

   gHistoryData.numDataPoints = 0;
   gHistoryData.historyLastSample = 0;

} // Initialize

// Main routine...

extern int
main (void) {

   DialogPtr      additionWindow;

   // Initialize the toolbox, then check for AppleShare.
   // If AppleShare is installed, go get the window,  set default control
   // and data structure values, install server event handler, and install
   // a dialog user item to draw the histogram.

   Initialize ();

   if (AppleShareIsInstalled ()) {
      additionWindow = GetNewDialog (kAdditionWindowRsrcID,
                               kAllocateStorage, kPlaceInFront);
      if (additionWindow != NULL) {
         InstallServerEventHandler ();
         SetServerTimeSlice (kOneDayTotalTimeSlice);
         SetUserItem (additionWindow, kWindowUserItemIdx,
                              DrawHistogram);
         SetDialogValues (additionWindow);
         HandleDialog (additionWindow);
         RemoveServerEventHandler ();      
      } // if
   } // if

   return 0;

} // main

Once we have the dialog in place, we need to keep it up to date. Listing 3, DialogStuff.c, contains all the basic routines for maintaining the UI. Of these routines, MonitorServerEvents and DrawHistogram merit some additional attention.

Keeping the Window Up-to-Date

Since this is a modal dialog, we ensure that the window is updated by making server control calls from the filterproc, MonitorServerEvents. The filterproc gets called fairly often but we don't want to bog down the server with all sorts of server control calls to get status and history information updated (particularly since we know the history information will only change every 84 seconds!), so we only poll every 10 seconds, which still allows us to see changes in the number of active users in a timely fashion. You might want to improve the logic here to further lighten the load on the server - nobody wants to run a server addition that impairs performance!

When the data changes, we force the histogram user item to completely redraw by invalidating its bounding rectangle from the filterproc. The user item, implemented by DrawHistogram, simply steps through the data points in the server history record, and uses them to draw vertical lines (bar chart style). For contrast, based on the radio button the user has chosen, the different activity levels (maximum, average, and minimum) are drawn in different colors. Since we allow the user to change which data to chart in the histogram, a redraw can also be forced from the HandleDialog routine, based on the radio button hit.

One quick note about how the history data are arranged by the server: the most recent sample is always in array position zero, with older data scrolling to higher positions and then disappearing. This may seem backwards, but makes sense if you envision the server history data as kind of like "activity EKG", with the recording needle fixed at the left hand side, and the tape moving to the right.

Listing 3: DialogStuff.c

#include    "ServerControlAddition.h"

extern pascal void
DrawHistogram (WindowPtr theDialog, SInt16 itemNo) {

   SInt16            itemType;
   Handle            itemHandle;
   Rect               itemRect;
   UInt16            drawItemIdx = 0;
   RGBColor         svColor;
   UInt16            drawValue;
   HistoryData*   currentPoint;
   HistoryData*   nextPoint;
   SInt16            horiz;
   SInt16            bottom;
   SInt16            top;

   // Get the item, frame it, inset by one; then draw a bar from
   // bottom to top for each defined data point, based on whether
   // we're showing minimum/maximum/average data. Now, because we
   // know that the area screen is half the number of available
   // data points (512 vs. 1024) and yet the height is twice as
   // tall (200 vs. 100 percent) we'll add pairs together. This
   // might make the last reading bogus if the number of
   // available data points is odd, but it will fix itself soon enough...
   
   GetDialogItem (theDialog, itemNo, &itemType,
         &itemHandle, &itemRect);
   FrameRect (&itemRect);
   InsetRect (&itemRect, 1, 1);
   EraseRect (&itemRect);
   GetForeColor (&svColor);
   
   while (drawItemIdx < gHistoryData.numDataPoints) {
      currentPoint = &gHistoryData.dataPoint[drawItemIdx];
      nextPoint = &gHistoryData.dataPoint[drawItemIdx+1];
      switch (gActivityType) {
         case kWindowMaxRadioButtonIdx:
            drawValue = currentPoint->dpMax +
                           nextPoint->dpMax;
            ForeColor (redColor);
            break;
         case kWindowMinRadioButtonIdx:
            drawValue = currentPoint->dpMin +
                           nextPoint->dpMin;
            ForeColor (blueColor);
            break;
         case kWindowAvgRadioButtonIdx:
         default:
            drawValue = currentPoint->dpAverage +
                           nextPoint->dpAverage;
            ForeColor (greenColor);
            break;
      } // switch
      horiz = itemRect.left + (drawItemIdx >> 1);
      bottom = itemRect.bottom - 1;
      top = bottom - drawValue;
      MoveTo (horiz, bottom);
      LineTo (horiz, top);
      drawItemIdx += 2;
   } // while

   RGBForeColor (&svColor);

} // DrawHistogram

extern void
HandleDialog (WindowPtr theDialog) {

   SInt16                  itemHit;
   ModalFilterUPP      modalFilterUPP;
   
   modalFilterUPP = NewModalFilterProc (MonitorServerEvents);

   do {
   
      ModalDialog (modalFilterUPP, &itemHit);
   
      switch (itemHit) {
      
         // For the radio buttons, we may need to update the
         // display immediately, but only if there's a change...
      
         case kWindowMaxRadioButtonIdx:
         case kWindowMinRadioButtonIdx:
         case kWindowAvgRadioButtonIdx:
            if (gActivityType != itemHit) {
               gActivityType = itemHit;
               InvalDialogItem (theDialog, kWindowUserItemIdx);
               SetDialogValues (theDialog);
            } // if
            break;
         
         // These are plain server control calls...
         
         case kWindowResetCacheButtonIdx:
            DoResetCache ();
            break;
         
         default:
            break;
         
      } // switch
   
   } while (itemHit != kStdOkItemIndex);

} // HandleDialog

extern void
SetDialogValues (DialogPtr additionWindow) {

   Str255      statusText;
   Str32      countText;

   // Update the radio group...

   TurnControlOn (additionWindow, gActivityType, true);
   if (gActivityType != kWindowMaxRadioButtonIdx) {
      TurnControlOn (additionWindow, kWindowMaxRadioButtonIdx,
                           false);
   } // if
   if (gActivityType != kWindowAvgRadioButtonIdx) {
      TurnControlOn (additionWindow, kWindowAvgRadioButtonIdx, 
                           false);
   } // if
   if (gActivityType != kWindowMinRadioButtonIdx) {
      TurnControlOn (additionWindow, kWindowMinRadioButtonIdx, 
                           false);
   } // if
   
   // Update the state text...
   
   GetIndString (statusText, kServerStateStringsRsrcID, 
                           gServerState);
   SetTextItem (additionWindow, kWindowStatusTextIdx, 
                           statusText);
   
   // Update the counters...
   
   NumToString (gActiveUserCount, countText);
   SetTextItem (additionWindow, kWindowActiveUsersTextIdx,
                         countText);
   NumToString (gLoginCount, countText);
   SetTextItem (additionWindow, kWindowLoginsTextIdx, 
                        countText);
   NumToString (gAccessCount, countText);
   SetTextItem (additionWindow, kWindowAccessesTextIdx, 
                        countText);
   
   // Update the button...
   
   SetButtonEnabled (additionWindow,
             kWindowResetCacheButtonIdx, gResetEnabled);
   
} // SetDialogValues

extern pascal Boolean
MonitorServerEvents (DialogPtr theDialog,
               EventRecord* theEvent, DialogItemIndex* itemHit) {

   // This filter gets called fairly often; if we make frequent server control
   // calls, we'll start impairing the performance of the server. So, let's 
   // only do it once every 10 seconds...

   UInt32      currentTime;
   UInt32      lastHistoryTime;

   GetDateTime (&currentTime);
   if (currentTime - gLastTimeServerPolled > 
                        kNumberOfSecondsBetweenPolls) {

      // Check for changes in the status of the server, and update
      // if necessary...

      UpdateServerStatus ();
      if (gServerState == kStateRunningIdx) {
         gResetEnabled = true;
      } else {
         gResetEnabled = false;
      } // if
      SetDialogValues (theDialog);
      
      // Since our UserItem will erase and redraw the entire histogram,
      // let's be sure it really changed before we force a redraw...
      
      lastHistoryTime = gHistoryData.historyLastSample;
      UpdateServerHistory ();
      if (lastHistoryTime != gHistoryData.historyLastSample) {
         InvalDialogItem (theDialog, kWindowUserItemIdx);
      } // if
   } // if
   
   return StdFilterProc (theDialog, theEvent, itemHit);

} // MonitorServerEvents

Talk to me, AppleShare IP

Now that we have enough of a UI in place to display what we want, and the mechanisms for keeping that UI updated, we need to write some code that actually talks to the server, updates our global variables, and populates our history buffer. Although there are 29 calls available, we're only going to need seven of them in this server addition.

Server control calls are parameter-block based, so, starting with a parameter block, you set various fields to different values, and that determines what the call actually is. The entry point is the routine ServerDispatchSync, which is defined in the header AppleShareFileServerControl.h, and implemented in the SDK library file SyncServerDispatch.c.

As can be seen in Listing 4, talking to the W&F Server is relatively straightforward: declare a parameter block, stuff in the required values, and then make the call, and retrieve the values. There are some additional things to consider for server event handlers, and we'll talk about those next.

Listing 4: Making Server Control Calls

#include    "ServerControlAddition.h"

extern void
SetServerTimeSlice (UInt32 secondsToWait) {

   OSErr                     err;
   SCParamBlockRec         pb;
   SetHistoryParamPtr   setHistoryParam;
   
   setHistoryParam = &pb.setHistoryParam;
   setHistoryParam->scCode = kSCSetHistorySampleTime;
   setHistoryParam->historySampleTime = secondsToWait;
   err = ServerDispatchSync (&pb);

} // SetServerTimeSlice

extern void
UpdateServerStatus (void) {

   OSErr                     err;
   SCParamBlockRec         pb;
   StatusParamPtr         statusParam;
   PollServerParamPtr   pollServerParam;

   // We get the number of connected users from the GetServerStatus call...

   statusParam = &pb.statusParam;
   statusParam->scNamePtr = NULL;
   statusParam->scCode = kSCGetServerStatus;
   err = ServerDispatchSync (&pb);
   if (err == noErr) {
      gActiveUserCount = statusParam->scNumSessions;
   } // if
   
   // And we get the state of the server from the PollServer call...
   
   pollServerParam = &pb.pollServerParam;
   pollServerParam->scCode = kSCPollServer;
   err = ServerDispatchSync (&pb);
   if (err == noErr) {
      switch (pollServerParam->scServerState) {
         case kSCPollRunning:
            gServerState = kStateRunningIdx;
            break;
         case kSCPollStartingUp:
            gServerState = kStateStartingIdx;
            break;
         case kSCPollSleeping:
            gServerState = kStateSleepingIdx;
            break;
         case kSCPollJustDisabled:
         case kSCPollDisabledErr:
            gServerState = kStateNotRunningIdx;
            break;
         default:
            gServerState = kStateShuttindDownIdx;
            break;
      } // switch
   } else {
      gServerState = kStateNotRunningIdx;
   } // if

} // UpdateServerStatus

extern void
UpdateServerHistory (void) {

   OSErr                     err;
   SCParamBlockRec         pb;
   GetHistoryParamPtr   getHistoryParam;
   
   getHistoryParam = &pb.getHistoryParam;
   getHistoryParam->scHistory = &gHistoryData;
   getHistoryParam->numDataPointsRequested = kSCMaxDataPoints;
   getHistoryParam->scCode = kSCGetServerActivityHistory;
   err = ServerDispatchSync (&pb);

} // UpdateServerHistory

extern void
InstallServerEventHandler (void) {

   OSErr                     err;
   SCParamBlockRec         pb;
   ServerEventParamPtr   serverEventParam;

   // Fill out the queue entry (requesting logons and opens), then
   // queue it...
   
   gServerEventQEntry.callBack = 
         NewServerEventHandlerProc (HandleServerEvents);
   gServerEventQEntry.serverEventMask = 0;
   gServerEventQEntry.afpCommandMask[0] = 0;
   gServerEventQEntry.afpCommandMask[1] = 0;
   gServerEventQEntry.serverControlMask = 0;
   SetAFPFlag (&gServerEventQEntry, afpLogin, 
                        true, false, true);
   SetAFPFlag (&gServerEventQEntry, afpOpenFork, 
                        false, true, true);

   serverEventParam = &pb.serverEventParam;
   serverEventParam->scSEQEntryPtr = 
                  (Ptr) &gServerEventQEntry;
   serverEventParam->scCode = kSCInstallServerEventProc;
   err = ServerDispatchSync (&pb);

} // InstallServerEventHandler

extern void
RemoveServerEventHandler (void) {

   OSErr                     err;
   SCParamBlockRec         pb;
   ServerEventParamPtr   serverEventParam;

   serverEventParam = &pb.serverEventParam;
   serverEventParam->scSEQEntryPtr = 
               (Ptr) &gServerEventQEntry;
   serverEventParam->scCode = kSCRemoveServerEventProc;
   err = ServerDispatchSync (&pb);

} // RemoveServerEventHandler

extern void
DoResetCache (void) {

   OSErr                     err;
   SCParamBlockRec         pb;
   ResetCacheParamPtr   resetCacheParam;
   
   resetCacheParam = &pb.resetCacheParam;
   resetCacheParam->scCode = kSCResetCache;
   resetCacheParam->bitmap = kSCShrinkAllCaches;
   err = ServerDispatchSync (&pb);

} // DoResetCache

Handling Server Events

Probably the most complicated part of the W&F API suite is in server event handling. An application (or really, any piece of code) can register to receive notifications of different kinds of events. You can receive server control event notification (lets you know when someone makes a server control call). You can receive server events (such things as when a CD has been inserted and has become sharable). And you can receive AFP events - those relating to AppleShare clients: logons, opens, closes, reads, writes - you name it. A complete list can be found in the AppleShare IP SDK, with explanations and sample code on how to set the server event block so that you get called for the events you're interested in.

For our addition, we're installing a handler that looks at two AFP events: afpLogin and afpOpenFork. You can receive event notification either before or after it has executed; if you receive the event after it has executed, you'll know what the result of the call was (for the most part, whether it succeeded or failed).

The most important part about writing a server event handler is recognizing that the server calls such handlers immediately, regardless of the state of the machine. This means that you probably don't have access to the toolbox or other routines you might need, and, just as importantly, because the server is in the middle of doing something (such as servicing a user request), your event handler should do the minimum amount of work possible. The typical strategy is to have your handler quickly queue server event records in memory you've allocated earlier, and then process those queued records in your main event loop, when you can do what you please. The SDK shows how to do this in detail.

Another thing to be careful about is not to modify server event data "in place", because, very often, the data in the server event record points to live data in the server itself, and modifying it could adversely affect the server's operation, and the server may become displeased with you. Thankfully, in this server addition, all we need to do is count events, so there is no need to queue things up - just increment the appropriate counter and continue. Listing 5 completes our application with the routines to set up the server event handler and the event handler itself.

Listing 5: Server Event Handling

extern pascal void
HandleServerEvents (ServerEventQEntry* queueEntry,
                  ExtendedServerEventRecord* event) {

   // Since this is probably not main event time, there's limits on
   // what we can do.  However, it should be safe to increment counters,
   // based on which afp command it is...
   
   if (event->eventNumber == kSCStartAFPRequestEvt) {
      switch (event->afpCommand) {
         case afpLogin:
            gLoginCount += 1;
            break;
         case afpOpenFork:
            gAccessCount += 1;
            break;
         default:
            break;
      } // switch
   } // if

} // HandleServerEvents

extern void
SetEventFlag (ServerEventQEntry* queueEntry,
         UInt32 whichEvent, Boolean onOff) {

   UInt32 maskValue = 0x1 << whichEvent;

   if (onOff) {
      queueEntry->serverEventMask |= maskValue;
   } else {
      queueEntry->serverEventMask &= ~maskValue;
   } // if

} // SetEventFlag

extern void
SetAFPFlag (ServerEventQEntry* queueEntry, UInt32 whichEvent,
         Boolean inDo, Boolean inReply, Boolean onOff) {

   UInt32 maskValue0 = 0;
   UInt32 maskValue1 = 0;
   
   // Special case of AddIcon gets remapped to bit 0.
   if (whichEvent == afpAddIcon) {
      whichEvent = 0;
   } // if
   
   if (whichEvent >= 32) {
      maskValue0 = 1 << (whichEvent % 32);
   } else {
      maskValue1 = 1 << whichEvent;
   } // if
   
   if (onOff) {
      queueEntry->afpCommandMask[0] |= maskValue0;
      queueEntry->afpCommandMask[1] |= maskValue1;
   } else {
      queueEntry->afpCommandMask[0] &= ~maskValue0;
      queueEntry->afpCommandMask[1] &= ~maskValue1;
   } // if
   
   // Set the appropriate Event flag(s) so this actually gets called.
   
   if (inDo) {
      SetEventFlag (queueEntry, kSCStartAFPRequestEvt, onOff);
   } // if
   if (inReply) {
      SetEventFlag (queueEntry, kSCStartAFPRequestEvt, onOff);
   } // if

} // SetAFPFlag

Go Write a Server Addition

I hope that this brief exposure to server control and event handling for the AppleShare IP Web & File Server has been illuminating. There are literally hundreds of events that you could process in new and different ways, and server information that you can expose to the user. Several commercial products have been written using these APIs, and AppleShare IP users are always looking for additional tools to help them better administer their servers.

With a bit of imagination, you could come up with a piece of software that fills a void or expands the usefulness of the world's easiest-to-use Web & File Server.

Happy controlling.

Related Links

http://www.apple.com/appleshareip/


Erik Sea joined the AppleShare IP team at Apple in March, 1998, as the Engineering Lead for the File Server (versions 6.0 and 6.1). Before that, he worked in the PowerBook group on such products as Apple Location Manager and its friends. When not busy coding, he can be found herding his free-range slinky collection. You can reach Erik at sea@apple.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Parallels Desktop 12.0.0 - Run Windows a...
Parallels allows you to run Windows and Mac applications side by side. Choose your view to make Windows invisible while still using its applications, or keep the familiar Windows background and... Read more
Spotify 1.0.36.124. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
Firefox 48.0.2 - Fast, safe Web browser.
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
BBEdit 11.6.1 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
OmniGraffle Pro 6.6.1 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
OmniGraffle 6.6.1 - Create diagrams, flo...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
Dropbox 8.4.21 - Cloud backup and synchr...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
BetterTouchTool 1.84 - Customize Multi-T...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
ScreenFlow 6.1 - Create screen recording...
ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your entire monitor while also capturing your video camera, microphone and your... Read more
f.lux 37.7 - Adjusts the color of your d...
f.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day. Ever notice how people texting at night have that eerie blue glow? Or wake... Read more

Become the King of Avalon in FunPlus’ la...
King Arthur is dead. Considering the legend dates back to the 5th century, it would be surprising if he wasn’t. But in the context of real-time MMO game King of Avalon: Dragon Warfare, Arthur’s death plunges the kingdom into chaos. Evil sorceress... | Read more »
Nightgate (Games)
Nightgate 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: *** Launch Sale: 25% OFF for a limited time! *** In the year 2398, after a great war, a network of intelligent computers known as... | Read more »
3 best fantasy football apps to get you...
Last season didn't go the way you wanted it to in fantasy football. You were super happy following your drafts or auctions, convinced you had outsmarted everyone. You were all set to hustle on the waiver wire, work out some sweet trades, and make... | Read more »
Pokemon GO update: Take me to your leade...
The Team Leaders in Pokemon GO have had it pretty easy up until now. They show up when players reach level 5, make their cases for joining their respective teams, and that's pretty much it. Light work, as Floyd Mayweather might say. [Read more] | Read more »
Ruismaker FM (Music)
Ruismaker FM 1.0 Device: iOS Universal Category: Music Price: $4.99, Version: 1.0 (iTunes) Description: Following up on the success of Ruismaker, here's her crazy twin-sister, designed for people who want to design their own... | Read more »
Space Marshals 2 (Games)
Space Marshals 2 1.0.15 Device: iOS iPhone Category: Games Price: $5.99, Version: 1.0.15 (iTunes) Description: The sci-fi wild west adventure in outer space continues with Space Marshals 2. This tactical top-down shooter puts you in... | Read more »
Dungeon Warfare (Games)
Dungeon Warfare 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Dungeon Warfare is a challenging tower defense game where you become a dungeon lord to defend your dungeon against greedy... | Read more »
Solitairica (Games)
Solitairica 1.0.7 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0.7 (iTunes) Description: Solitairica takes RPG combat and challenging rogue-like progression to a fresh new place—the world of solitaire! | Read more »
Bowmasters tips, tricks and hints
At least for this writer, archery was one of the more pleasant surprises of the 2016 Rio Olympics. As opposed to target shooting with guns, which was dreadfully boring, watching people shoot arrows at targets was pretty darn cool. [Read more] | Read more »
Best apps for watching live TV
The Olympics have come and gone, leaving nearly everyone in a temporary state of "What the heck am I going to watch on TV right now?" Besides old reruns of Golden Girls, but that goes without saying. [Read more] | Read more »

Price Scanner via MacPrices.net

BookBook Releases SurfacePad, BookBook &...
BookBook has released three new covers just for iPad Pro: SurfacePad, BookBook and BookBook Rutledge Edition. BookBook for iPad Pro is a gorgeous leather case reminiscent of a vintage sketchbook.... Read more
Clean Text 1.0 for iOS Reduces Text Cleanup a...
Apimac today announced availability of Clean Text for iOS, a tool for webmasters, graphic designers, developers and magazine editors to reduce text cleanup and editing time, and also for any iPhone... Read more
27-inch iMacs on sale for up to $220 off MSRP
B&H Photo has 27″ Apple iMacs on sale for up to $200 off MSRP including free shipping plus NY sales tax only: - 27″ 3.3GHz iMac 5K: $2099 $200 off MSRP - 27″ 3.2GHz/1TB Fusion iMac 5K: $1899 $100... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 2016 and 2015 13″ MacBook Airs now available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 2016 13″ 1.6GHz/8GB/... Read more
Apple refurbished iPad mini 2s available for...
Apple is offering Certified Refurbished iPad mini 2s for up to $80 off the cost of new minis. An Apple one-year warranty is included with each model, and shipping is free: - 16GB iPad mini 2 WiFi: $... Read more
Save up to $600 with Apple refurbished Mac Pr...
Apple has Certified Refurbished Mac Pros available for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The following... Read more
Mac Pros on sale for $200 off MSRP
B&H Photo has Mac Pros on sale for $200 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2799, $200 off MSRP - 3.5GHz 6-core Mac Pro: $3799, $200... Read more
Will We See A 10.5″ iPad Pro in 2017? – The ‘...
A MacRumors report, cites a research note from KGI Securities analyst Ming-Chi Kuo, saying a new size iPad model is in the works. According to the highly respected Cho, who has a strong track record... Read more
IOGEAR USB-C Docking Station Transforms Lapto...
IOGEAR has announced the launch of its innovative USB-C Docking Station with Power Delivery which turns USB-C enabled laptops into desktop workstations. The new IOGEAR USB-C Docking Station features... Read more
12-inch Retina MacBooks on sale for up to $10...
Amazon has 2016 12″ Apple Retina MacBooks on sale for $100 off MSRP. Shipping is free: - 12″ 1.1GHz Space Gray Retina MacBook: $1199 $100 off MSRP - 12″ 1.1GHz Silver Retina MacBook: $1224.99 $75 off... Read more

Jobs Board

*Apple* Professional Learning Specialist - A...
# Apple Professional Learning Specialist Job Number: 51234243 Portland, Maine, Maine, United States Posted: Aug. 18, 2016 Weekly Hours: 40.00 **Job Summary** The Read more
*Apple* Mobile Master - Best Buy (United Sta...
What does a Best Buy Apple Mobile Master do? At Best Buy, our mission is to leverage the unique talents and passions of our employees to inspire, delight, and enrich Read more
*Apple* Retail - Multiple Positions Akron, O...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Simply Mac *Apple* Specialist- Repair Techn...
…The Technician is a master at working with our customers to diagnose and repair Apple devices in a manner that exceeds the expectations set forth by Apple Read more
*Apple* Retail - Multiple Positions Germanto...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.