find next/prev slot

Monotone-Parent: 03b6f11c1a9a04660326370486a96b95a63cbaf2
Monotone-Revision: 4efc308e378492efdb95420253ab9f2c2b3ab674

Monotone-Author: crobert@inverse.ca
Monotone-Date: 2009-06-04T14:57:50
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
C Robert 2009-06-04 14:57:50 +00:00
parent bfab0126e3
commit 52a4de60bd
6 changed files with 389 additions and 5 deletions

View File

@ -1,3 +1,11 @@
2009-06-04 Cyril Robert <crobert@inverse.ca>
* UI/Scheduler/UIxCalListingActions.m: Added everything needed for the
findSlot action.
* UI/WebServerResources/UIxAttendeesEditor.js: Added everything needed for
the findSlot action.
* UI/Scheduler/UIxCalListingActions.h (findPossibleSlotAction): Added header.
2009-06-03 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoGCSFolder.m

View File

@ -51,6 +51,7 @@
- (WOResponse *) alarmsListAction;
- (WOResponse *) eventsListAction;
- (WOResponse *) tasksListAction;
- (WOResponse *) findPossibleSlotAction;
@end

View File

@ -44,6 +44,7 @@
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentFolders.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import <Appointments/SOGoFreeBusyObject.h>
#import <UI/Common/WODirectAction+SOGo.h>
@ -57,6 +58,12 @@ static NSArray *tasksFields = nil;
#define dayLength 86400
#define quarterLength 900
#define intervalSeconds 900
#define offsetHours 24 * 5
#define offsetSeconds offsetHours * 60 * 60
#define offsetBlocks offsetHours * 4
#define maxBlocks offsetBlocks * 2
@implementation UIxCalListingActions
+ (void) initialize
@ -953,4 +960,289 @@ _computeBlocksPosition (NSArray *blocks)
return [self _responseWithData: filteredTasks];
}
- (void) _fillFreeBusy: (unsigned int *) fb
forUid: (NSString *) uid
fromDate: (NSCalendarDate *) start
{
SOGoUser *user;
SOGoFreeBusyObject *fbObject;
NSCalendarDate *end;
NSTimeInterval interval;
NSArray *records;
NSDictionary *record;
NSArray *emails, *partstates;
NSCalendarDate *currentDate;
unsigned int intervals, recordCount, recordMax;
int count, startInterval, endInterval, i, type;
int itemCount = (offsetBlocks + maxBlocks) * 15 * 60;
user = [SOGoUser userWithLogin: uid roles: nil];
fbObject = [[user homeFolderInContext: context]
freeBusyObject: @"freebusy.ifb"
inContext: context];
end = [start addTimeInterval: itemCount];
interval = [end timeIntervalSinceDate: start];// + 60;
intervals = interval / intervalSeconds; /* slices of 15 minutes */
records = [fbObject fetchFreeBusyInfosFrom: start to: end];
recordMax = [records count];
for (recordCount = 0; recordCount < recordMax; recordCount++)
{
record = [records objectAtIndex: recordCount];
if ([[record objectForKey: @"c_isopaque"] boolValue])
{
type = 0;
// If the event has NO organizer (which means it's the user that has created it) OR
// If we are the organizer of the event THEN we are automatically busy
if ([[record objectForKey: @"c_orgmail"] length] == 0 ||
[user hasEmail: [record objectForKey: @"c_orgmail"]])
{
type = 1;
}
else
{
// We check if the user has accepted/declined or needs action
// on the current event.
emails = [[record objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
for (i = 0; i < [emails count]; i++)
{
if ([user hasEmail: [emails objectAtIndex: i]])
{
// We now fetch the c_partstates array and get the participation
// status of the user for the event
partstates = [[record objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
if (i < [partstates count])
{
type = ([[partstates objectAtIndex: i] intValue] < 2 ? 1 : 0);
}
break;
}
}
}
currentDate = [record objectForKey: @"startDate"];
if ([currentDate earlierDate: start] == currentDate)
startInterval = 0;
else
startInterval = ([currentDate timeIntervalSinceDate: start]
/ intervalSeconds);
currentDate = [record objectForKey: @"endDate"];
if ([currentDate earlierDate: end] == end)
endInterval = itemCount - 1;
else
endInterval = ([currentDate timeIntervalSinceDate: start]
/ intervalSeconds);
if (type == 1)
for (count = startInterval; count < endInterval; count++)
*(fb + count) = 1;
}
}
}
- (unsigned int **) _loadFreeBusyForUsers: (NSArray *) uids
fromDate: (NSCalendarDate *) start
{
unsigned int **fbData, **node, count;
fbData = calloc ([uids count], sizeof (unsigned int *));
for (count = 0; count < [uids count]; count++)
{
fbData[count] = calloc (offsetBlocks + maxBlocks, sizeof (unsigned int *));
node = fbData + count;
[self _fillFreeBusy: *node
forUid: [uids objectAtIndex: count]
fromDate: start];
}
return fbData;
}
- (BOOL) _possibleBlock: (int) block
forUsers: (int) users
freeBusy: (unsigned int **) fbData
interval: (int) blocks
{
unsigned int *node;
int bCount, uCount;
BOOL rc = YES;
for (uCount = 0; uCount < users; uCount++)
{
node = *(fbData + uCount);
for (bCount = block; bCount < block + blocks; bCount++)
{
if (*(node+bCount) != 0)
{
rc = NO;
break;
}
}
}
return rc;
}
- (NSCalendarDate *) _parseDateField: (NSString *) dateF
timeField: (NSString *) timeF
{
NSString *buffer;
NSCalendarDate *rc;
buffer = [NSString stringWithFormat: @"%@ %@",
[[context request] formValueForKey: dateF],
[[context request] formValueForKey: timeF]];
rc = [NSCalendarDate dateWithString: buffer
calendarFormat: @"%Y%m%d %H:%M"];
return rc;
}
- (NSMutableDictionary *) _makeValidResponseFrom: (NSCalendarDate *) nStart
to: (NSCalendarDate *) nEnd
{
NSMutableDictionary *rc;
rc = [NSMutableDictionary dictionaryWithCapacity: 512];
[rc setObject: [nStart descriptionWithCalendarFormat: @"%Y%m%d"]
forKey: @"startDate"];
[rc setObject: [nStart descriptionWithCalendarFormat: @"%H"]
forKey: @"startHour"];
[rc setObject: [nStart descriptionWithCalendarFormat: @"%M"]
forKey: @"startMinute"];
[rc setObject: [nEnd descriptionWithCalendarFormat: @"%Y%m%d"]
forKey: @"endDate"];
[rc setObject: [nEnd descriptionWithCalendarFormat: @"%H"]
forKey: @"endHour"];
[rc setObject: [nEnd descriptionWithCalendarFormat: @"%M"]
forKey: @"endMinute"];
return rc;
}
- (BOOL) _validateStart: (NSCalendarDate *) start
andEnd: (NSCalendarDate *) end
against: (NSArray *) limits
{
NSCalendarDate *maxFrom, *maxTo;
NSString *buffer;
BOOL rc = YES;
buffer = [NSString stringWithFormat: @"%04d-%02d-%02d %@ %05d",
[start yearOfCommonEra], [start monthOfYear], [start dayOfMonth],
[[limits objectAtIndex: 0] descriptionWithCalendarFormat: @"%H:%M"],
([[start timeZone] secondsFromGMTForDate: start]/36)];
maxFrom = [NSCalendarDate dateWithString: buffer
calendarFormat: @"%Y-%m-%d %H:%M %z"];
buffer = [NSString stringWithFormat: @"%04d-%02d-%02d %@ %05d",
[start yearOfCommonEra], [start monthOfYear], [start dayOfMonth],
[[limits objectAtIndex: 1] descriptionWithCalendarFormat: @"%H:%M"],
([[end timeZone] secondsFromGMTForDate: end]/36)];
maxTo = [NSCalendarDate dateWithString: buffer
calendarFormat: @"%Y-%m-%d %H:%M %z"];
if ([maxFrom compare: start] == NSOrderedDescending)
rc = NO;
if ([maxTo compare: end] == NSOrderedAscending)
rc = NO;
return rc;
}
- (NSArray *) _loadScheduleLimitsForUsers: (NSArray *) users
{
SOGoUserDefaults *ud;
NSCalendarDate *from, *to, *maxFrom, *maxTo;
int count;
maxFrom = [NSCalendarDate dateWithString: @"00:01"
calendarFormat: @"%H:%M"];
maxTo = [NSCalendarDate dateWithString: @"23:59"
calendarFormat: @"%H:%M"];
for (count = 0; count < [users count]; count++)
{
ud = [[SOGoUser userWithLogin: [users objectAtIndex: count]
roles: nil] primaryUserDefaults];
from = [NSCalendarDate dateWithString: [ud objectForKey: @"DayStartTime"]
calendarFormat: @"%H:%M"];
to = [NSCalendarDate dateWithString: [ud objectForKey: @"DayEndTime"]
calendarFormat: @"%H:%M"];
maxFrom = (NSCalendarDate *)[from laterDate: maxFrom];
maxTo = (NSCalendarDate *)[to earlierDate: maxTo];
}
return [NSArray arrayWithObjects: maxFrom, maxTo, nil];
}
- (WOResponse *) findPossibleSlotAction
{
WORequest *r;
NSCalendarDate *nStart, *nEnd;
NSArray *uids, *limits;
NSMutableDictionary *rc;
int direction, count, blockDuration;
unsigned int **fbData;
r = [context request];
rc = nil;
uids = [[r formValueForKey: @"uids"] componentsSeparatedByString: @","];
if ([uids count] > 0)
{
limits = [self _loadScheduleLimitsForUsers: uids];
direction = [[r formValueForKey: @"direction"] intValue];
nStart = [[self _parseDateField: @"startDate"
timeField: @"startTime"]
addTimeInterval: intervalSeconds * direction];
nEnd = [[self _parseDateField: @"endDate"
timeField: @"endTime"]
addTimeInterval: intervalSeconds * direction];
blockDuration = [nEnd timeIntervalSinceDate: nStart] / intervalSeconds;
fbData = [self _loadFreeBusyForUsers: uids
fromDate: [nStart addTimeInterval: -offsetSeconds]];
for (count = offsetBlocks;
(count < offsetBlocks + maxBlocks) && count >= 0;
count += direction)
{
if ([self _validateStart: nStart
andEnd: nEnd
against: limits])
{
if ([self _possibleBlock: count
forUsers: [uids count]
freeBusy: fbData
interval: blockDuration])
{
rc = [self _makeValidResponseFrom: nStart
to: nEnd];
break;
}
}
nStart = [nStart addTimeInterval: intervalSeconds * direction];
nEnd = [nEnd addTimeInterval: intervalSeconds * direction];
}
for (count = 0; count < [uids count]; count++)
free (*(fbData+count));
free (fbData);
}
return [self _responseWithData: [NSArray arrayWithObjects: rc, nil]];
}
@end

View File

@ -74,6 +74,11 @@
actionClass = "UIxCalListingActions";
actionName = "tasksList";
};
findPossibleSlot = {
protectedBy = "View";
actionClass = "UIxCalListingActions";
actionName = "findPossibleSlot";
};
dayview = {
protectedBy = "View";
pageName = "UIxCalDayView";

View File

@ -21,9 +21,9 @@
<div id="attendeesView">
<div id="freeBusyViewButtons">
<var:string label:value="Suggest time slot:"/>
<a href="#" class="button _disabled"
<a id="previousSlot" href="#" class="button"
><var:string label:value="Previous slot" /></a>
<a href="#" class="button _disabled"
<a id="nextSlot" href="#" class="button"
><var:string label:value="Next slot" /></a>
</div>
<!--

View File

@ -447,9 +447,9 @@ function initializeWindowButtons() {
okButton.observe("click", onEditorOkClick, false);
cancelButton.observe("click", onEditorCancelClick, false);
var buttons = $("freeBusyViewButtons").childNodesWithTag("a");
for (var i = 0; i < buttons.length; i++)
buttons[i].observe("click", listRowMouseDownHandler, false);
$("previousSlot").observe ("click", onPreviousSlotClick, false);
$("nextSlot").observe ("click", onNextSlotClick, false);
/* buttons = $("freeBusyZoomButtons").childNodesWithTag("a");
for (var i = 0; i < buttons.length; i++)
buttons[i].observe("click", listRowMouseDownHandler, false);
@ -459,6 +459,84 @@ function initializeWindowButtons() {
*/
}
function findSlot (direction) {
var userList = UserLogin;
var table = $("freeBusy");
var inputs = table.getElementsByTagName("input");
var sd = window.timeWidgets['start']['date'].valueAsShortDateString();
var st = window.timeWidgets['start']['hour'].value
+ ":" + window.timeWidgets['start']['minute'].value;
var ed = window.timeWidgets['end']['date'].valueAsShortDateString();
var et = window.timeWidgets['end']['hour'].value
+ ":" + window.timeWidgets['end']['minute'].value;
for (var i = 0; i < inputs.length - 2; i++)
{
userList += "," + inputs[i].uid;
}
// Abort any pending request
if (document.findSlotAjaxRequest) {
document.findSlotAjaxRequest.aborted = true;
document.findSlotAjaxRequest.abort();
}
var urlstr = (ApplicationBaseURL
+ "/findPossibleSlot?direction=" + direction
+ "&uids=" + escape (userList)
+ "&startDate=" + escape (sd)
+ "&startTime=" + escape (st)
+ "&endDate=" + escape (ed)
+ "&endTime=" + escape (et));
document.findSlotAjaxRequest = triggerAjaxRequest(urlstr,
updateSlotDisplayCallback,
userList);
}
function cleanInt (data) {
var rc = data;
if (rc.substr (0, 1) == "0")
rc = rc.substr (1, rc.length - 1);
return parseInt (rc);
}
function updateSlotDisplayCallback (http) {
var data = http.responseText.evalJSON (true);
var start = new Date ();
var end = new Date ();
var cb = redisplayFreeBusyZone;
start.setFullYear (parseInt (data[0]['startDate'].substr (0, 4)),
parseInt (data[0]['startDate'].substr (4, 2)) - 1,
parseInt (data[0]['startDate'].substr (6, 2)));
end.setFullYear (parseInt (data[0]['endDate'].substr (0, 4)),
parseInt (data[0]['endDate'].substr (4, 2)) - 1,
parseInt (data[0]['endDate'].substr (6, 2)));
window.timeWidgets['end']['date'].setValueAsDate (end);
window.timeWidgets['end']['hour'].value = cleanInt (data[0]['endHour']);
window.timeWidgets['end']['minute'].value = cleanInt (data[0]['endMinute']);
if (window.timeWidgets['start']['date'].valueAsShortDateString () !=
data[0]['startDate'])
{
cb = onTimeDateWidgetChange;
}
window.timeWidgets['start']['date'].setValueAsDate (start);
window.timeWidgets['start']['hour'].value = cleanInt (data[0]['startHour']);
window.timeWidgets['start']['minute'].value = cleanInt (data[0]['startMinute']);
cb ();
}
function onPreviousSlotClick (event) {
findSlot (-1);
}
function onNextSlotClick (event) {
findSlot (1);
}
function onEditorOkClick(event) {
preventDefault(event);