TweetFollow Us on Twitter

Gossip Net
Volume Number:3
Issue Number:8
Column Tag:Advanced Mac'ing

Build a Gossip Net on Appletalk in C

By Ty Shipman, San Diego, CA

Introduction

What? I don’t understand! Where’s the tea? Like Arthur Dent these phrases have been commonly uttered by most (if not all) programmers who have looked at and used the chapter on AppleTalk. But, hopefully with the running example program presented here you won’t hear those famous phrases quite so often.

The program entitled DON’T PANIC is a simple AppleTalk communications program using Apple’s Pascal Interface. The program will allow a user to become an active node on the network, see who else is on, and send and receive messages over that network. A perfect example of a simple gossip net on Appletalk as MacTutor’s own Shelly and Mona demonstrate in the figures shown on the right.

I’m assuming that you have a working knowledge of ‘C’, if you do not my code is heavily commented. You should have read the section “Calling The AppleTalk Manager From Pascal” in the chapter on AppleTalk Manager. If you have not read the section skim it, now, then read the rest of the article. By the way, don’t read the whole section, just the sub-sections on AppleTalk Transaction Protocol(ATP) and Name Binding Protocol(NBP).

Now that you have skimed the sections lets go over some of the important vocabulary you should become familiar with:

Network- a collection of 1 or more zones.

Zone- a collection of at least 2 nodes.

Node- a station(device) that occupies only one address on the zone.

Socket- the subaddress that does all the work.

Requester- The node that starts a session.

Responder- The node to which the requester sent the first message.

Because of the lopsided construction of the ATP, these definitions can change depending on the way you set up your application. For example, a requester could send a message to a responder to instruct it to be the requester, after this message. At that point the definitions still hold, but the logic does not. Some print servers do this because more data is sent to the printer than from the printer to the host.

InterNet

Address- the zone, node and socket numbers that are known to the net.

Entity- the name, type, and zone by which a device is known. This is the only way you want to access other devices.

Tuple- the result that is formed with the Entity and the corresponding InterNet Address.

BDSElement- (Buffer Data Structure) is a structure that contains information required to access the data dealing with a response.

Mona asks Shelley about Boston on gossip net

Setup

