/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* calendar-pilot-sync.c:
*
* (C) 1999 International GNOME Support
*
* Author:
* Miguel de Icaza (miguel@gnome-support.com)
*
*/
/*
*
* this only works in a monogamous pilot/desktop situation.
*
*/
#include <config.h>
#include <gnome.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bonobo.h>
#include <bonobo/bonobo-control.h>
#ifdef USING_OAF
#include <liboaf/liboaf.h>
#else
#include <libgnorba/gnorba.h>
#endif
#include <cal-client/cal-client.h>
#include "cal-util/calobj.h"
#include "cal-util/timeutil.h"
#include "pi-source.h"
#include "pi-socket.h"
#include "pi-datebook.h"
#include "pi-dlp.h"
char *calendar_file;
/* The default port to communicate with */
char *pilot_port = "/dev/pilot";
/* Our pi-socket address where we connect to */
struct pi_sockaddr addr;
/* The Pilot DB identifier for DateBook */
int db;
/* If true, enable debug output for alarms */
int debug_alarms = 0;
/* True if you want to dump the flags bits from the records */
int debug_attrs = 0;
int only_desktop_to_pilot = 0;
int only_pilot_to_desktop = 0;
const struct poptOption calendar_sync_options [] = {
{ "pilot", 0, POPT_ARG_STRING, &pilot_port, 0,
N_("Specifies the port on which the Pilot is"), N_("PORT") },
{ "debug-attrs", 0, POPT_ARG_NONE, &debug_attrs, 0,
N_("If you want to debug the attributes on records"), NULL },
{ "only-desktop", 0, POPT_ARG_NONE, &only_desktop_to_pilot, 0,
N_("Only syncs from desktop to pilot"), NULL },
{ "only-pilot", 0, POPT_ARG_INT, &only_pilot_to_desktop, 0,
N_("Only syncs from pilot to desktop"), NULL },
{ NULL, '\0', 0, NULL, 0 }
};
static void
conduit_free_Appointment (struct Appointment *a)
{
/* free_Appointment is brain-dead with respect to guarding against
double-frees */
free_Appointment (a);
a->exception = 0;
a->description = 0;
a->note = 0;
}
static int
setup_connection (void)
{
int socket;
int ret, news;
if (!(socket = pi_socket(PI_AF_SLP, PI_SOCK_STREAM, PI_PF_PADP)))
g_error (_("Can not create Pilot socket\n"));
addr.pi_family = PI_AF_SLP;
strncpy ((void *)&addr.pi_device, pilot_port, sizeof (addr.pi_device));
ret = pi_bind (socket, (struct sockaddr *)&addr, sizeof (addr));
if (ret == -1)
g_error (_("Can not bind to device %s\n"), pilot_port);
if (pi_listen (socket, 1) == -1)
g_error (_("Failed to get a connection "
"from the Pilot device"));
if ((news = pi_accept (socket, 0, 0)) == -1)
g_error (_("pi_accept failed"));
return news;
}
static void
init_bonobo (int *argc, char **argv)
{
# ifdef USING_OAF
/* FIXME: VERSION instead of "0.0". */
gnome_init_with_popt_table ("evolution-calendar", "0.0",
*argc, argv, oaf_popt_options,
0, NULL);
oaf_init (*argc, argv);
# else
CORBA_Environment ev;
CORBA_exception_init (&ev);
gnome_CORBA_init_with_popt_table (
"evolution-calendar", "0.0",
argc, argv, NULL, 0, NULL, GNORBA_INIT_SERVER_FUNC, &ev);
CORBA_exception_free (&ev);
# endif
if (bonobo_init (CORBA_OBJECT_NIL,
CORBA_OBJECT_NIL, CORBA_OBJECT_NIL) == FALSE)
g_error (_("Could not initialize Bonobo"));
}
static void
delete_record_from_desktop (CalClient *client, int id)
{
char *uid;
CalClientGetStatus status;
status = cal_client_get_uid_by_pilot_id (client, id, &uid);
if (status == CAL_CLIENT_GET_SUCCESS) {
cal_client_remove_object (client, uid);
g_free (uid);
}
}
static void
dump_attr (int flags)
{
if (flags & dlpRecAttrDeleted)
printf (" Deleted");
if (flags & dlpRecAttrDirty)
printf (" Dirty");
if (flags & dlpRecAttrBusy)
printf (" Busy");
if (flags & dlpRecAttrSecret)
printf (" Secret");
if (flags & dlpRecAttrArchived)
printf (" Archive");
printf ("\n");
}
/* take a record retrieved from a pilot and merge it into the desktop cal */
static void
update_record (CalClient *client, int id, struct Appointment *a, int attr)
{
iCalObject *obj;
int i;
CalClientGetStatus status;
char *uid = NULL;
gboolean success;
printf ("pilot->cal: %d, ", id);
status = cal_client_get_uid_by_pilot_id (client, id, &uid);
if (status == CAL_CLIENT_GET_SUCCESS)
status = cal_client_get_object (client, uid, &obj);
if (status != CAL_CLIENT_GET_SUCCESS) {
/* Object did not exist, creating a new one */
time_t now = time (NULL);
obj = ical_new (a->note ? a->note : "",
g_get_user_name (),
a->description ? a->description : "");
obj->created = now;
obj->last_mod = now;
obj->priority = 0;
obj->transp = 0;
obj->related = NULL;
obj->pilot_id = id;
obj->pilot_status = ICAL_PILOT_SYNC_NONE;
}
if (obj->pilot_status == ICAL_PILOT_SYNC_MOD) {
printf (_("\tObject has been modified on desktop and on "
"the pilot, desktop takes precedence\n"));
ical_object_unref (obj);
return;
}
/*
* Begin and end
*/
if (a->event)
{
/* turn day-long events into a full day's appointment */
a->begin.tm_sec = 0;
a->begin.tm_min = 0;
a->begin.tm_hour = 6;
a->end.tm_sec = 0;
a->end.tm_min = 0;
a->end.tm_hour = 10;
}
if (a->note)
obj->comment = g_strdup (a->note);
if (a->description)
obj->summary = g_strdup (a->description);
obj->dtstart = mktime (&a->begin);
obj->dtend = mktime (&a->end);
/* Special case: daily repetitions are converted to a multiday event */
if (a->repeatType == repeatDaily){
time_t newt = time_add_day (obj->dtend, a->repeatFrequency);
obj->dtend = newt;
}
/*
* Alarm
*/
if (a->alarm){
obj->aalarm.type = ALARM_AUDIO;
obj->aalarm.enabled = 1;
obj->aalarm.count = a->advance;
switch (a->advanceUnits){
case advMinutes:
obj->aalarm.units = ALARM_MINUTES;
break;
case advHours:
obj->aalarm.units = ALARM_HOURS;
break;
case advDays:
obj->aalarm.units = ALARM_DAYS;
break;
default:
}
}
/*
* Recurrence
*/
if (a->repeatFrequency && a->repeatType != repeatDaily){
obj->recur = g_new0 (Recurrence, 1);
switch (a->repeatType){
case repeatDaily:
/*
* In the Pilot daily repetitions are actually
* multi-day events
*/
g_warning ("Should not have got here");
break;
case repeatMonthlyByDate:
obj->recur->type = RECUR_MONTHLY_BY_DAY;
obj->recur->u.month_day = a->repeatFrequency;
break;
case repeatWeekly:
{
int wd;
obj->recur->type = RECUR_WEEKLY;
for (wd = 0; wd < 7; wd++)
if (a->repeatDays [wd])
obj->recur->weekday |= 1 << wd;
if (obj->recur->weekday == 0){
struct tm tm = *localtime (&obj->dtstart);
obj->recur->weekday = 1 << tm.tm_wday;
}
break;
}
case repeatMonthlyByDay:
obj->recur->type = RECUR_MONTHLY_BY_POS;
obj->recur->u.month_pos = a->repeatFrequency;
obj->recur->weekday = (a->repeatDay / 7);
break;
case repeatYearly:
obj->recur->type = RECUR_YEARLY_BY_DAY;
break;
default:
g_warning ("Unhandled repeate case");
}
if (a->repeatForever)
obj->recur->duration = 0;
else
obj->recur->_enddate = mktime (&a->repeatEnd);
}
/*
* Load exception dates
*/
obj->exdate = NULL;
for (i = 0; i < a->exceptions; i++){
time_t *t = g_new (time_t, 1);
*t = mktime (&(a->exception [i]));
obj->exdate = g_list_prepend (obj->exdate, t);
}
g_free (obj->class);
if (attr & dlpRecAttrSecret)
obj->class = g_strdup ("PRIVATE");
else
obj->class = g_strdup ("PUBLIC");
/*
* Now, convert the in memory iCalObject to a full vCalendar
* we can send
*/
success = cal_client_update_object (client, obj);
/* set the pilot_status to sync_none so we don't send
this event right back to the pilot */
cal_client_update_pilot_id (client, obj->uid, obj->pilot_id,
ICAL_PILOT_SYNC_NONE);
dump_attr (attr);
printf (" but not used.\n");
/*
* Shutdown
*/
ical_object_unref (obj);
}
/*
* Sets the alarm for Appointment based on @alarm
*/
static int
try_alarm (CalendarAlarm *alarm, struct Appointment *a)
{
if (!alarm->enabled)
return 0;
a->advance = alarm->count;
switch (alarm->type){
case ALARM_DAYS:
a->advanceUnits = advDays;
break;
case ALARM_HOURS:
a->advanceUnits = advHours;
break;
case ALARM_MINUTES:
a->advanceUnits = advMinutes;
break;
default:
return 0;
}
a->alarm = 1;
return 1;
}
static void
sync_object_to_pilot (CalClient *client, iCalObject *obj, int pilot_fd)
{
char buffer [65536];
struct Appointment *a;
int wd, i, idx, attr, cat, rec_len;
recordid_t new_id;
GList *l;
a = g_new0 (struct Appointment, 1);
attr = 0;
cat = 0;
idx = 0;
printf ("cal->pilot: pilotid=%d, ", obj->pilot_id);
if (obj->pilot_id) {
rec_len = dlp_ReadRecordById (pilot_fd, db, obj->pilot_id,
buffer,
&idx, &rec_len, &attr, &cat);
if (rec_len > 0)
unpack_Appointment (a, buffer, rec_len);
} else {
attr = 0;
cat = 0;
}
/* a contains the appointment either cleared or with
the data from the Pilot */
a->begin = *localtime (&obj->dtstart);
a->end = *localtime (&obj->dtend);
/* FIXME: add support for timeless */
a->event = 0;
/* Alarms, try the various ones. Probably we should only do Audio?
* Otherwise going gnomecal->pilot->gnomecal would get the gnomecal
* with *possibly* an alarm that was not originally defined.
*/
a->alarm = 0;
if (try_alarm (&obj->aalarm, a) == 0)
if (try_alarm (&obj->dalarm, a) == 0)
try_alarm (&obj->palarm, a);
/* Recurrence */
if (obj->recur){
a->repeatFrequency = obj->recur->interval;
switch (obj->recur->type){
case RECUR_MONTHLY_BY_POS:
a->repeatType = repeatMonthlyByDay;
a->repeatFrequency = obj->recur->u.month_pos;
a->repeatDay = obj->recur->weekday * 7;
break;
case RECUR_MONTHLY_BY_DAY:
a->repeatType = repeatMonthlyByDate;
a->repeatFrequency = obj->recur->u.month_day;
break;
case RECUR_YEARLY_BY_DAY:
a->repeatType = repeatYearly;
break;
case RECUR_WEEKLY:
for (wd = 0; wd < 7; wd++)
if (obj->recur->weekday & (1 << wd))
a->repeatDays [wd] = 1;
a->repeatType = repeatWeekly;
break;
case RECUR_DAILY:
default:
a->repeatType = repeatNone;
break;
}
if (obj->recur->enddate == 0){
a->repeatForever = 1;
} else
a->repeatEnd = *localtime (&obj->recur->enddate);
}
/*
* Pilot uses a repeat-daily for a multi-day event, adjust
* for that case
*/
if ((a->end.tm_mday != a->begin.tm_mday) ||
(a->end.tm_mon != a->begin.tm_mon) ||
(a->end.tm_year != a->begin.tm_year)) {
a->event = 1;
a->begin.tm_sec = 0;
a->begin.tm_min = 0;
a->begin.tm_hour = 0;
a->end.tm_sec = 0;
a->end.tm_min = 0;
a->end.tm_hour = 0;
a->repeatEnd = a->end;
a->repeatForever = 0;
a->repeatFrequency = 1;
a->repeatType = repeatDaily;
}
/*
* Exceptions
*/
a->exceptions = g_list_length (obj->exdate);
a->exception = (struct tm *)malloc (sizeof(struct tm) * a->exceptions);
for (i = 0, l = obj->exdate; l; l = l->next, i++){
time_t *exdate = l->data;
a->exception [i] = *localtime (exdate);
}
/*
* Description and note.
*
* We use strdup to be correct. free_Appointment assumes we used
* malloc.
*/
if (obj->comment)
a->note = strdup (obj->comment);
else
a->note = 0;
if (obj->summary)
a->description = strdup (obj->summary);
else
a->description = strdup (_("No description"));
if (strcmp (obj->class, "PUBLIC") != 0)
attr |= dlpRecAttrSecret;
else
attr &= ~dlpRecAttrSecret;
/*
* Send the appointment to the pilot
*/
rec_len = pack_Appointment (a, buffer, sizeof (buffer));
attr &= ~dlpRecAttrDirty;
dump_attr (attr);
printf ("\n");
dlp_WriteRecord (pilot_fd, db, attr,
obj->pilot_id, 0, buffer, rec_len, &new_id);
cal_client_update_pilot_id (client, obj->uid, new_id,
ICAL_PILOT_SYNC_NONE);
conduit_free_Appointment (a);
g_free (a);
}
static void
sync_cal_to_pilot (CalClient *client, int pilot_fd)
{
int c;
int i;
GList *uids;
GList *cur;
uids = cal_client_get_uids (client, CALOBJ_TYPE_ANY);
c = g_list_length (uids);
for (cur=uids, i=0; cur; cur=cur->next, i++) {
const char *uid = cur->data;
CalClientGetStatus status;
iCalObject *ico;
status = cal_client_get_object (client, uid, &ico);
if (status == CAL_CLIENT_GET_SUCCESS &&
ico->pilot_status == ICAL_PILOT_SYNC_MOD) {
printf ("uid='%s', pilot_status=%d\n", uid,
ico->pilot_status);
sync_object_to_pilot (client, ico, pilot_fd);
}
/*
else {
warn
}
*/
}
}
static void
sync_pilot_to_cal (CalClient *client, int pilot_fd)
{
int record;
unsigned char buffer [65536];
for (record = 0;; record++) {
struct Appointment a;
int rec_len, attr, size;
recordid_t id;
rec_len = dlp_ReadRecordByIndex (pilot_fd, db,
record, buffer,
&id, &size, &attr, 0);
if (rec_len < 0)
break;
unpack_Appointment (&a, buffer, rec_len);
if (debug_attrs)
dump_attr (attr);
/* If the object was deleted, remove it from
the desktop database */
if (attr & dlpRecAttrDeleted) {
delete_record_from_desktop (client, id);
conduit_free_Appointment (&a);
dlp_DeleteRecord (pilot_fd, db, 0, id);
continue;
}
if (attr & dlpRecAttrArchived)
continue;
if (attr & dlpRecAttrDirty) {
update_record (client, id, &a, attr);
} else {
/* if the dirty flag is clear yet we have
no copy of it in the desktop database, then
we deleted it from the desktop database, so
delete it from the pilot */
char *uid;
CalClientGetStatus status;
status = cal_client_get_uid_by_pilot_id (client,
id, &uid);
if (status == CAL_CLIENT_GET_NOT_FOUND) {
printf ("deleting %ld from pilot\n", id);
dlp_DeleteRecord (pilot_fd, db, 0, id);
}
else
g_free (uid);
}
attr &= ~dlpRecAttrDirty;
conduit_free_Appointment (&a);
}
}
static void
sync_pilot (CalClient *client, int pilot_fd)
{
struct PilotUser user_info;
struct SysInfo sys_info;
unsigned char buffer [300];
/* Get the pilot's system information. FIX ME check return */
dlp_ReadSysInfo (pilot_fd, &sys_info);
/* Ask the pilot who it is. FIX ME check return */
dlp_ReadUserInfo (pilot_fd, &user_info);
printf ("---------sys info--------------\n");
printf ("romVersion=%ld\n", sys_info.romVersion);
printf ("locale=%ld\n", sys_info.locale);
strncpy (buffer, sys_info.name, sys_info.nameLength);
printf ("name='%s'\n", buffer);
printf ("---------user info--------------\n");
printf ("userID=%ld\n", user_info.userID);
printf ("viewerID=%ld\n", user_info.viewerID);
printf ("lastSyncPC=%ld\n", user_info.lastSyncPC);
printf ("successfulSyncDate=%s",
ctime (& user_info.successfulSyncDate));
printf ("lastSyncDate=%s",
ctime (& user_info.lastSyncDate));
printf ("username='%s'\n", user_info.username);
strncpy (buffer, user_info.password, user_info.passwordLength);
printf ("password='%s'\n", buffer);
printf ("--------------------------------\n");
/* This informs the user of the progress on the Pilot */
dlp_OpenConduit (pilot_fd);
if (dlp_OpenDB (pilot_fd, 0, 0x80 | 0x40, "DatebookDB", &db) < 0){
g_warning (_("Could not open DatebookDB on the Pilot"));
dlp_AddSyncLogEntry (pilot_fd, _("Unable to open DatebookDB"));
pi_close (pilot_fd);
exit (1);
}
/*
* 1. Pull all the records from the Pilot, and make any updates
* required on the desktop side
*/
if (! only_desktop_to_pilot)
sync_pilot_to_cal (client, pilot_fd);
/*
* 2. Push all changes on the desktop to the pilot.
*
*/
if (! only_pilot_to_desktop)
sync_cal_to_pilot (client, pilot_fd);
/*
* 3. Clear the dirty bits on all the pilot's events.
*
*/
dlp_ResetSyncFlags (pilot_fd, db);
/*
* 4. Close down.
*
*/
dlp_CloseDB (pilot_fd, db);
dlp_AddSyncLogEntry (pilot_fd,
_("Synced DateBook from Pilot to GnomeCal"));
pi_close (pilot_fd);
/*
* 5. Dump Core.
*
*/
}
static void
gnome_calendar_load_cb (GtkWidget *cal_client,
CalClientLoadStatus status,
int *link)
{
CalClient *client = CAL_CLIENT (cal_client);
static int tried = 0;
if (status == CAL_CLIENT_LOAD_SUCCESS) {
printf ("<Syncing>\n");
sync_pilot (client, *link);
printf ("</Syncing>\n");
}
else {
if (tried) {
printf ("load and create of calendar failed\n");
return;
}
cal_client_create_calendar (client, calendar_file);
tried = 1;
}
gtk_main_quit ();
}
int
main (int argc, char *argv [])
{
int link;
CalClient *client;
init_bonobo (&argc, argv);
g_log_set_always_fatal (G_LOG_LEVEL_ERROR |
G_LOG_LEVEL_CRITICAL |
G_LOG_LEVEL_WARNING);
/* FIX ME */
calendar_file = g_concat_dir_and_file (g_get_home_dir (),
"evolution/local/Calendar/calendar.vcf");
for (;;) {
printf ("Please, press HotSync button on the palm...\n");
fflush (stdout);
link = setup_connection ();
printf ("Connected\n");
printf ("Contacting calendar server...\n");
fflush (stdout);
client = cal_client_new ();
gtk_signal_connect (GTK_OBJECT (client), "cal_loaded",
gnome_calendar_load_cb, &link);
cal_client_load_calendar (client, calendar_file);
bonobo_main ();
gtk_object_unref (GTK_OBJECT (client));
pi_close (link);
}
return 0;
}
/* Just a stub to link with */
void calendar_notify (time_t time, CalendarAlarm *which, void *data);
void
calendar_notify (time_t time, CalendarAlarm *which, void *data)
{
}