Issue 73* June 18 2009

Creating Tcal: Part Three, Sockets
Making the calendar clients talk to the server.

by Roberto Trevisan

This is the third in a series looking at the creation of a full featured Calendar application in Revolution. You can read part one here, and part two here.

As I mentioned before, Tcal is a client/server calendar. The idea is to have several Tcal clients on an intranet or connected via the internet that send and receive data from TcalServer (the server) using TCP/ip. TcalServer would be launched on a computer together with the database which would be either FileMaker Pro or Microsoft Access according to the platform, and would manage the messages between users and database.

I will talk about Tcalserver > Database communication in a later note. In this article I will describe how I managed to do socket communication between the clients and the server, the personal solutions I have chosen and the mechanics of the overall communication of my calendar. I owe a great deal to all the chatting examples I found on the RunRev Forum and in the Revolution examples (so much that I often did not even bother to change command names...)

Client Server Connections

Server and clients must be set with a number of pieces of information before they can connect. Tcalserver needs:

  •  a list of users names, each one with its IP number and the chosen passwords.
     
  • the list of Jobs the company is working on, loaded from the database (trough AppleScript or VBscript).

Tcal needs:

  •  in the preferences of each copy the IP number and port of the computer where TcalServer is located and, of course, the name of the user and their password.

Security is a complex issue so, due to my limited experience, I reduced it to the fact that the Server must have on its preferences each IP, name and password of would be clients.

Another security precaution is that any message sent to the Server has a special character and the name of the client at the beginning of the message. In order to see other people's events and appointments, the logic has been that Tcal can talk only to Tcalserver, but Tcalserver talks to each client in its approved list. This way, on connection, each Tcal calendar receives a complete list of every user's events (there is a user settable limit on past events...you mostly don’t need to load the events of the previous year...)

TcalServer sits waiting for connection. The script that makes it work is

accept connections on port ThePortNumber with message \
       chatConnected

For a good explanation of socket connections, see the “Internet Chat” stack in the Revolution Resource Center.

Both in Tcal and TcalServer I created a substack to contain all the socket scripting, so as to get it more manageable. On the preopenstack I needed of course to do a 

start using stack "ChatLibrary" 

so that I could use the scripts from anywhere.

When a client connects to Tcalserver, he sends name and password. TcalServer cheks if the connection is allowed and, if yes, it sends (in this case only to the specific user) some basic information: a list of users and Jobs previously loaded from the database.

At this point TcalServer waits for the user to do some action. I will describe the creation of a new event (a record to be created on the event database). Before that, let me remark on a few facts concerning the composition of messages:

- each message is a text string split into different items with a arbitrarily choosen itemdelimiter. Since the message may contains any kind of text, I had to choose an itemdelimiter that would always allow Tcal and TcalServer to extract from the message the starting character, the sender name and the real message.

- I may have return chars on the string, so before sending and on receiving, the return char is substituted with a special character. Due to the differences between OSX and Windows, I had to be sure that the chars were the same on both OSes.

- In Italy we have several accented words. If you send an accented char from OSX to Windows, it does not arrive the way you intended because of ASCII differences; so, before sending, both on Tcal and TcalServer I had to run this line:

if the platform is not "MacOs" then put MACToISO(Thedata) into \
       Thedata

- each message ends with a chosen combination of characters. This way the receiving script knows where the end of the message is.

- Finally (I will explain this later on) on the Tcal message there is a unique ID number that gets sent back from Tcalserver and that allows Tcal to cancel the script that handles the no-answer or delayed-answer.

Of course, all of this message composition is taken care of by a command, like this for example:

on chatMessage Thedata
      put endOfMessage after Thedata --I’ve set this var before \
          with “|##”, as a ending of message mark
      send "MysocketTimeOut" to me in 10 seconds --explained \
          later
      put the result into TheMessageID --explained later
      put "*|" & TheMessageID & "|" & MyName & "|" before Thedata
      if the platform is not "MacOs" then put IsoToMac(Thedata) \
          into Thedata
      put false into TcalServerAnswered --explained later
      write  Thedata to socket TheChatSocket --the \
          TheChatSocket variable
   -- contains the host and port of the server, received on
   -- connection
end chatMessage  

TheData has been previously cleaned up with

replace return with "^" in Thedata
 replace quote with "§" in Thedata --this because we do not \
       want to mess with quotes
--in the Server/database Applescript or VBscript talking 