Lets start our discussion with the function of ATCheck(). This function is the first step in setting up any calls to the AppleTalk Manager in any language. The routine takes care of checking and setting the two system globals, explained below, and opening the correct drivers and system routines. The drivers that must be loaded and opened are the .MPP(#9) and .ATP(#10). On the 128k Macintoshes the NBP routines must be loaded explicitly so this routine checks to make sure that it is loaded -- as well.

The two system globals that should be examined before any thing is done with the AppleTalk drivers are the Port Configuration byte, SPConfig, and PortBUse. SPConfig located at 0x1FB, contains both the current configuration for both the modem (Port A) and printer (Port b) ports. Figure 1 shows the layout of these bytes. You will notice for AppleTalk we only need be interested in bit number 1 of SPConFig. If this bit is set then we may not open AppleTalk because a Serial Driver is currently installed in the port. The other global is the PortBUse byte at 0x291. This byte tell us the current condition of port B (printer port)on the Macintosh 1. Currently, we only need to look at the low nibble and the high bit. If the low nibble is set to 4 then the ATP driver has been loaded and a node number has been assigned to this station. If the high bit is NOT set this could mean that the port is currently being used by some other client (If you are this far in the examination of the port bits, then the client is probably an AppleTalk client), otherwise it could be a special driver associated with this port. (See figure 1)

Shelley responds on gossip net

Lookup

Now that the drivers are loaded and running you will want to see who or what is currently on the net. This is done through my routine Lookup(). In this routine you will: get this device’s node and zone number; open a socket to receive requests; register the name, type, and zone, with the respective socket, node, and zone number on the network; do a network zone lookup; and set up to receive requests from other devices.

You will notice I stated, “a network zone lookup.” This is because of the redefinition of the wildcard ‘*’ for the zone. Apple had originally wanted this to be “all zones” but changed it later to mean “the current zone” only. So what does that mean -- you can’t look up anything on another zone unless you know the zone name or number -- ahead of time. I hope this will be fixed, otherwise you will have to write you own lookup type function.

The node and zone numbers were assigned when you opened the drivers in ATCheck(). The zone number is set to zero unless there is a bridge on the zone and then some non-zero number may be assigned. The node number the Macintosh receives is a unique number that may or may not be the same as the last time. It is not a good idea to depend upon the node number being the same even if you have not added other devices to the net. If the user changes or updates the system on his or her station the number may no longer be the same, but the name should be. We will talk about the name a little bit later.

If you are following along in the code as I explain the different routines you will notice that Inside Macintosh has the argument atpSocket of the ATPOpenSocket() defined as a pointer to a byte. In my code it is a pointer to an integer. The difference is that Apple’s byte here is 16 bits long. Don’t ask why, just watch out for this inconsistency , it took me three day to find this oversight (thanks Steve).

A zone lookup is done quite simply. By setting six fields in the .nbpProto and calling the NBPLookup() function you will get back all of the named entities known on the current zone. If you are wondering how to filter out unwanted responses you set the elements of the .nbpEntity structure to what you want returned. The entity is a character string representation of a name (object), a type and a zone. So, lets say you only wanted every thing from type “GORILLA”; you would just set the type to “GORILLA” and leave the name (object) and zone in the wildcard state.

Another structure we have not seen is the retransmission information. This structure contains two byte quantities that specify the time-out, in 8 tick quantities, and the number of retries the system should attempt during the NBPlookup() calls. You want the time-out to be large because some device may be busy and may not be able to respond immediately If the retries is set to 0 the system will execute the call 0 times. (It should be noted here that most of the other calls requiring the retransmission information take the time-out in seconds and the retries as n + 1 times). The buffer that holds the returned tuples is accessed by the pointer .nbpBufPtr. The size of the buffer should be large enough to handle all expected tuples: 33 bytes for the object, type, and zone; and a byte each for the socket and node, then an integer for the zone number.

To keep the code simple you want to make this call synchronous. The number of named entities on the zone are returned in .nbpDataField. This field is set to 0 and incremented with each tuple found. It does not include your name because a requester and responder can not be on the same node. And if you don’t know who you are you’re in sorry shape.

Notice that the Receive() function is called a total of five times. This is for two reasons. One, you should be able to receive more than one request at a time. You may not be able to respond to more that one at a time, but you should be able to receive them. Second, when most of the asynchronous Pascal Interfaces routines complete they post an event in the event query that looks like a Request from another Macintosh and use up the queued ATPGetRequests(). The actual calls to NetReg() and Receive() are covered below.

Net Registration

The NetReg() routine does exactly what the name implies. It takes a name (or object), a type, a zone and attempts to register them in the socket listening table. This is done so other devices can do a lookup and see your device is active on the network. The structure myEntity contains the name of your Macintosh, this is retrieved from a dialog item that came originally from an owned system resources(-16096). The type can be set to just about any thing you want, I put in “GORILLA”; and the zone field set to the current zone, ‘*’. The character array myEBuff is used internal to NBP. The .aSocket field should be filled with the socket number you opened to receive requests through. I did the call synchronously to keep track of any errors that might have occurred.

Receiving Requests and Sending Responses

The setup to receive a request is probably the simplest of all the calls. Before calling the ATPGetRequest() the following fields must be set up. The field .atpSocket, must be set to the socket number returned when from ATPOpenSocket(). The fields .atpDataPtr and atpReqCount should be set to a buffer large enough to store the requests. I’m using a buffer of 256 bytes because you are limited to 256 bytes in a Pascal string. The call is done asynchronously, if you do it synchronously you will be hung here waiting for a request to come through.

Once you receive a request you should respond to whatever action the request required. DON’T PANIC just sends a message saying “... I got your request everything is OK ...”. In a higher level application the request may have asked for data from a disk or the current processor status. The data that is going to be sent back is referenced by the Buffer Data Structure (BDS) and is known through the .atpRspBDSPtr. If your higher level application has sent out more that one request and expects back more than one response you will need to keep track of which responses correspond to the correct requests. This is done through the .atpTransID. My program deals with only one response so, I just set the .atpTransID to the request ID. The .atpEOM is set to TRUE and the .atpNumBufs and .atpBDSSize fields are set to one; because DON’T PANIC sends only one BDS. The requester’s InterNet Address is contained in the fields of .aNet, .aNode, and .aSocket of the request record. The socket the response is sent through is set in the .atpSocket field of the response record. I choose to send it through the socket which my requests are received. Depending on which way your application is set up you may want to do this action either asynchronously, or synchronously. Given a choice I always choose synchronously, because it makes the overall program structure simpler.

A little on BDSElements. The BDS is just a structure to hold information about the data. It has a pointer to and the size of the data as well as a userbytes and buffer size. This means that the data itself should be in some safe area that does not move around when the system does a heep compact. DON’T PANIC uses a global character array for the data, it send the same response message every time.

Extracting Target and Send Requests

The first step in sending a request to another device is choosing that device. There are some restrictions in choosing that device in DON’T PANIC. One, the device should be in the lookup list; if it is not it probably is not an active device on this zone. Two, it must be of the type “GORILLA” to return the correct response. Because we did a Lookup() with no filter all named devices on the current zone will be listed.

Setting up the NBPExtract() routine can be a little tricky. The second argument to the function is the number of tuples in the list. This is contained in the .nbpDataField of the abrecord passed to NBPLookup(). The pointer variable, thebuffer, should point to the same buffer used during the NBPLookup(). Also, you must have defined structures of the type EntityName and AddrBlock. These two structures store the name and InterNet Address of the selections referenced by the argument whichOne. WhichOne is an integer that specifies which element of the tuples array should be set in the AddrBlock and EntityName structures.

Now that we have selected our target lets set up the request. The request contains a Pascal string that you entered using a editable field in the dialog. The address of the target is contained in the AddrBlock which was set in the NBPExtract() call. So, set the .aNet, .aNode, and .aSocket in the request abrecord to the .aNet, .aNode, and .aSocket of the AddrBlock respectfully. Now set up the data part of the request using the .atpReqCount and the .atpDataPtr. The .atpRspBDSPtr is set to the element that will be used when a response is received. The .atpXO is set to FALSE because, we do not wish to sent as an exactly once transaction type. If you glance down to the end of the routine there is a call to the routine Receive(). This is to offset the event posted asynchronous call to ATPSndRequest() is completed.

Event Processing

Now that we can send requests, receive requests, send responses and receive responses; how do we deal with them? When you receive a request or send a request, an event of the type networkEvt is posted. So, just like any other event you process it in your main event loop. To cope with the event I defined a routine DoATNet() that has only one argument. The argument is the event’s message field which contains a handle to the abrecord associated with this event.

In the DoATNet() routine I look at the handle to see if it belongs to a request I sent or a request some other device sent. If I sent the request two possibilities occurred. One, the routine completed and a response was received; in that case I process the response. Two, the routine completed and no response was received. I can tell the difference by examining the .atpNumRsp of the record. If the .atpNumRsp is greater that 0 a response was received.

If the event was a request that some other device sent I place the data pointed to by .atpDataPtr in the dialog static text field. Then I send a response to the requester. If you were writing a higher level application you would act on the request and then send the appropriate response.

Closing

When closing your AppleTalk application you should close all opened sockets and remove your station from the socket lookup table. This is done by calling ATPCloseSocket() and NBPRemove() respectively.

Notes

You may have noticed that I have not talked much about the dialog. This was not an oversight. I did not want to make this article any longer. Just a few notes though. The code presented in the file main.c is every thing you need to work the example. The code is solid so don’t play with it. If you do, I don’t know what will happen. The code is very plain, handling just what it has to and nothing more. The resource output, from MPW DeRez, is presented at the end of the article. If you don’t have a resource complier available I have given a numbered printout of the screen (Figure 2). The important items have a small numbers next to them. If you change the items listed just change the #defines in the headers. You should also include the resource ‘atpl’ from the ATalk/ABPackage in your application resource. This file contains a small bit of glue code that most Pascal Interfaces need.

You should also include the library AppleTalk.Lib in your project, for a full list just see Figure 3 .

Notes on LS C

[Think Technologies has released Lightspeed C 2.11, which was used to compile this code. However, despite Think’s claim that 2.11 is Macintosh II compatible, this code does not work on a Mac II. It crashes with an ID 1 error when you attempt to send a message. It does work on a Plus with the new system files. Think admits that version 2.11 does not include the latest Appletalk software available and suggest you contact the company about getting this. If you identify why this program doesn’t work on the Mac II as written, please write to the Editor care of MacTutor. -Ed]

The All Important Users Guide

Before double clicking on the application icon go to the DA “Chooser”. There, type the name you wish to be known by on the network. Now you are ready to enter the world of networking -- good luck and may the force be with you!

After the window is put up the drivers are opened and your node number is assigned. By clicking the button “LOOKUP” your current node number and the socket number you received will appear in the upper right corner. After the lookup is complete you will see the total number of named entities plus one appear in the middle of the right hand side. Then by clicking the button “DISPLAY” you can choose a target. After you have typed a message in the edit field click the send button once. If you click it more than once you will queue up the whole new ATPSndRequest() process. Also, you could run the risk of hanging your Macintosh if you queue up too many requests. By the way don’t send messages to tuples that are not of the type “GORILLA”. [Mona and Shelley just LOVE seeing that description on their screens! -Ed]

Conclusion

Apple has updated their Pascal Interface to AppleTalk. The update is available from APDA as #KMSAMU. The update fixes some known bugs in the Interface and removes the glue code in the ‘atpl’ resource. It also does much more but I’m not going to cover that. Even with the update the Pascal Interface is still buggy. Apple recommends that you do all “real” AppleTalk calls through PB Block type call using the Device Manager. If there is time I will try to write another article using the Device Manager call to replace all the calls presented. But until then read the three articles on AppleTalk in MacTutor’s September and October 1985. They were written by Bob Denny and Alan Wootton and are very good.

Ty Shipman is a contract programmer and can be reached in San Diego, CA.

 /*
 THIS PROGRAM WILL OPEN A NODE ON THE APPLE 
 TALK NETWORK. IT WILL THEN FIND ALL THE NODES OUT       THERE.  IF THE 
USR ENTERS A MESSAGE IN THE EDIT FIELD AND PRESSES “SEND” THE USR CAN 
ALSO OPEN A RECIEVING SOCKET FOR PACKETS SENT TO IT.
 FILE:
 ATSIMPLE.C 
 BY:
 TY SHIPMAN
 (C) Copyright 1987 Ty Shipman for MacTutor
 All Rights Resereved by author.
This notice must appear in all copies. Commerical use of this code forbidden, 
without prior written permission of the author.
*/
#include “MacTypes.h”
#include “DialogMgr.h”
#include “EventMgr.h”
#include “ControlMgr.h”
#include “DON’T PANIC.h”

/* GLOBALS NEEDED TO RUN PROGRAM */
WindowPtr myWindow;  /* dialog window struct */
EventRecord myEvent; /* event that just happened*/
Handle  NodeName;  /* name rsrc hndl (chooser) */

main() 
{
 InitGraf(&thePort);
 InitFonts();
 FlushEvents( everyEvent, 0 );
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(0L);
 InitCursor();
 MaxApplZone();
 if( !Setup() )
 while (DEvent()) ;
 ExitToShell();
}

Setup()
{
 if( PutUpWindow() ) /* get window */
 return(1); 
 return(0); 
}

PutUpWindow()
{
 /* this routine will put up the dialog specified and init all buttons 
to default condition.  It will also check to see if the ports are configured 
for AppleTalk or not. */

ControlHandle  itemHand;
inttype;
Rect    SelBox;

CouldDialog(DIALOG_ID); /*read into memory*/
myWindow = GetNewDialog(DIALOG_ID,NIL,(WindowPtr)-1);
NodeName = GetResource(‘STR ‘, -16096); /* Chooser unit name*/
LoadResource(NodeName); /* station Chooser name */
SetPort(myWindow); /* hook in quickdraw  */
GetDItem(myWindow,DISPLAYB,&type,&itemHand,&SelBox);           /*
 THE DISPALY BUTTON, NO ITEMS IN LIST YET */
HiliteControl(itemHand,255);/*part 255, dim out*/
GetDItem(myWindow,SENDB,&type,&itemHand,&SelBox);        /*  I DON’T 
KNOW WHOM TO SEND TO YET */
HiliteControl(itemHand,255);/*part 255, dim out*/
WindUpdate();    /* do all the boxing necessary */

if( ATCheck() )  /* at this time get net stuff */
 return(1);

ShowWindow(myWindow);/*all of a sudden up*/
return(0);
}

DEvent() 
{
WindowPtr whichWindow;
intitem;
DialogPtr theDialog;
 
SystemTask();
if (GetNextEvent(everyEvent, &myEvent)) 
{
 switch (myEvent.what) 
 {
 case driverEvt:
 case networkEvt:
 {
 DoATNet(myEvent.message); /*param block */
 break;
 }
 case mouseDown:
 switch (FindWindow( myEvent.where,  &whichWindow )) 
 {
 case inGoAway:
 if (TrackGoAway( myWindow, myEvent.where) )
 {
 CloseAT();
 return(0);
 }
 break;
 }
 case updateEvt:
 {
 WindUpdate(); 
 break;
 }
 default: ;
 } /* end of case myEvent.what */
 
 IsDialogEvent(&myEvent);
 if( DialogSelect(&myEvent,&theDialog,&item) )
 if( theDialog == myWindow) /* some DA’s are Dialogs */
 DoATDialog(item);
 } 
 else
 {
 IsDialogEvent(&myEvent);
 /* so the caret blinks */
 DialogSelect(&myEvent,&theDialog,&item);
 }
 return(1);
}

DoATDialog(item)
intitem;/*the item just hit*/

{  /* respond to the item hit on the dialog */
 switch(item)
 {
 case LOOKB:
 {
 LookUp();
 break;
 } 
 case DISPLAYB:
 { 
 Display();
 break;
 }
 case SENDB:
 {
 Send();
 break;
 }
 default:
 break;
 }/*end switch*/
}

WindUpdate()
{/* draw all the stuff mwhen an update occures */
 
 ControlHandle   itemHand;
 int    type;
 Rect   SelBox;
 
 GetDItem(myWindow,RECM,&type,&itemHand,&SelBox);
 InsetRect(&SelBox,-1,-1);
 FrameRect(&SelBox); /* RECIEVED MESSAGE BOX */
 GetDItem(myWindow,MYNODE,&type,&itemHand,&SelBox);
 SetIText(itemHand,(*NodeName)); /* name set is chooser */
 FrameRect(&SelBox); /* WHOM AM I FIELD */
 GetDItem(myWindow,MYNUM,&type,&itemHand,&SelBox);
 InsetRect(&SelBox,-2,-2);
 FrameRect(&SelBox); /* WHOM AM I node number*/
 GetDItem(myWindow,SENDM,&type,&itemHand,&SelBox);
 InsetRect(&SelBox,-2,-2);
 FrameRect(&SelBox); /* WHOM AM I FIELD */
 GetDItem(myWindow,NUMBER,&type,&itemHand,&SelBox);
 InsetRect(&SelBox,-2,-2);
 FrameRect(&SelBox); /* NUMBER OF NODES/SOCKETS */
 GetDItem(myWindow,CNODEIS,&type,&itemHand,&SelBox);
 InsetRect(&SelBox,-2,-2);
 FrameRect(&SelBox); /* CURRENT NODE NUMBER *
 GetDItem(myWindow,CNODENUM,&type, &itemHand, &SelBox);
 InsetRect(&SelBox,-2,-2);
 FrameRect(&SelBox); /* CURRENT NAME OF NUMBER */
}

/*
 File:
 “Don’t Panic” 
 an AppleTalk application
 By:
 Ty Shipman,  Gorilla Software Systems
 (C) Copyright 1987 Ty Shipman for MacTutor
 All Rights Resereved by author.
This notice must appear in all copies. Commerical use of this code forbidden, 
without prior written permission of the author.
*/

#include “MacTypes.h”
#include “DialogMgr.h”
#include “EventMgr.h”
#include “ControlMgr.h”
#include “Appletalk.h”

#include “DON’T PANIC.h”

 /* all the globals*/

CurEntity NowEntity; /* target for request */
char  *ErrMess;  /* ptr to error message */
ABRecHandle atpRec;/* handle to the above */
Str255  atpRecBuf; /* hold request data in */
intRecSocket;  /* receive requests */
char  LUBuffer[2000];/* my lookup buffer */
intExtractwhere; /* place in extract list */
ABRecHandle myABRecord; /* for my mac reg on net */
EntityNamemyEntity;/* store name,type,zone */
char  myEBuff[105];  /* wasted space, max 105 */
ABRecHandle Responce;/* for responce stuff */
BDSElementRspBDS;
char  *BDSmess = “\pI recieved your message, requester”;
BDSPtr  myBDSPtr;  /* used in the request stuff */
BDSElementmyBDS;   /* used to reference responce */
char  mysBuffer[512];/* store responce here */
ABRecHandle SendRec;

 /* end of globals */

extern  DialogPtrmyWindow;

doAlloc()
{
 /* allocate all the records needed */
 myABRecord = (ABRecHandle)NewHandle(sizeof(ABusRecord)); 
/*for my mac reg on net */
 Responce = (ABRecHandle)NewHandle(sizeof(ABusRecord));
 SendRec = (ABRecHandle)NewHandle(sizeof(ABusRecord));
}

ATCheck()
{
 char *PortBUseP; /* a pointer to PortBUse Byte */
 char *SPConfigP; /* SerialPortConfig byte */
 char use,con;   /* storage for pointer contence*/
 int    error;

 /*check to see if the port is set up for
 AppleTalk. It will also load the ATP, NBP, and
 any other packages necessary for AppleTalk.
 */

 CouldDialog(ERRDLOG_ID); /*read into memory*/
 PortBUseP = (char *)(PortBUse); /* a ptr to PortBUse Byte*/
 SPConfigP = (char *)(SPConfig);  
 /* a pointer to SerialPortConfiguration byte*/
 use = *PortBUseP; /* get the data */
 con = *SPConfigP; /* get the data */

 if( (con & 0x0F) > useATalk )
 {
 ErrMess = “\pSomething wrong with the port(config).  Exit now!”;
 ErrDial(); /* put up error message in dialog */
 return(1); 
 }

 if( (use & 0xFF) || ( (use&0x0F) == useATalk) ) 
 {
 /* port is currently closed, open as I like  
 or it’s AppleTalk */
 if( !(IsMPPOpen()) )
 { 
 if( (error = MPPOpen()) != noErr) 
 {
 ErrMess = “\pSomething wrong with the port(MMP).  Exit now, and restart 
your system!”;
 ErrDial(); 
 /* put up error message in dialog */
 return(1);
 }
 }

 if( !IsATPOpen() )
 {
 if( (error = ATPLoad()) != noErr)
 {
 ErrMess = “\pSomething wrong with the port(ATP).  Exit now!”;
 ErrDial(); 
 /* put up error message in dialog */
 return(1);
 }
 *PortBUseP |= 0x04; /* ATP loaded */
 *PortBUseP &= 0x7F; /* in use set, set=0*/
 }
 if( (error = NBPLoad()) )/* only on 128 MAC’s*/
 {
 if(error != noErr)
 {
 ErrMess = “\pSomething wrong with the port(NBP).  Exit now!”;
 ErrDial(); 
 /* put up error message in dialog */
 return(1);
 }
 } 
 } /* end test on port config and use */
 else
 {
 ErrMess = “\pUse bits not correct”;
 ErrDial();
 return(1);
 }
 *SPConfigP |= 0x01; /* set for appleTalk */
 doAlloc();
 return;
}


LookUp()
{
 /* lookup everything on current zone. */

 long   junklong;
 Str255 string,jstr;
 char   *tempstr;

 int    myNet,myNode;
 AddrBlockatpaddrs;
 /* a filter of who I receive requests through */
 EntityName searchEntity; 
 /* who I should search for during lookup */
 Handle itemHand;
 int    type;
 Rect   SelBox;
 int    error;
 if( atpRec == 0 )
 {
 /* only executed first time it is called */
 atpRec = (ABRecHandle)NewHandle( sizeof(ABusRecord));         
 /* record for the ReceivedRequest  */
 RecSocket = MYSOCKET;
 atpaddrs.aNet   = (int)0;/* wild, all */
 atpaddrs.aNode = (Byte)0;/* wild, all */
 atpaddrs.aSocket = (Byte)0;/* wild, all */
 if( (error = ATPOpenSocket(atpaddrs,&RecSocket)) != noErr)
 {
 ErrMess = “\pSomething wrong ATPOpenSocket.”;
 NumToString( (long)error, string);
 Pappend(ErrMess,string); /* give error number */
 ErrDial();
 return(1); 
 /* for some error control return 1 
 else return a 0 */
 }
 NetReg();
 /* register name and node/socket on net */
 if( GetNodeAddress(&myNode,&myNet) != noErr)
 {
 /* Done only to display on the screen, not                    necessary 
normally because I have opened the receive               socket above 
I know the  InterNet address.*/
 ErrMess = “\pSomething wrong GetNodeAddress”;
 ErrDial();
 }
 junklong = (myNode);
 NumToString( junklong, jstr);/* put the node number in a char string 
*/
 Pstrcopy(string,jstr);
 tempstr = “\p:”;/* just a colon */
 Pappend(string,tempstr);
 junklong = RecSocket;  
 /* no longer contains 0,convert to string*/
 NumToString(junklong,jstr);
 Pappend(string,jstr);  /* add to string */
 
 tempstr = “\p@”;
 Pappend(string,tempstr);
 junklong = myNet;
 NumToString(junklong,jstr);
 Pappend(string,jstr);  /* add net number */
 GetDItem( myWindow,MYNUM, &type,&itemHand,&SelBox);
 SetIText(itemHand,string); 
 /* display the InterNet Address */
 }
/*now I have my name and node I want to know       everyone else */
 tempstr = “\p=”;/*wild card for obj and type*/
 Pstrcopy(searchEntity.objStr,tempstr);
 Pstrcopy(searchEntity.typeStr,tempstr);
 tempstr =”\p*”; /* wild card for this zone only */
 Pstrcopy(searchEntity.zoneStr,tempstr); /* this zone is */

 (**myABRecord).nbpProto.nbpEntityPtr = &searchEntity;         
 /* set up filter for search of net */
 (**myABRecord).nbpProto.nbpBufPtr = (Ptr)&LUBuffer[0];        
 /* used internal by NBP */
 (**myABRecord).nbpProto.nbpBufSize = sizeof(LUBuffer);        
 /* where the returned names go */
 (**myABRecord).nbpProto.nbpDataField = 100;                   /* about 
100 node out there??? */
(**myABRecord).nbpProto.nbpRetransmitInfo. retransInterval=75; 
 /* in 8 tick counts, 75 = 10 sec*/
(**myABRecord).nbpProto.nbpRetransmitInfo. retransCount = 2;   
 /* set up the retry stuff */
 if( (NBPLookup(myABRecord,SYNC) != noErr) )
 {
 ErrMess = “\pSomething wrong NBPLookupTo many node maybe?”;
 ErrDial();
 }
 junklong = (long)(**myABRecord).nbpProto. nbpDataField + 1;
/*.nbpDataField tells how may named enities were on the net*/
 if( junklong > 1)
 {
 GetDItem(myWindow,DISPLAYB,&type,&itemHand,&SelBox);
 HiliteControl(itemHand,0); /*part0, turn on*/
 }
 NumToString( junklong, string);
 GetDItem(myWindow,NUMBER,&type,&itemHand,&SelBox);
 SetIText(itemHand,string); /* display how may */
 Extractwhere = 1; /* start here on extract */
 Receive(0);
/* now that someone maybe on the system set up to        receive a request 
*/
 Receive(1);
 Receive(1);
 Receive(1);
 Receive(1);
/* put in a bunch of request receives in the que */
 return(0); /* good return*/
}
NetReg()
{
 /* this routine will register name, type on net */
 Handle itemHand;
 int    type;
 Rect   SelBox;
 char   *tempstr;
 Str255 string;
 int    error;
 /* set up the name, type, and zone of this Mac */
 GetDItem(myWindow,MYNODE,&type,&itemHand,&SelBox);            
 /*my usr name, set with the chooser */
 GetIText(itemHand,&myEntity.objStr); /* put in usr name */
 tempstr = “\pGORILLA”; /* a Ty Shipman Type! */
 Pstrcopy(myEntity.typeStr,tempstr);
 tempstr = “\p*”;/*  current zone */
 Pstrcopy(myEntity.zoneStr,tempstr);
 (**myABRecord).nbpProto.nbpEntityPtr = &myEntity;             
 /* set up my name */
 (**myABRecord).nbpProto.nbpBufPtr = (Ptr)&myEBuff;      
 /* used internal by NBP */
 (**myABRecord).nbpProto.nbpBufSize = sizeof(myEBuff);
 (**myABRecord).nbpProto.nbpAddress.aSocket = RecSocket;       
 /* can not be allocated on fly, set to       opensocket */
(**myABRecord).nbpProto.nbpRetransmitInfo.retransInterval = 7; 
  /* in seconds */
(**myABRecord).nbpProto.nbpRetransmitInfo.retransCount = 1;
 /* register the name on the system */
 if( (error = NBPRegister(myABRecord,SYNC) ) != noErr)
 {
 ErrMess = “\pSomething wrong with NBPRegister, try a different name.”;
 NumToString( (long)error, string);
 Pappend(ErrMess,string); 
 /* give me the error number */
 ErrDial();
 return(1);
 }
 return(0);
}
Receive(redo)
int redo;
{
 /* place a GetRequest in the Que */
 int    error;
 Str255 tempstr;

 if( redo == 0)
 {
 (**atpRec).atpProto.atpSocket   = RecSocket;
 (**atpRec).atpProto.atpReqCount = 255;/* not len byte*/
 (**atpRec).atpProto.atpDataPtr = (Ptr)&atpRecBuf; 
 /* my sting to store message */
 }
 if( (error = ATPGetRequest(atpRec,ASYNC)) != noErr)
 {
 ErrMess = “\pSomething wrong ATPGetRequest.”;
 NumToString( (long)error, tempstr);
 Pappend(ErrMess,tempstr);
 ErrDial();
 return(1);
 }
 return(0);
}
SendResponce(request)
ABRecHandle request;
{
 /*This routine will send a response to a request from another device. 
The socket I want to send the response to is contained in the request 
packet. */

 int    error;   /* a temp area */
 Str255 tempstr;
 SetupRsp(&RspBDS,BDSmess,256);  
 /* copy message to BDS specified */
 (**Responce).atpProto.atpSocket = RecSocket;                  
 /* send out socket which request was sent */
 (**Responce).atpProto.atpAddress.aNet = (**request).atpProto.atpAddress.aNet;
 /* contains the requesting address */
 (**Responce).atpProto.atpAddress.aNode = 
(**request).atpProto.atpAddress.aNode;
 (**Responce).atpProto.atpAddress.aSocket = 
(**request).atpProto.atpAddress.aSocket;
 (**Responce).atpProto.atpRspBDSPtr = (BDSPtr)&RspBDS;
 /* pointer to BDS, data should be in BDS */
 (**Responce).atpProto.atpTransID =  (**request).atpProto.atpTransID;
 (**Responce).atpProto.atpEOM = TRUE;/* only one responce here */
 (**Responce).atpProto.atpNumBufs = 1; /* only one buffer envolved here 
*/
 (**Responce).atpProto.atpBDSSize = 1;
 if( (error = ATPSndRsp(Responce,SYNC)) != noErr)
 {
 ErrMess = “\pSomething wrong ATPSndRsp.”;
 NumToString( (long)error, tempstr);
 Pappend(ErrMess,tempstr);
 ErrDial();
 return(1);
 }
 return(0);
}
SetupRsp(BDS,mess,size)
BDSElement*BDS;  /* ptr to BDSElement to use */
char    *mess; /* the message I am to copy in */
intsize;/* number bytes to copy into BDS */
{

 BDS->buffSize = mess[0] + 1; 
 /* only one packet, of max 256 bytes*/
 BDS->buffPtr = mess;/* ptr to data to send */

/* Notes:
 .dataSize set by reciever of this packet. i.e. the re         
 quester.
 .userBytes set by sender in ATP.*/
}
Display()
{
 /* display all nodes/sockets on the screen */

 Handle itemHand;
 int    type;
 Rect   SelBox;
 Str255 currstrg, string;
 char   *tempstr;

 if( ((**myABRecord).nbpProto.nbpDataField) <= 0)
 return;
 /* just in case there is nothing in the lookup */
 if( (NBPExtract((Ptr)&LUBuffer,(**myABRecord). nbpProto.nbpDataField, 
Extractwhere,&(NowEntity. CurName),&(NowEntity.CurAddrs)) ) != noErr)
 {
 ErrMess = “\pSomething wrong NBPExtract.”;
 ErrDial();
 }
 GetDItem(myWindow,SENDB,&type,&itemHand,&SelBox);
 HiliteControl(itemHand,0); /*part0, turn on*/

 /* has nothing to do with AppleTalk, just so I can display it on the 
screen. Uses the standard display format of name : type @ zone */

 Pstrcopy(currstrg,NowEntity.CurName.objStr);
 tempstr = “\p:”;/* the seperator for name and type */
 Pappend(currstrg,tempstr);
 Pappend(currstrg,NowEntity.CurName.typeStr);
 tempstr = “\p@”;/* the seperator for name and type */
 Pappend(currstrg,tempstr);
 Pappend(currstrg,NowEntity.CurName.zoneStr);
 GetDItem(myWindow,CNODEIS,&type,&itemHand,&SelBox);           
 /* do the name */
 SetIText(itemHand,currstrg);

 NumToString( (long)NowEntity.CurAddrs.aNode, string);
 Pstrcopy(currstrg,string);
 tempstr = “\p•”;/* just some filler */
 Pappend(currstrg,tempstr);
 NumToString( (long)NowEntity.CurAddrs.aSocket, string);
 Pappend(currstrg,string);
 GetDItem(myWindow,CNODENUM,&type,&itemHand, &SelBox);
 SetIText(itemHand,currstrg);

 /* all done with the screen display */

 if( (Extractwhere < (**myABRecord).nbpProto.nbpDataField) )
 Extractwhere++; /* the next time */
 else
 Extractwhere = 1; /* reset to beginning of list */
 return;
}
Send()
{
 /* send a request that contains the editable
 text field of the dialog */

 int    error;
 Str255 tempstr;
 Handle itemHand;
 int    type;
 Rect   SelBox;

 myBDSPtr = (BDSPtr)&myBDS;
 myBDSPtr[0]->buffPtr = (Ptr)&mysBuffer[0]; 
 /* buffer that will contain message */
 myBDSPtr[0]->buffSize = 512;
 GetDItem(myWindow,SENDM,&type,&itemHand,&SelBox);
 GetIText(itemHand,(Ptr)&mysBuffer[0]);/* put in message */
 (**SendRec).atpProto.atpAddress.aNet = NowEntity.CurAddrs.aNet;
 (**SendRec).atpProto.atpAddress.aNode = NowEntity.CurAddrs.aNode;
 (**SendRec).atpProto.atpAddress.aSocket = NowEntity.CurAddrs.aSocket;
 
 /* setup the target address */

 (**SendRec).atpProto.atpReqCount = PLength(mysBuffer);
 (**SendRec).atpProto.atpDataPtr = (Ptr)&mysBuffer[0];
 /* setup the request data */
 (**SendRec).atpProto.atpRspBDSPtr = (BDSPtr)&myBDSPtr;
 (**SendRec).atpProto.atpXO = FALSE; /* set to NOT exactly once */
 (**SendRec).atpProto.atpTimeOut = 4;
 (**SendRec).atpProto.atpRetries = 2;
 (**SendRec).atpProto.atpNumBufs = 1; /* one BDS element */
 (**SendRec).atpProto.atpNumRsp = 0; /* set to no resp yet*/
 if( (error = ATPSndRequest(SendRec,ASYNC)) != noErr)
 {
 ErrMess = “\pSomething wrong ATPSndRequest.”;
 NumToString( (long)error, tempstr);
 Pappend(ErrMess,tempstr);
 ErrDial();
 return(1);
 }
 Receive(1);
 /* post because of when done it will post a request event and use up 
one GetRequest() */
 return(0); /* no error in this code */
}
DoATNet(ParHan)
ABRecHandle ParHan;/* a handle to record generated by AT */
{
 /*handle all events generated by appletalk
 net work. The possible events call include: */

 Handle itemHand;
 int    type;
 Rect   SelBox;
 if( ParHan == SendRec)
 {
 if( (**ParHan).atpProto.atpNumRsp > 0)
 {
 /* here is where you would process response */
 }
 /* no response, the responder is out ??? */
 }
 else if( ParHan == atpRec)
 {
GetDItem(myWindow,RECM,&type,&itemHand,&SelBox);
SetIText(itemHand,((**ParHan).atpProto.atpDataPtr) );
SendResponce(ParHan);/* send responce to requester*/
 }
 Receive(1);/* set up for another */
 return(0);
}
CloseAT()
{
 /* clean up the frame and glb area */
 int    error;
 Str255 tempstr;

if(  (RecSocket != 0) && (error = ATPCloseSocket(RecSocket) != noErr) 
  )/* for the receive socket */
 {
 ErrMess = “\pSomething wrong ATPCloseSocket.  “;
 NumToString( (long)error, tempstr);
 Pappend(ErrMess,tempstr);
 ErrDial();
 return(1);
 }
 NBPRemove(&myEntity);  /* remove from net */
 if( SendRec != 0)
 {
 ATPReqCancel(SendRec,((Boolean)FALSE));                       /* for 
send socket, async */
 }
 if( atpRec != 0)
 DisposHandle(atpRec);
 if( myABRecord != 0)
 DisposHandle(myABRecord);
 if( Responce != 0)
 DisposHandle(Responce);
 if( SendRec != 0)
 DisposHandle(SendRec);
 return(0); /* good return */
}
ErrDial()
{
 /* dialog box with the error in it the error message is contained in 
a globalthe error dialog stays up until a mouse down  occures */
 Handle itemHand;
 int    type;
 Rect   SelBox;
 DialogPtrerrorWindow;
 WindowPtroldport;

 GetPort(&oldport);
 errorWindow = GetNewDialog(ERRDLOG_ID,NIL,(WindowPtr)-1);
 SetPort(errorWindow);
 GetDItem(errorWindow,ERRMESS,&type,&itemHand,&SelBox);
 SetIText(itemHand,ErrMess);
 FrameRect(&SelBox);
 GetDItem(errorWindow,2,&type,&itemHand,&SelBox);
 ErrMess = “\pClick Mouse to Continue”;
 SetIText(itemHand,ErrMess);
 do {
 } while ( !Button() );   /* click to go away */
 CloseDialog(errorWindow);/* make go away */
 SetPort(oldport);
 return;
}
int
PLength(strPtr)
unsigned char *strPtr;
{
 /*returns the length of the pascal string*/
 return( ((int)strPtr[0] + 1) ); 
 /* the first byte is the length */
}
Pstrcopy(dest,sour)
unsigned  char *dest, *sour;
{
 dest[0] = 0;    /* no string in thre */
 Pappend(dest,sour);
 return(0);
}
Pappend(dest,sour)
unsigned  char *dest, *sour;
{
 /*copy the ‘C’ string at sour to a P string at dest */
 /*NOTE:the dest char array should be declared static to insure you don’t 
bad results */
 int    loop;
 int    dlen,slen;
 slen = (int)sour[0];/* length of string*/ 
 dlen = (int)dest[0];/* not include length byte */
 slen = ( (dlen + slen) <= 255) ? slen : 255 - dlen;
 /* not over the stupid limit */
 dest[0] += slen;/* new length of stuff */
 dest += dlen;   /* get to end of stuff */
 sour++;/* get past the length byte */
 dest++;/* same */ 
 BlockMove(sour,dest,(long)slen);  
 return(0); 
}


/* this is the header file for all the good id on the dialog window */
#include “Appletalk.h”
#define ERRDLOG_ID 129  /* the error dialog */
#define DIALOG_ID128
#define LOOKB    1
#define DISPLAYB 2
#define SENDB    3
#define RECM10 /* received message */
#define MYNODE   11/* chooser name*/
#define CNODEIS  12/* NAME OF CURRENT NODE */
#define CNODENUM 13/* current device selected */
#define NUMBER   14/*no. NODES,SOCKETS on zone */
#define SENDM    15/* EDIT FIELD SEND MESSAGE */
#define MYNUM    17/* THE NODE NUMBER I AM */
#define ERRMESS  1 /* FOR USE IN ERROR DIALOG */
#define NIL 0L /* a nil pointer */
#define PortBUse 0x291
#define SPConfig 0x1FB
#define MYSOCKET 0 /* 0 is on fly allocations */
#define ASYNC    1
#define SYNC0
#define useFree  0
#define useATalk 1
#define useAsync 2
typedef struct CurEntity
 {
 AddrBlockCurAddrs;
 EntityName CurName;
 } CurEntity;

MPW Format Resource File

resource ‘DLOG’ (128) {
 {40, 22, 338, 484},
 noGrowDocProc,
 visible,
 goAway,
 0x0,
 22877,
 “APPLETALK TEST DIALOG”
};

resource ‘DLOG’ (129, “error”) {
 {78, 64, 278, 404},
 dBoxProc,
 visible,
 goAway,
 0x0,
 13469,
 “AppleTalk Error Messages”
};

resource ‘DITL’ (22877) {
 { /* array DITLarray: 17 elements */
 /* [1] */
 {248, 44, 282, 135},
 Button {
 enabled,
 “LOOKUP”
 };
 /* [2] */
 {247, 187, 281, 278},
 Button {
 enabled,
 “DISPLAY”
 };
 /* [3] */
 {247, 326, 281, 417},
 Button {
 enabled,
 “SEND”
 };
 /* [4] */
 {8, 16, 32, 112},
 StaticText {
 disabled,
 “DEVICE NAME:”
 };
 /* [5] */
 {64, 16, 80, 152},
 StaticText {
 disabled,
 “RECIEVED MESSAGE:”
 };
 /* [6] */
 {120, 17, 136, 153},
 StaticText {
 disabled,
 “SEND THIS MESSAGE:”
 };
 /* [7] */
 {160, 224, 176, 368},
 StaticText {
 disabled,
 “# OF DEVICES ON NET:”
 };
 /* [8] */
 {186, 16, 202, 192},
 StaticText {
 disabled,
 “CURRENT DEVICE SELECTED:”
 };
 /* [9] */
 {208, 16, 225, 122},
 StaticText {
 disabled,
 “CURRENT NAME:”
 };
 /* [10] */
 {48, 160, 96, 416},
 StaticText {
 disabled,
 “”
 };
 /* [11] */
 {9, 120, 34, 297},
 StaticText {
 disabled,
 “Mac’s Name”
 };
 /* [12] */
 {208, 128, 232, 392},
 StaticText {
 disabled,
 “”
 };
 /* [13] */
 {186, 197, 202, 293},
 StaticText {
 disabled,
 “”
 };
 /* [14] */
 {160, 376, 176, 416},
 StaticText {
 disabled,
 “”
 };
 /* [15] */
 {104, 160, 152, 416},
 EditText {
 disabled,
 “”
 };
 /* [16] */
 {8, 303, 32, 375},
 StaticText {
 disabled,
 “DEVICE # :”
 };
 /* [17] */
 {8, 379, 32, 458},
 StaticText {
 disabled,
 “”
 }
 }
};

resource ‘DITL’ (13469) {
 { /* array DITLarray:  */
 /* [1] */
 {8, 10, 120, 331},
 StaticText {
 disabled,
 “Error Message.”
 };
 /* [2] */
 {166, 75, 189, 242},
 StaticText {
 disabled,
 “Click Mouse to continue.”
 }
 }
};
 
AAPL
$118.63
Apple Inc.
+2.16
MSFT
$47.59
Microsoft Corpora
-0.39
GOOG
$539.27
Google Inc.
+1.77

MacTech Search:
Community Search:

Software Updates via MacUpdate

Carbon Copy Cloner 4.0.3 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
ForeverSave 2.1.3 - Universal auto-save...
ForeverSave auto-saves all documents you're working on while simultaneously doing backup versioning in the background. Lost data can be quickly restored at any time. Losing data, caused by... Read more
Voila 3.8.1 - Capture, annotate, organiz...
Voila is a screen-capture, recording, and annotation tool that is a full-featured replacement for Mac's screen-capture and screen-recording capabilities. It has a large and robust set of editing,... Read more
SyncTwoFolders 2.0.6 - Syncs two user-sp...
SyncTwoFolders simply synchronizes two folders. It supports synchronization across mounted network drives and it is a possibility to run a simulation showing in a log what will be done. Please visit... Read more
Duplicate Annihilator 5.1.1 - Find and d...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
Cobook 3.0.7 - Intelligent address book....
Cobook Contacts is an intuitive, engaging address book. Solve the problem of contact management with Cobook Contacts and its simple interface and powerful syncing and integration possibilities.... Read more
StatsBar 1.9 - Monitor system processes...
StatsBar gives you a comprehensive and detailed analysis of the following areas of your Mac: CPU usage Memory usage Disk usage Network and bandwidth usage Battery power and health (MacBooks only)... Read more
Cyberduck 4.6 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Evernote 6.0.1 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more

Latest Forum Discussions

See All

This Week at 148Apps: November 17-21, 20...
Expert App Reviewers   So little time and so very many apps. What’s a poor iPhone/iPad lover to do? Fortunately, 148Apps is here to give you the rundown on the latest and greatest releases. And we even have a tremendous back catalog of reviews; just... | Read more »
RuPaul’s Drag Race: Dragopolis 2.0 is C...
RuPaul’s Drag Race: Dragopolis 2.0 is Coming to iOS December 4th Posted by Jessica Fisher on November 24th, 2014 [ permalink ] So Much Drama | Read more »
Crimson Keyboard (Utilities)
Crimson Keyboard 1.0 Device: iOS iPhone Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: Introducing a faster way to type. Crimson Keyboard adapts to your writing style and provides smart, personal and contextual... | Read more »
The Sandbox EDU Review
The Sandbox EDU Review By Nadia Oxford on November 24th, 2014 Our Rating: :: COME PLAY IN THE SANDBOX AGAINUniversal App - Designed for iPhone and iPad Like its predecessor, The Sandbox EDU offers lots for players to see, do, and... | Read more »
Taichi Panda Hits iOS in December
Taichi Panda Hits iOS in December Posted by Jessica Fisher on November 24th, 2014 [ permalink ] Snail Games has released the first official game play trailer for  | Read more »
Five Apps to Make Your Thanksgiving Plan...
Thanksgiving is nearly upon us! You know what that means? Eating too much turkey, watching the Big Game, and spending time with family (whether you like it or not). Oh, and that scary Black Friday thing, but we won’t talk about that here. For those... | Read more »
Kingdom Rush Origins HD Review
Kingdom Rush Origins HD Review By Jennifer Allen on November 24th, 2014 Our Rating: :: JUST AS GOOD AS BEFOREiPad Only App - Designed for the iPad It’s more of the same again, but that’s really no bad thing at all.   | Read more »
Skylanders, Show Off How You Fight Kaos...
Skylanders, Show Off How You Fight Kaos to Win Cool Prizes Posted by Jessica Fisher on November 24th, 2014 [ permalink ] iPad Only App - Designed for the iPad | Read more »
Mark of the Dragon – Tips, Tricks, and S...
Calling All Dragon Riders: | Read more »
Crossy Road Review
Crossy Road Review By Jennifer Allen on November 24th, 2014 Our Rating: :: ADDICTIVELY FROGGER LIKEUniversal App - Designed for iPhone and iPad Frogger-like in nature, Crossy Road is an easy to recommend endless runner of sorts.   | Read more »

Price Scanner via MacPrices.net

Jumptuit Launches One-Tap Windows 8.1 iTunes...
Jumptuit has launched Windows 8.1 support for One-Tap iTunes Sync. with which Windows 8.1 users can now easily sync their iTunes libraries with Microsoft OneDrive. Jumptuit provides easy access from... Read more
CEA Study Finds More People Recycling Electro...
A new study by the Consumer Electronics Association (CEA) finds that electronics recycling receives the continued and growing support of consumers. According to the CEA,s Recycling and Reuse Study,... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale today for $1749. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of Parallels Desktop... Read more
27-inch 3.5GHz 5K iMac in stock today and on...
 B&H Photo has the new 27″ 3.5GHz 5K iMac in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’s the lowest price available... Read more
21-inch 1.4GHz iMac on sale for $979, save $1...
B&H Photo has the new 21″ 1.4GHz iMac on sale for $979.99 including free shipping plus NY sales tax only. Their price is $120 off MSRP. B&H will also include free copies of Parallels Desktop... Read more
13-inch 1.4GHz/256GB MacBook Air on sale for...
B&H Photo has lowered their price on the 13″ 1.4GHz/256GB MacBook Air to $1059.99 including free shipping plus NY sales tax only. Their price is $140 off MSRP, and it’s the lowest price for this... Read more
Save up to $400 with Apple refurbished 2014 1...
The Apple Store has restocked Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and shipping... Read more
New 13-inch 1.4GHz MacBook Air on sale for $8...
 Adorama has the 2014 13″ 1.4GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY & NJ tax only. Their price is $100 off MSRP. B&H Photo has the 13″ 1.4GHz/128GB MacBook... Read more
Apple Expected to Reverse Nine-Month Tablet S...
Apple and Samsung combined accounted for 62 percent of the nearly 36 million branded tablets shipped in 3Q 2014, according to early vendor shipment share estimates from market intelligence firm ABI... Read more
Stratos: 30 Percent of US Smartphone Owners t...
Stratos, Inc., creator of the Bluetooth Connected Card Platform, has announced results from its 2014 Holiday Mobile Payments Survey. The consumer survey found that nearly one out of three (30 percent... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.