use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class Appointment method checkAvailability.
private ConflictCheckResult checkAvailability(long now, Invite invite, int maxNumConflicts, int maxPctConflicts) throws ServiceException {
long st, et;
if (invite.isRecurrence()) {
// Resource is getting invited to a recurring appointment.
st = getStartTime();
et = getEndTime();
} else {
// Resource is getting invited to a single instance.
ParsedDateTime dtStart = invite.getStartTime();
ParsedDateTime dtEnd = invite.getEffectiveEndTime();
if (dtStart != null && dtEnd != null) {
st = dtStart.getUtcTime();
et = dtEnd.getUtcTime();
} else {
// Start time and/or end time can't be determined. Give up.
return null;
}
}
// Ignore conflicts in the past.
st = Math.max(st, now);
if (et <= st)
return null;
OperationContext octxt = new OperationContext(getAccount());
Collection<Instance> instances;
if (invite.isRecurrence()) {
instances = expandInstances(st, et, false);
} else {
instances = new ArrayList<Instance>(1);
instances.add(Instance.fromInvite(getId(), invite));
}
if (instances == null || instances.isEmpty())
return null;
int maxByPct = maxPctConflicts * instances.size() / 100;
int maxConflicts = Math.min(maxNumConflicts, maxByPct);
List<Conflict> list = new ArrayList<Conflict>();
int numConflicts = 0;
boolean hasMoreConflicts = false;
for (Instance inst : instances) {
if (numConflicts > Math.max(maxConflicts, MIN_CONFLICT_LIST_SIZE - 1)) {
hasMoreConflicts = true;
break;
}
long start = inst.getStart();
long end = inst.getEnd();
// Run free/busy search of this user between instance start/end times.
FreeBusy fb = getMailbox().getFreeBusy(octxt, start, end, this);
String status = fb.getBusiest();
if (Conflict.isBusy(status)) {
list.add(new Conflict(inst, status, fb));
numConflicts++;
}
}
return new ConflictCheckResult(list, numConflicts > maxConflicts, hasMoreConflicts);
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class CalendarItem method create.
static CalendarItem create(int id, Folder folder, int flags, Tag.NormalizedTags ntags, String uid, ParsedMessage pm, Invite firstInvite, long nextAlarm, CustomMetadata custom) throws ServiceException {
firstInvite.sanitize(false);
if (!folder.canAccess(ACL.RIGHT_INSERT)) {
throw ServiceException.PERM_DENIED("you do not have the required rights on the folder");
}
if (!firstInvite.isPublic() && !folder.canAccess(ACL.RIGHT_PRIVATE)) {
throw ServiceException.PERM_DENIED("you do not have permission to create private calendar item in this folder");
}
Mailbox mbox = folder.getMailbox();
if (pm != null && pm.hasAttachments()) {
firstInvite.setHasAttachment(true);
flags |= Flag.BITMASK_ATTACHED;
} else {
firstInvite.setHasAttachment(false);
flags &= ~Flag.BITMASK_ATTACHED;
}
if (firstInvite.isDraft()) {
flags |= Flag.BITMASK_DRAFT;
} else {
flags &= ~Flag.BITMASK_DRAFT;
}
if (firstInvite.isHighPriority()) {
flags |= Flag.BITMASK_HIGH_PRIORITY;
} else {
flags &= ~Flag.BITMASK_HIGH_PRIORITY;
}
if (firstInvite.isLowPriority()) {
flags |= Flag.BITMASK_LOW_PRIORITY;
} else {
flags &= ~Flag.BITMASK_LOW_PRIORITY;
}
MailItem.Type type = firstInvite.isEvent() ? Type.APPOINTMENT : Type.TASK;
String sender = null;
ZOrganizer org = firstInvite.getOrganizer();
if (org != null) {
sender = org.getIndexString();
}
sender = Strings.nullToEmpty(sender);
String subject = Strings.nullToEmpty(firstInvite.getName());
List<Invite> invites = new ArrayList<Invite>();
invites.add(firstInvite);
Recurrence.IRecurrence recur = firstInvite.getRecurrence();
long startTime, endTime;
if (recur != null) {
ParsedDateTime dtStart = recur.getStartTime();
startTime = dtStart != null ? dtStart.getUtcTime() : 0;
ParsedDateTime dtEnd = recur.getEndTime();
endTime = dtEnd != null ? dtEnd.getUtcTime() : 0;
} else {
ParsedDateTime dtStart = firstInvite.getStartTime();
startTime = dtStart != null ? dtStart.getUtcTime() : 0;
ParsedDateTime dtEnd = firstInvite.getEffectiveEndTime();
endTime = dtEnd != null ? dtEnd.getUtcTime() : startTime;
}
Account account = mbox.getAccount();
firstInvite.updateMyPartStat(account, firstInvite.getPartStat());
UnderlyingData data = new UnderlyingData();
data.id = id;
data.type = type.toByte();
data.folderId = folder.getId();
if (!folder.inSpam() || mbox.getAccount().getBooleanAttr(Provisioning.A_zimbraJunkMessagesIndexingEnabled, false)) {
data.indexId = IndexStatus.DEFERRED.id();
}
data.imapId = id;
data.date = mbox.getOperationTimestamp();
data.setFlags(flags & (Flag.FLAGS_CALITEM | Flag.FLAGS_GENERIC));
data.setTags(ntags);
data.setSubject(subject);
data.metadata = encodeMetadata(DEFAULT_COLOR_RGB, 1, 1, custom, uid, startTime, endTime, recur, invites, firstInvite.getTimeZoneMap(), new ReplyList(), null);
data.contentChanged(mbox, false);
if (!firstInvite.hasRecurId()) {
ZimbraLog.calendar.info("Adding CalendarItem: id=%d, Message-ID=\"%s\", folderId=%d, subject=\"%s\", UID=%s", data.id, pm != null ? pm.getMessageID() : "(none)", folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid());
} else {
ZimbraLog.calendar.info("Adding CalendarItem: id=%d, Message-ID=\"%s\", folderId=%d, subject=\"%s\", UID=%s, recurId=%s", data.id, pm != null ? pm.getMessageID() : "(none)", folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid(), firstInvite.getRecurId().getDtZ());
}
new DbMailItem(mbox).setSender(sender).create(data);
CalendarItem item = type == Type.APPOINTMENT ? new Appointment(mbox, data) : new Task(mbox, data);
Invite defInvite = item.getDefaultInviteOrNull();
if (defInvite != null) {
Collection<Instance> instances = item.expandInstances(CalendarUtils.MICROSOFT_EPOC_START_MS_SINCE_EPOC, Long.MAX_VALUE, false);
if (instances.isEmpty()) {
ZimbraLog.calendar.info("CalendarItem has effectively zero instances: id=%d, folderId=%d, subject=\"%s\", UID=%s ", data.id, folder.getId(), firstInvite.isPublic() ? firstInvite.getName() : "(private)", firstInvite.getUid());
item.delete();
throw ServiceException.FORBIDDEN("Recurring series has effectively zero instances");
}
}
// If we're creating an invite during email delivery, always default to NEEDS_ACTION state.
// If not email delivery, we assume the requesting client knows what it's doing and has set the
// correct partstat in the invite.
String defaultPartStat;
if (mbox.getOperationContext() == null) {
// octxt == null implies we're in email delivery. (There needs to be better way to determine this...)
defaultPartStat = IcalXmlStrMap.PARTSTAT_NEEDS_ACTION;
} else {
defaultPartStat = firstInvite.getPartStat();
}
item.processPartStat(firstInvite, pm != null ? pm.getMimeMessage() : null, true, defaultPartStat);
item.finishCreation(null);
folder.updateHighestMODSEQ();
if (pm != null) {
item.createBlob(pm, firstInvite);
}
item.mEndTime = item.recomputeRecurrenceEndTime(item.mEndTime);
if (firstInvite.hasAlarm()) {
item.recomputeNextAlarm(nextAlarm, false, false);
item.saveMetadata();
AlarmData alarmData = item.getAlarmData();
if (alarmData != null) {
long newNextAlarm = alarmData.getNextAtBase();
if (newNextAlarm > 0 && newNextAlarm < item.mStartTime) {
item.mStartTime = newNextAlarm;
}
}
}
DbMailItem.addToCalendarItemTable(item);
Callback cb = getCallback();
if (cb != null) {
cb.created(item);
}
return item;
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class CalendarItem method updateLocalExceptionsWhichMatchSeriesReply.
/**
* Exceptions can be created which aren't communicated to ATTENDEEs, either because the ORGANIZER wants to
* have local changes like a different alarm time or because a response is received which only affects one
* instance of a series.
* Caller is responsible for ensuring changed MetaData is written through to SQL sending notification of change.
*/
private void updateLocalExceptionsWhichMatchSeriesReply(Invite reply) throws ServiceException {
if ((reply == null) || reply.getRecurId() != null) {
// Only interested in series replies
return;
}
IRecurrence replyRecurrence = reply.getRecurrence();
if (replyRecurrence == null) {
sLog.debug("Giving up on trying to match series reply to local exceptions - no recurrence in reply");
return;
}
for (int i = 0; i < numInvites(); i++) {
Invite cur = getInvite(i);
if (!cur.classPropSetByMe() || (cur.getRecurId() == null)) {
continue;
}
ParsedDateTime recurIdDT = cur.getRecurId().getDt();
ParsedDateTime startDT = cur.getStartTime();
// If the start time has moved then the series response can't be applicable.
if ((recurIdDT == null) || (startDT == null) || !recurIdDT.sameTime(startDT)) {
continue;
}
long utcTime = recurIdDT.getUtcTime();
// Find instances within 2 seconds either side of start - assuming it will be a direct hit if found
List<Instance> instances = Recurrence.expandInstances(replyRecurrence, getId(), utcTime - 2000L, utcTime + 2000L);
if (instances == null || (instances.size() != 1)) {
continue;
}
cur.updateMatchingAttendeesFromReply(reply);
}
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class CalendarItem method computeNextAlarms.
/**
* Returns the next trigger times for alarms defined on the given Invite.
* @param inv
* @param lastAlarmsAt Map key is the 0-based alarm position and value is the last trigger time
* for that alarm.
* @return NextAlarms object that tells trigger time and instance start time by alarm position.
* It will not contain alarm position if that alarm doesn't have next trigger time later
* than its last trigger time.
*/
public NextAlarms computeNextAlarms(Invite inv, Map<Integer, Long> lastAlarmsAt) throws ServiceException {
NextAlarms result = new NextAlarms();
if (inv.getRecurrence() == null) {
// non-recurring appointment or exception instance
long instStart = 0, instEnd = 0;
ParsedDateTime dtstart = inv.getStartTime();
if (dtstart != null)
instStart = dtstart.getUtcTime();
ParsedDateTime dtend = inv.getEffectiveEndTime();
if (dtend != null)
instEnd = dtend.getUtcTime();
List<Alarm> alarms = inv.getAlarms();
int index = 0;
for (Iterator<Alarm> iter = alarms.iterator(); iter.hasNext(); index++) {
Alarm alarm = iter.next();
Long lastAtLong = lastAlarmsAt.get(index);
if (lastAtLong != null) {
long lastAt = lastAtLong.longValue();
long triggerAt = alarm.getTriggerTime(instStart, instEnd);
if (lastAt < triggerAt)
result.add(index, triggerAt, instStart);
}
}
} else {
// series invite of recurring appointment
long oldest;
oldest = Long.MAX_VALUE;
for (long lastAt : lastAlarmsAt.values()) {
oldest = Math.min(oldest, lastAt);
}
long endTime = getNextAlarmRecurrenceExpansionLimit();
Collection<Instance> instances = expandInstances(oldest, endTime, false);
List<Alarm> alarms = inv.getAlarms();
int index = 0;
for (Iterator<Alarm> iter = alarms.iterator(); iter.hasNext(); index++) {
Alarm alarm = iter.next();
Long lastAtLong = lastAlarmsAt.get(index);
if (lastAtLong != null) {
long lastAt = lastAtLong.longValue();
for (Instance inst : instances) {
if (// only look at non-exception instances
inst.isException())
continue;
long instStart = inst.getStart();
if (instStart < lastAt && inst.hasStart())
continue;
long triggerAt = alarm.getTriggerTime(instStart, inst.getEnd());
if (lastAt < triggerAt) {
result.add(index, triggerAt, instStart);
// We can break now because we know alarms on later instances are even later.
break;
}
}
}
}
}
return result;
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class CalendarRequest method inviteIsAfterTime.
// Check if invite is relevant after the given time. Invite is relevant if its DTSTART
// or RECURRENCE-ID comes after the reference time. For all-day appointment, look back
// 24 hours to account for possible TZ difference.
protected static boolean inviteIsAfterTime(Invite inv, long time) {
long startUtc = 0;
ParsedDateTime dtStart = inv.getStartTime();
if (dtStart != null)
startUtc = dtStart.getUtcTime();
long ridUtc = 0;
RecurId rid = inv.getRecurId();
if (rid != null) {
ParsedDateTime ridDt = rid.getDt();
if (ridDt != null)
ridUtc = ridDt.getUtcTime();
}
long invTime = Math.max(startUtc, ridUtc);
if (inv.isAllDayEvent())
time -= MSECS_PER_DAY;
return invTime >= time;
}
Aggregations