|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/compasssysweb/calendar/CalciumDir39/Calendar/ |
Upload File : |
# Copyright 1999-2003, Fred Steinberg, Brown Bear Software
# Database.pm - non-implementation specific database stuff.
#
# Each Database object contains a DBImplementation object to handle the
# implementation specific things. (current implementations include:
# DBM, DB_Serialize)
# Warning: if you are looking at this, take it as an example of what not to
# do. This DBImp/Database stuff will all be redone, as it was poorly
# designed, and we'll be adding support for DBI::* anyway. Lots of
# finagling was patched on to avoid leaking memory (which matters for
# mod_perl/Apache::Registry type systems.)
# new
# name, description
# openDatabase, closeDatabase
# createDatabase, deleteDatabase, renameDatabase
# version
# description
# _getBaseFilename
# _getFilename
# setPreferencesToDefault
# setPreferences, getPreferences
# insertEvent
# _insertRegularEvent, _insertRepeatingEvent
# getEvent, getEvents
# deleteEvent
# deleteEventsInRange
# getApplicableEvents
# getEventDateHash
# getEventLists
# setPermission, getPermission
# getPermittedUsers, setPermittedUsers
# removeFromIncludeLists
# findEventsMatching
# findRepeatingEventsMatching
# getAllRegularEvents, getAllRepeatingEvents
# renameCategory
package Database;
use strict;
use Calendar::Date;
use Calendar::Defines;
use Calendar::Permissions;
use Calendar::Preferences;
use Calendar::AddIn;
use Fcntl;
my %imps = (Serialize => 'DB_Serialize',
DBM => 'DB_DBM');
# Pass in the name of the database, and an optional Implementation to use.
# If no implementation specified, we get it from the Defines.
{
# Cache Database objects - if we've already got one w/same name & imp,
# just return it.
my %objCache;
sub new {
my $class = shift;
my ($dbName, $imp) = @_;
$imp ||= Defines->databaseType;
die "Don't know how to do a $imp database!" unless $imps{$imp};
return $objCache{$dbName.$imp} if (defined $objCache{$dbName.$imp});
eval "require Calendar::$imps{$imp}";
die "Couldn't find Calendar::$imps{$imp}" if $@;
my $okName;
die "Bad Calendar or Database name! '$dbName' \n"
unless ($dbName =~ /^(\w+)$/ and $okName = $1); # untaint and check
my $self = {};
bless $self, $class;
$self->{'name'} = $okName;
$self->{'Imp'} = $imps{$imp}->new ($self);
my $selfRef = \$self;
bless $selfRef, $class; # so we can avoid memory leak
$objCache{$dbName.$imp} = $selfRef;
$selfRef;
}
sub END {
foreach my $ook (values %objCache) {
$ook->closeDatabase (1);
}
}
}
sub DESTROY {
my $self = shift;
return unless ($self->isa ('REF')); # oh dear
delete $$self->{Imp}; # all to patch leak from circular refs...oy
}
sub name {
my $self = shift;
$self = $$self if ($self->isa ('REF')); # oh dear (called from Imps)
return $self->{'name'};
}
# Optionally pass version string to set it
sub version {
my $self = shift;
my $version = shift;
$self = $$self if ($self->isa ('REF')); # oh my
$self->{'Imp'}->setVersion ($version) if $version;
$self->{'Imp'}->getVersion ();
}
sub description {
my $self = shift;
my $description = shift;
if (defined $description) {
$self->setPreferences ({Description => $description});
} else {
$description = $self->getPreferences ('Description') || '';
}
return $description;
}
# Pass readwrite flag
sub openDatabase {
my $self = shift;
$self = $$self if ($self->isa ('REF')); # oh dear
my $readWrite = $_[0];
if ($readWrite =~ /^(rw|rdwr|o_rdwr)$|write/i and
!$self->isSyncing and
$self->isLockedForSync) {
require Calendar::GetHTML;
GetHTML->errorPage (undef, # i18n
header => 'Calendar `' . $self->name .
"' is Locked",
message => 'This Calendar is currently being ' .
'Synchronized. Please try again ' .
'in a few moments');
warn "Error: Sync in progress on " . $self->name . "\n";
die "\n";
}
$self->{'Imp'}->openDatabase (@_);
}
sub closeDatabase {
my $self = shift;
$self = $$self if ($self->isa ('REF')); # gosh
$self->{'Imp'}->closeDatabase (@_);
}
# Delete a database altogether, i.e. remove it's data files or purge its DB
# tables
sub deleteDatabase {
my $self = shift;
$$self->{'Imp'}->deleteDatabase (@_);
}
sub renameDatabase {
my ($self, $newName) = @_;
return unless $newName =~ /^(\w+)$/; # untaint and check it
$newName = $1;
$$self->{'Imp'}->renameDatabase ($newName);
1;
}
# Pass an arg if you want to overwrite an existing db of the same name.
# This is used when a new Database object has been created but the database
# itself does not yet exist.
sub createDatabase {
my $self = shift;
$self = $$self if ($self->isa ('REF'));
$self->{'Imp'}->createDatabase (@_);
$self->version (Defines->version);
# default permission is 'anybody can do anything' (!?)
my $perm = Permissions->new ($self);
$perm->setAnonymous ('Admin');
}
# Return full path of datafile name, sans extension, (DBImplementation adds
# it) Derived classes override this to put files in weird places. Unused
# for real database implementations.
sub _getBaseFilename {
my $self = shift;
$self = $$self if ($self->isa ('REF'));
return Defines->baseDirectory . "/data/$self->{'name'}";
}
# Return full path of datafile name
sub _getFilename {
my $self = shift;
$self = $$self if ($self->isa ('REF'));
$self->{'Imp'}->_getFilename;
}
sub setPreferencesToDefault {
my $self = shift;
my $db = shift;
my $prefs = ($db && $db->isa ('Database')) ? Preferences->new ($db)
: Preferences->new ();
# Title defaults to calendar name
$prefs->Title ($self->name);
$self->setPreferences ($prefs);
}
# Set preferences, i.e. write to the database
# Pass a ref to a hash of key/value pairs, or a Preferences object
sub setPreferences {
my $self = shift;
$self = $$self if ($self->isa ('REF'));
my ($argHash) = (@_);
my $prefs;
if (ref ($argHash) eq 'Preferences') {
$prefs = $argHash;
} else {
$prefs = $self->{'Imp'}->getPreferences || Preferences->new;
$prefs->setValues ($argHash);
}
$self->{'Imp'}->savePreferences ($prefs);
}
# If called w/no args, return the whole Prefs object.
# If called w/ 1 arg, return just the specified value.
sub getPreferences {
my $self = shift;
my $key = shift;
my $prefs = $$self->{'Imp'}->getPreferences;
return $prefs->{$key} if ($prefs && $key);
return $prefs;
}
# Insert an event. It can be regular or repeating.
sub insertEvent {
my $self = shift;
my ($event, $date) = @_;
$event->id ($$self->{'Imp'}->nextID);
if ($event->isRepeating()) {
$self->_insertRepeatingEvent ($event);
} else {
$self->_insertRegularEvent ($date, $event);
}
# keep track for syncing, maybe
if ($self->getPreferences ('IsSyncable') and
$self->getPreferences ('LastRMSyncID')) {
require Calendar::SyncInfo;
SyncInfo->new ($self->name)->eventAdded ($event->id);
}
}
# Delete event w/the specified id, and re-insert the new version
sub replaceEvent {
my ($self, $event, $date, $noDelete) = @_;
return unless defined $event->id;
if (!defined $noDelete) {
$self->deleteEvent ($date, $event->id, 'all', 'noSyncEntry');
}
if ($event->isRepeating()) {
$self->_insertRepeatingEvent ($event);
} else {
$self->_insertRegularEvent ($date, $event);
}
if ($self->getPreferences ('IsSyncable') and
$self->getPreferences ('LastRMSyncID')) {
require Calendar::SyncInfo;
SyncInfo->new ($self->name)->eventModified ($event->id);
}
}
# Pass list of events, which are all expected to be the same type!
# Return ref to new list of event objects (w/id set.)
# (used for compiling AddIns, bulk loading, TripleSync)
sub insertEvents {
my $self = shift;
my ($eventList) = @_;
return $self->insertEvent ($eventList) unless (ref $eventList eq 'ARRAY');
return unless @$eventList;
my $imp = $self->isa ('REF') ? $$self->{Imp} : $self->{Imp};
my $numEvents = @$eventList;
$numEvents /= 2 unless ($eventList->[0]->isRepeating()); # gross
my $nextID = $imp->reserveNextIDs ($numEvents);
my $newEvents;
if ($eventList->[0]->isRepeating()) {
$newEvents = $imp->insertRepeatingEvents ($eventList, $nextID);
} else {
$newEvents = $imp->insertRegularEvents ($eventList, $nextID);
}
return $newEvents;
}
# Stick an event on the list for a certain date
sub _insertRegularEvent {
my $self = shift;
$$self->{'Imp'}->insertRegularEvent (@_);
}
sub _insertRepeatingEvent {
my $self = shift;
$$self->{'Imp'}->insertRepeatingEvent (@_);
}
# Return an event, identified by date and id.
# Repeating events don't have dates, just ids.
# Return undef if event not found
sub getEvent {
my $self = shift;
my ($date, $eventID) = @_;
my $ev = $$self->{'Imp'}->getEvent ($date, $eventID);
$ev->Prefs ($self->getPreferences) if $ev;
return $ev;
}
# Return event and date; pass in only ID (i.e. no date)
# Return StartDate for repeating events
# Return nada if not found.
sub getEventById {
my ($self, $id) = @_;
my $repeats = $self->getAllRepeatingEvents;
foreach (@$repeats) {
if ($_->id == $id) {
$_->Prefs ($self->getPreferences);
return ($_, $_->repeatInfo->startDate);
}
}
my $regHash = $self->getAllRegularEvents;
foreach my $date (keys %$regHash) {
foreach my $event (@{$regHash->{$date}}) {
if ($event->id == $id) {
$event->Prefs ($self->getPreferences);
return ($event, $date);
}
}
}
return;
}
# Return a ref to a hash of regular events in the specified date range, and
# a ref to an array of ALL repeating events. (Unless the date range is
# bad.) Don't use a large date range, as DB implementations are not very
# smart...
sub getEvents {
my $self = shift;
my ($fromDate, $toDate) = @_;
return if ($fromDate > $toDate);
my @ret;
eval {
@ret = $$self->{'Imp'}->getEvents ($fromDate, $toDate);
};
return @ret unless $@;
warn "Problem with " . $self->name . ": $@";
return ({}, [], 1); # if DB error, return empty event lists and err flag
}
# Delete an event for a certain date. If the event isn't found for that
# date (or the date is undef), maybe it's a repeating event. The third
# param only applies for repeating events. And if there are mail reminders,
# delete them. Return deleted event text.
sub deleteEvent {
my $self = shift;
my ($date, $eventID, $allOrOne, $noSyncEntry) = @_;
my $event = $self->getEvent ($date, $eventID);
return undef unless $event;
if (Defines->mailEnabled and $event and $event->reminderTimes) {
my $justOne = ($allOrOne and ($allOrOne =~ /all/i ? undef : $date));
require Calendar::Mail::MailReminder;
MailReminder->deleteEventReminders ($eventID, $self->name, $justOne);
}
$$self->{'Imp'}->deleteEvent ($date, $eventID, $allOrOne);
# keep track for syncing, maybe (not if we're doing a "replace", though)
if (!$noSyncEntry and
$self->getPreferences ('IsSyncable') and
$self->getPreferences ('LastRMSyncID')) {
require Calendar::SyncInfo;
if ($allOrOne and $allOrOne !~ /all/i) {
SyncInfo->new ($self->name)->eventModified ($eventID);
} else {
SyncInfo->new ($self->name)->eventDeleted ($eventID);
}
}
return ($event && $event->text);
}
# Delete all events in a specified date range. Repeating events will be
# deleted if their start and end specs are within the specified range.
# Don't forget about the MailReminders, too.
# Return ref to list of deleted ids
sub deleteEventsInRange {
my $self = shift;
my ($fromDate, $toDate) = @_;
return if ($fromDate > $toDate);
my $ids = $$self->{'Imp'}->deleteEventsInRange ($fromDate, $toDate);
if (Defines->mailEnabled) {
require Calendar::Mail::MailReminder;
MailReminder->deleteEventReminders ($ids, $self->name);
}
return $ids;
}
# Delete ALL events for the calendar. (Used for syncing.)
# Also resets last id.
sub deleteAllEvents {
my $self = shift;
$$self->{Imp}->deleteAllEvents;
if (Defines->mailEnabled) {
require Calendar::Mail::MailReminder;
MailReminder->deleteAllForCalendar ($self->name);
}
}
# Return a list of all (regular & repeating) events which apply for a
# certain date. Due to timezone offsets, we may need to check previous and
# next day too. Also, events which start yesterday and extend into today
# can be returned.
#
# $flags specifies whether to not adjust event times ("noadjust") for
# timezone, and whether to get "yesterday"s events as well.
#
# So: EVENTS ARE ADJUSTED for TIMEZONE unless 'noadjust' passed in flags
sub getApplicableEvents {
my $self = shift;
my ($date, $prefs, $flags) = @_;
$flags ||= '';
my $noAdjust = $flags =~ /noadjust/;
my $yesterday = $flags =~ /yesterday/;
my ($startDate, $endDate);
my $offset = $noAdjust ? 0 : $prefs->Timezone || 0;
if ($offset < 0) {
$startDate = $date;
$endDate = $date + 1;
} elsif ($offset > 0) {
$startDate = $date - 1;
$endDate = $date;
} else {
$startDate = $endDate = $date;
}
# Events might start yesterday and extend into today
if ($yesterday) {
$startDate--;
}
my $hash = $self->getEventDateHash ($startDate, $endDate, $prefs,
$noAdjust);
my @theEvents = @{$hash->{"$date"} || []};
# for repeating events, make sure Date is right
# set Date for events; important for repeating events
if (!$noAdjust) {
foreach (@theEvents) {
next unless $_->isRepeating;
$_->Date ($date);
}
}
# add events that start yesterday, but extend into today. (Need for
# TimePlan, DayPlanner views, as well as conflict checking.)
if ($yesterday) {
my $yesterday = $date - 1;
foreach (@{$hash->{"$yesterday"} || []}) {
next unless ($_->endTime and $_->endTime < $_->startTime);
# save actual date it's on. If repeating, we need a copy! (In
# case on both days.)
my $copy = $_->copy;
$copy->Date ($yesterday);
push @theEvents, $copy;
}
}
return @theEvents;
}
sub getApplicableEvents_old {
my $self = shift;
my ($date, $prefs) = @_;
my @events;
my ($regularEvents, $repeatingEvents) = $self->getEventLists ($prefs,
$date);
foreach (@$repeatingEvents,
($regularEvents ? @{$regularEvents->{"$date"}} : ())) {
next unless defined;
next unless ($_->applies ($date));
push @events, $_;
}
@events;
}
# Return a ref to a hash of lists of applicable events, keyed on dates.
# Pass in a from and to date, and prefs, and flag to not adjust for tz.
# We'll grab the single events from the Database, and ask each repeating
# event to fill the hash with its repeating fellows.
# Events included from other cals aren't included unless they should be.
# EVENTS ARE ADJUSTED for TIMEZONE unless 'noadjust' passed
sub getEventDateHash {
my $self = shift;
my ($fromDate, $toDate, $prefs, $noAdjust) = @_;
my ($regs, $repeats) = $self->getEventLists ($prefs, $fromDate, $toDate);
foreach (@$repeats) {
$_->addToDateHash ($regs, $fromDate, $toDate, $prefs);
}
return $regs if $noAdjust;
my $offset = $prefs->Timezone;
if (!$offset) {
return $regs;
}
# Timezone Offset specified, adjust
my %adjusted;
while (my ($date, $list) = each %$regs) {
foreach my $event (@$list) {
my $dateObj = Date->new ($date);
my $newDate;
my $copy = $event->copy;
# be careful, it may have already been adjusted
if (defined $copy->TZoffset) {
$newDate = $dateObj + $copy->TZoffset;
} else {
# we need to make a copy first, in case orig is cached
$copy->adjustForTimezone ($dateObj, $offset);
$newDate = $copy->Date;
}
$newDate = "$newDate"; # must stringify Date obj
$adjusted{$newDate} ||= [];
push @{$adjusted{$newDate}}, $copy;
}
}
return \%adjusted;
}
# Return a ref to a hash of lists of tentative events, keyed on dates.
# Repeating events are NOT EXPANDED; they're only added to the list for
# their start date. (But the start date is adjusted to be first date event
# actually occurs on.)
sub getTentativeEvents {
my ($self) = @_;
my $regHash = $self->getAllRegularEvents;
my $repeats = $self->getAllRepeatingEvents;
foreach my $date (keys %$regHash) {
$regHash->{$date} = [grep {$_->isTentative} @{$regHash->{$date}}];
delete $regHash->{$date} unless $regHash->{$date}->[0];
}
my $prefs;
foreach (@$repeats) {
next unless $_->isTentative;
my $startDate = $_->repeatInfo->startDate;
# need to find first date event actually occurs
if (!$_->repeatInfo->applies ($startDate)) {
$prefs ||= $self->getPreferences; # only get first time
my $hash = $_->repeatInfo->nextNOccurrences ($_, 1, $startDate,
$prefs);
my ($date) = keys %$hash;
$startDate = $date || $startDate;
}
$startDate = "$startDate"; # in case it's a Date
$regHash->{$startDate} ||= [];
push @{$regHash->{$startDate}}, $_;
}
$regHash;
}
# Return a hash of lists of regular events, keyed on date, and a list of
# repeating events. Pass 1 arg for a single day, or a from date and to date.
# Add-Ins are handled through the 'included calendar' stuff.
sub getEventLists {
my $self = shift;
my ($prefs, $fromDate, $toDate) = @_;
$toDate = $fromDate unless $toDate;
return (undef, undef) if ($fromDate > $toDate);
my (%regularEvents, @repeatingEvents);
# Get events from each calendar we include, including ourselves.
my @includeNames = $prefs->getIncludedCalendarNames;
my @dbList = map {Database->new ($_)} @includeNames;
unshift @dbList, $self;
# Keep track of which categories to include for each included calendar
my $incInfo = $prefs->getIncludedCalendarInfo;
my %catLists;
while (my ($calName, $incHash) = each %$incInfo) {
$catLists{$calName} = $incHash->{Categories};
}
# And don't forget to stick the Add-Ins on there
my @addInNames = $prefs->getIncludedAddInNames;
push @dbList, map {AddIn->new ($_, $self)} @addInNames;
foreach my $db (@dbList) {
# First, regular (i.e. not repeating) events
my ($eventHash, $repeaters, $error, $key, $value);
($eventHash, $repeaters, $error) = $db->getEvents ($fromDate, $toDate);
# if problem w/AddIn, un-include it
if ($error and $db->isa ('AddIn')) {
$self->removeAddIns ($db->name);
}
while (($key, $value) = (each %$eventHash)) {
foreach (@$value) {
next if (($db != $self) && $_->private);
if ($db != $self) {
my $dbname = $db->name;
# If we don't want this category, skip it
next unless _wantedCategory ($_, $dbname, \%catLists);
$dbname = "ADDIN $dbname" if (ref ($db) eq 'AddIn');
$_->includedFrom ($dbname);
}
# Have each event keep track of the preferences for this DB
$_->Prefs ($prefs);
push @{$regularEvents{$key}}, $_;
}
}
# Then repeating events
foreach (@$repeaters) {
# Keep track of where this event was included from
next if (($db != $self) && $_->private);
# skip it if completely outside date range
next if ($_->repeatInfo->startDate > $toDate or
$_->repeatInfo->endDate < $fromDate);
if ($db != $self) {
my $dbname = $db->name;
next unless _wantedCategory ($_, $dbname, \%catLists);
$dbname = "ADDIN $dbname" if (ref ($db) eq 'AddIn');
$_->includedFrom ($dbname);
}
# Have each event keep track of the preferences for this DB
$_->Prefs ($prefs);
push @repeatingEvents, $_;
}
}
sub _wantedCategory {
my ($event, $dbname, $catLists) = @_;
my $theseCats = $catLists->{$dbname};
return 1 unless ($theseCats and @$theseCats);
my $evCat = $event->category || '<- - - ->';
# return if (!defined $evCat);
foreach (@$theseCats) {
return 1 if ($_ eq $evCat);
}
return undef;
}
return (\%regularEvents, \@repeatingEvents);
}
# Pass username and permission level.
sub setPermission {
my $self = shift;
my ($userName, $permission) = @_;
$self = $$self if ($self->isa ('REF'));
$self->{'Imp'}->setPermission ($userName, $permission);
}
# Pass username; returns current permission level, or undef.
sub getPermission {
my $self = shift;
my ($userName) = @_;
$$self->{'Imp'}->getPermission ($userName);
}
# Return hash of username->perms
sub getPermittedUsers {
my $self = shift;
$$self->{'Imp'}->getPermittedUsers;
}
# Set the hash of username->perms
sub setPermittedUsers {
my $self = shift;
my $hashRef = shift;
$$self->{'Imp'}->setPermittedUsers ($hashRef);
}
sub _removeOrRenameInIncludeLists {
my $classname = shift;
my ($name1, $name2) = @_; # if $name2 defined, we rename, else delete
my @calendars = MasterDB->getAllCalendars;
foreach (@calendars) {
my $db = Database->new ($_);
my $prefs = $db->getPreferences;
my $includes = $prefs->{Includes};
if ($includes && defined $includes->{$name1}) {
$includes->{$name2} = $includes->{$name1} if ($name2);
delete $includes->{$name1};
$db->setPreferences ({Includes => $includes});
}
}
}
sub removeFromIncludeLists {
my $classname = shift;
my ($dbNameToDelete) = @_;
$classname->_removeOrRenameInIncludeLists ($dbNameToDelete);
}
sub renameInIncludeLists {
my $classname = shift;
my ($oldName, $newName) = @_;
$classname->_removeOrRenameInIncludeLists ($oldName, $newName);
}
sub removeAddIns {
my ($self, @addInNames) = @_;
my $includes = $self->getPreferences ('Includes');
foreach (@addInNames) {
delete $includes->{"ADDIN $_"};
}
$self->setPreferences ({Includes => $includes});
}
sub findEventsMatching {
my $self = shift;
my ($text, $fromDate, $toDate, $ignoreCase, $quoteRegex) = @_;
my %returnHash;
my $eventHash = $self->getAllRegularEvents;
$text = quotemeta ($text) unless $quoteRegex;
$text = "(?i)$text" if $ignoreCase;
while (my ($date, $list) = each %$eventHash) {
next unless (Date->new ($date))->inRange ($fromDate, $toDate);
foreach my $event (@$list) {
next unless $event->text =~ /$text/;
$returnHash{$date} = [] unless $returnHash{$date};
push @{$returnHash{$date}}, $event;
}
}
\%returnHash;
}
sub findRepeatingEventsMatching {
my $self = shift;
my ($text, $fromDate, $toDate, $ignoreCase, $quoteRegex) = @_;
my @returnList;
my $eventList = $self->getAllRepeatingEvents;
$text = quotemeta ($text) unless $quoteRegex;
$text = "(?i)$text" if $ignoreCase;
foreach my $event (@$eventList) {
next unless $event->text =~ /$text/;
next unless ($event->repeatInfo->startDate->inRange ($fromDate,
$toDate) ||
$event->repeatInfo->endDate->inRange ($fromDate,
$toDate));
push @returnList, $event;
}
\@returnList;
}
sub getAllRegularEvents {
my $self = shift;
$$self->{'Imp'}->getAllRegularEvents (@_);
}
sub getAllRepeatingEvents {
my $self = shift;
$$self->{'Imp'}->getAllRepeatingEvents (@_);
}
# set/get file to log events to
sub auditingFile {
my $self = shift;
my $filename = shift;
return $$self->{'Imp'}->getAuditFile unless $filename;
$$self->{'Imp'}->setAuditFile ($filename);
}
# set/get list of email addresses to log events to
sub auditingEmail {
my $self = shift;
my (@emails) = @_;
unless (@emails) {
my $string = $$self->{'Imp'}->getAuditEmailAddresses || '';
return (wantarray ? split /\s/, $string : $string);
}
my $emailString = join ' ', @emails;
$$self->{'Imp'}->setAuditEmailAddresses ($emailString);
}
# Pass op and list of Auditing types ('file', 'email')
sub setAuditing {
my $self = shift;
my ($opName, @auditList) = @_;
my $auditString = join ' ', @auditList;
$$self->{'Imp'}->setAuditing ($opName, $auditString);
}
# Pass op type or name; returns list of Auditing types (current types:
# 'file' 'email')
sub getAuditing {
my $self = shift;
my ($opName) = @_;
my $auditString = $$self->{'Imp'}->getAuditing ($opName) || '';
split /\s/, $auditString;
}
# Ug. In addition to modifying prefs, have to find and modify all events in
# this category.
# "Flags" arg controls what to do. If:
# undef change in preferences and events
# /prefs/ change prefs
# /events/ change events
sub renameCategory {
my ($self, $oldName, $newName, $flags) = @_;
return unless ($oldName and $newName);
if (!$flags or ($flags =~ /prefs/)) {
my $prefs = $self->getPreferences;
my $newCat = $prefs->category ($oldName);
return unless $newCat;
$newCat->name ($newName);
$prefs->category ($newName, $newCat);
$prefs->deleteCategory ($oldName);
$self->setPreferences ($prefs);
}
return if ($flags and ($flags !~ /events/));
my $regHash = $self->getAllRegularEvents;
my $repeats = $self->getAllRepeatingEvents;
foreach my $date (keys %$regHash) {
foreach my $event (@{$regHash->{$date}}) {
my $evCat = $event->category;
next if (!defined $evCat or $evCat ne $oldName);
$event->category ($newName);
$self->replaceEvent ($event, $date);
}
}
foreach my $event (@$repeats) {
my $evCat = $event->category;
next if (!defined $evCat or $evCat ne $oldName);
$event->category ($newName);
$self->replaceEvent ($event);
}
}
sub isSyncing {
my $self = shift;
$self = $$self if ($self->isa ('REF')); # urg
$self->{isSyncing} = shift if (@_);
$self->{isSyncing};
}
sub isLockedForSync {
my ($self) = @_;
return undef if $self->isa ('MasterDB'); # Master can't be synced.
return undef unless -d $self->syncLockDirName;
# lock dir exists, see if expired
my $expireTime = $self->_getLockExpiration;
return 1 if ($expireTime > time); # lock still good
# lock expired; remove lock dir and file, return undef - we're not locked
$self->lockForSync;
return undef;
}
sub syncLockDirName {
my ($self) = @_;
return $self->_getBaseFilename . '.SyncLock';
}
sub syncLockExpiryFileName {
my ($self) = @_;
my $expireFile = $self->syncLockDirName . "/expireTime";
}
sub _getLockExpiration {
my ($self) = @_;
my $lockDir =
my $expireFile = $self->syncLockExpiryFileName;
open (TIMEOUT, "< $expireFile") or return -1;
my $expiry = <TIMEOUT>;
return $expiry + 0;
}
# Pass number of seconds to lock for; undef to unlock.
# Return undef if lock or unlock fails
sub lockForSync {
my ($self, $lockTime) = @_;
# For now, just create or test for existence of a lockfile.
my $lockDir = $self->syncLockDirName;
my $expireFile = $self->syncLockExpiryFileName;
# if locking, create dir and file containing expire time
if (defined $lockTime) {
return undef if $self->isLockedForSync;
mkdir ($lockDir, 0777) or return undef;
sysopen (TIMEOUT, $expireFile, O_WRONLY|O_CREAT|O_EXCL)
or return undef;
my $expiry = time + $lockTime;
print TIMEOUT "$expiry\n";
close TIMEOUT or return undef;
return 1;
} else { # otherwise, unlock; remove file and dir
unlink $expireFile;
return rmdir ($lockDir);
}
}
# Make copy of event data which we can revert to if necessary.
# Return 1 on success, 0 on failure.
sub backupForSync {
my ($self) = @_;
require Calendar::SyncInfo;
SyncInfo->new ($self->name)->backupForSync;
$$self->{'Imp'}->backupForSync;
}
# Return 1 on success, 0 on failure.
sub revertForSync {
my ($self) = @_;
SyncInfo->new ($self->name)->revertForSync;
$$self->{'Imp'}->revertForSync;
}
1;