Implementing Alicq module: Step by Step
Create simple extension module for Alicq, which monitors changes of
contact status and save them to file.
Content
Alicq module is just a Tcl script, which is loaded by Alicq, and can
access to data of other modules, hook Alicq events etc.
First, make directory modules in your alicq base
directory (it is ~/.alicq in Unix, and %USERPROFILE%/alicq in Windows). Alicq will look for user-specific modules there.
In modules directory create file
statusmonitor.tcl. Content of this file can be any
valid Tcl script. Let make it just say that module is loaded:
Event Log 0 "My first module us loaded"
That's it, simpliest module is ready and can be included into
~/.alicq/alicqrc fiile:
module statusmonitor
Run alicq and look at alicq.log file in alicq base directory. It
should contain record like:
[25.03.2004 10:45:29]: My first module is loaded
Ok, simpliest module is working, not it is time to make it more
usefll.
Our purpose it to monitor changes of contacts. When contact changes
it's status, icq module updates corresponding Status
field of object's variable.
First, we should find the objects we have to monitor. Any module can
create objects of different purposes, but we need only ICQ contacts.
They can be selected using select helper command.
# Find all contacts and invoke MonitorContact for each of
# them.
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
}
# Fake monitor installer - just log it was invoked.
proc MonitorContact {contact} {
Event Log 0 "Monitoring contact $contact"
}
InitMonitoring
Event Log 0 "My first module is loaded"
This module will work, but there is a problem. If contacts are
loaded after this module, no contacts will be selected in
InitMonitor procedre. There is a way to load the
module after contacts, but this is not best solution.
Normally, module should not depend on order of loading. There is
Alicq event ConfigLoaded, which is sent after startup
file was read and processed. At this moment all modules and contacts
are loaded. Properly changed example is:
# Find all contacts and invoke MonitorContact for each of
# them.
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
}
# Fake monitor installer - just log it was invoked.
proc MonitorContact {contact} {
Event Log 0 "Monitoring contact $contact"
}
hook ConfigLoaded [namespace current]::InitMonitoring
Event Log 0 "My first module is loaded"
Now, MonitorContact will be invoked for all existing
contacts. Seems everything is fine. But wait - what if contact is
being added during work? It had not existed when InitMonitoring was
invoked, and no monitoring is installed for it. Module should
monitor creation of new contacts:
# Find all contacts and invoke MonitorContact for each of
# them.
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
# Monitor new objects
hook New:Contact:ICQ:* [namespace current]::MonitorContact
}
# Fake monitor installer - just log it was invoked.
proc MonitorContact {contact} {
Event Log 0 "Monitoring contact $contact"
}
hook ConfigLoaded [namespace current]::InitMonitoring
Event Log 0 "My first module is loaded"
Ok, now addition of contacts is monitored, but what about deletion of
ones? It can be done, but there is no need in this for our purpose:
status of deleted contact never changes and need not to be
monitored.
Let's replace MonitorContact procedure stub for real
monitoring example. This procedure is invoked with one agrument: unique
object identifier (uid). This identifier can be
converted into corresponding variable name using
ref command. Having variable name, Tcl standard
command trace can be used for monitoring.
# Find all contacts and invoke MonitorContact for each of
# them.
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
hook New:Contact:ICQ:* [namespace current]::MonitorContact
}
proc MonitorContact {contact} {
set ref [ref $contact]
trace variable ${ref}(Status) w [nc StatusChanged $contact]
Event Log 0 "Monitoring contact $contact"
}
proc StatusChanged {contact ref field args} {
upvar 1 ${ref}($field) status
Event Log 0 "Contact $contact changed status to $status"
}
hook ConfigLoaded [namespace current]::InitMonitoring
Event Log 0 "My first module is loaded"
This example monitors contacts status and logs it in Alicq log file.
It can be not very conveniet, because log file can contain a lot of
other information. It is better to save log into separate file.
# Find all contacts and invoke MonitorContact for each of
# them.
variable logname status.log
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
hook New:Contact:ICQ:* [namespace current]::MonitorContact
}
proc MonitorContact {contact} {
set ref [ref $contact]
trace variable ${ref}(Status) w [nc StatusChanged $contact]
Event Log 0 "Monitoring contact $contact"
}
proc StatusChanged {contact ref field args} {
upvar 1 ${ref}($field) status
variable logname
if {[catch {
set fd [open $logname a+]
puts $fd "$contact $status"
close $fd
} reason]} { Log 0 "Can not log status of $contact: $reason"}
}
hook ConfigLoaded [namespace current]::InitMonitoring
Event Log 0 "My first module is loaded"
The monitoring module now have no configurable parameters. And
possibly module of such purpose does not need them. However, let's
add some of them just to demonstrate how modules metadata is used in
Alicq.
Each module and module child namespaces can have metadata,
containing module description, name of author, etc., as well as
configurable parameters metadata.
What configuration parameters can be usefull? There are thos of
them: name of log file and monitoring active/passive flag.
User should be able turn on or off monitoring from main menu and
configuration dialog, and change file name from configuration
dialog.
namespace eval meta {
set description "Log changes of contact status into file"
set name "Status logger"
array set active {
type boolean
default no
menu {Tools "Monitor Status"}
description "Activate status logging"
}
array set logname {
type file
default status.log
description "Log file name"
}
}
# Find all contacts and invoke MonitorContact for each of
# them.
proc InitMonitoring {} {
foreach contact [select Contact:ICQ] {
MonitorContact $contact
}
hook New:Contact:ICQ:* [namespace current]::MonitorContact
}
proc MonitorContact {contact} {
set ref [ref $contact]
trace variable ${ref}(Status) w [nc StatusChanged $contact]
Event Log 0 "Monitoring contact $contact"
}
proc StatusChanged {contact ref field args} {
variable logname
variable active
# If monitoring is inactive, return without saving
if {![string is true $active]} return
upvar 1 ${ref}($field) status
if {[catch {
set fd [open $logname a+]
puts $fd "$contact $status"
close $fd
} reason]} { Log 0 "Can not log status of $contact: $reason"}
}
hook ConfigLoaded [namespace current]::InitMonitoring
Event Log 0 "My first module is loaded"
After Alicq is loaded, new item, Tools -> Monitor Status appear in
main menu, as well as page "Status logger" in configuration dialog.
Now module can log status changes, and can be configured. What
enhancements can be usefull? If contact list is big, it is better to
monitor only some contacts, not all of them. For example, only users
of group monitor should be monitored.
proc StatusChanged {contact ref field args} {
variable logname
variable active
# If monitoring is inactive, return without saving
if {![string is true $active]} return
# Check, if contact is member of monitor group
if {![info exists ${ref}(Groups)] ||
[lsearch [set ${ref}(Groups)] monitor]==-1} return
upvar 1 ${ref}($field) status
if {[catch {
set fd [open $logname a+]
puts $fd "$contact $status"
close $fd
} reason]} { Log 0 "Can not log status of $contact: $reason"}
}
Now you can create group monitor, if it does not exist,
and copy contacts you want to monitor there.