When the user creates an event on its calendar, a rect fields gets created holding the event info in its custom properties and the event data are sent to Tcalserver. Tcalserver polls the “createRecord” script (an Applescript or VBscript saved on the stack's custom properties), replaces what’s needed in the script (event data, name of the database, password of the database, etc) and does a 

Do ThisScript as Language --Language is Applescript or VBScript 

The alternate language script, in this example, upon creation of the record in the database, gets back from it the unique ID number of the record,  replace the FakeID on the original received message from Tcal with the unique ID and send the message to all the users with something like this (taken from the Chat example):

put keys(lChatterArray) into tChatterList
   -- cycle through all of the currently connected clients
   -- placing the host and port for each one into the variable
-- "tSocket"
   put "*|" & TheMessageID & "|" & sender & "|" & TheMessage \
       into invio
   if the platform is not "MacOs" then put ISOToMAC(invio) into \
       invio
   repeat for each line tSocket in tChatterList
               write invio to socket tSocket -- send the data \
          contained in the message variable to each client
   end repeat 

The Tcal calendar that created the event receives the message and replaces the fakeID with the uniqueID on the event data. All the other connected calendars save internally the new event data (or show the event if in that particular moment they are inside the sender calendar). Note that in this way the user does not have to wait for the TcalServer answer, in order to draw the new event on the screen: it would have been a really poor interface with such delay...

But Intranet or internet connections don’t happen instantly: the Server may be busy doing something else or it may have been shut by the IT manager of the company (they do this kind of thing). So, how long has Tcal to wait for an answer by Tcalserver ?

Probably my way of doing socket communication is not the best and it may be flawed by some unforeseen mistake. But, before discovering this solution, I tried several times to use the socketTimeout command available in RunRev and I never was able to make it work. So let me explain the meaning of the send "MysocketTimeOut" to me in 10 seconds and TcalServerAnswered flag of the ChatMessage script above. Reading from the RunRev dictionary about the send command:

If you specify a time, the message is sent to the object after the specified time has elapsed. The message is added to the pendingMessages function. The ID of the message is returned by the result function. To cancel the message, use the cancel command with this ID.

In my script I tell Revolution to wait 10 seconds before calling off the back answer from TcalServer. The TcalServerAnswered variable is set to false before sending and, upon receiving the message back from Tcalserver,  if the message is correct, to true. Tcal also cancels the pending message using TheMessageID inside the message returned.

So my MysocketTimeOut looks like this:

on MysocketTimeout theID
      if lChatSocket is not empty  and TcalServerAnswered = false \
          then
        put true into TcalServerAnswered
               answer "Can't connect. TcalServer is off..." with "OK"
               send "MouseUp" to fld "Disconnect" of card 1 of stack \
             "Tcal"
               exit to top
      end if
end MysocketTimeout 

On top of this, I had to take into account the fact that Tcal, while waiting for an answer from Tcalserver, might receive other messages, concerning the creation of events by different users. My solution was to give priority to the return messages owned by the user, while saving in an array the pending messages from other users; messages to be done once the owned message was accomplished. This is the script that handles the receiving messages on Tcal:

on chatReceived s,data
      -- this command comes from the connect script: read from
   -- socket s until endOfMessage with message "chatReceived"
      set itemdelimiter to "|"
      if item 1 of data is "*" then -- correct message, the \
          flag character
            put item 2 of data into TheMessageID --this is the \
             ID of the MySocketTimeOut
            if the platform is not "MacOs" then put \
             MacToIso(data) into data --clean up the data
            put item 3 of data into TheSender --could be me or \
             not
            if TheSender <> ThisIsMe then --is not my answer
                  if TcalServerAnswered is false then --I was \
                waiting for an answer by Tcalserver
                        --I store the message for later use
                        if Chiave is empty or Chiave is not a \
                   number then --chiave is a local variable
                              put 0 into Chiave
                        end if
                        add 1 to Chiave
                        put s && data into \
                   ThePendingData[Chiave] --ThePendingData is a
            -- global var that holds the pending messages
            -- received
                  else --I was not waiting for a message so I \
                can take care of this and that's it
                        chatRicevuti s,data --this is the \
                   command that actually takes care of the
            --message inside the calendar
                  end if
            else --The message is mine (the answer back from \
             TcalServer)
                  if TcalServerAnswered is false then 
                        put true into TcalServerAnswered --set \
                   the flag
                        cancel TheMessageID --no more pending \
                   messaage
                        chatRicevuti s,data --take care of my \
                   data
                        if the keys of ThePendingData is not \
                   empty then --there are messages
               -- from other user waiting to be taken care of
                              repeat for each line LaChiave in \
                      the keys of ThePendingData
                                    put word 1 of \
                         ThePendingData[LaChiave] into S
                                    delete word 1 of \
                         ThePendingData[LaChiave]
                                    chatRicevuti \
                         s,ThePendingData[LaChiave] --do the message
                              end repeat
                              delete global ThePendingData
                              put 0 into Chiave
                        end if
                  else
                        cancel TheMessageID
                        chatRicevuti s,data --do the message
                  end if
            end if
      end if
      read from socket s  until endOfMessage with message \
          chatReceived
end chatReceived 

Looking at it, nowadays I wonder how I managed to create it! Revolution scripting is very easy but the logic of communication is not (see human beings...). Mostly, I believe, because you need to take timing into account.

In the next article I will describe the AppleScript and VbScript that allowed me to modify FileMaker Pro and Microsoft Access database which is much easier than everything above.

About the Author

Roberto Trevisan is an architect working as an Exposition and MultiMedia event Designer in Torino, Italy. He has managed projects for architectural firms working with Fiat, Alfa Romeo, Zegna, Philip Morris, Tim Telephone, Sai and several chainstores. Visit the Tcal website here.

 

 

Main Menu

What's New

RunRevLive Edinburgh