vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
gGroupCalendar_MessagePrefix0 = "GC2";
gGroupCalendar_MessagePrefix = gGroupCalendar_MessagePrefix0.."/";

gGroupCalendar_MessagePrefixLength = string.len(gGroupCalendar_MessagePrefix);

gGroupCalendar_Channel =
{
        Name          = nil,
        NameLower     = nil,
        ID            = nil,
        Password      = nil,
        AutoPlayer    = nil,
        Status        = "Disconnected",
        StatusMessage = nil,
        GotTooManyChannelsMessage = false,
};

gGroupCalendar_Queue =
{
        TasksDelay       = 0,
        TasksElapsed     = 0,
        Tasks            = {},
        
        RequestsDelay    = 0,
        RequestsElapsed  = 0,
        Requests         = {[1] = {}, [2] = {}},
        
        InboundDelay     = 0,
        InboundMessages  = {},
        InboundLastSender = nil,
        InboundTimeSinceLastMessage = 0,
        
        OutboundDelay    = 0,
        OutboundMessages = {},
        OutboundTimeSinceLastMessage = 0,
        
        DatabaseUpdates  = {},
        RSVPUpdates      = {},
};

gCalendarNetwork_UserTrustCache =
{
};

gCalendarNetwork_RequestDelay =
{
        Init = 120,
        ShortInit = 5,
        SelfUpdateRequest = 1,
        ExternalUpdateRequest = 30,
        OwnedNotices = 300, -- Allow time for updates to arrive before we'll start advertising databases
        JoinChannel2 = 2,
        OwnedUpdate = 3,
        ProxyUpdateMin = 6,
        ProxyUpdateRange = 4,
        
        RFUMin = 1,
        RFURange = 5,
        
        OwnedNOURange = 2,
        ProxyNOUMin = 6,
        ProxyNOURange = 4,
        
        InboundQueue = 0.2,
        OutboundQueue = 0.2,
        RequestQueue = 0.2,
        
        -- Outbound queue delay 1 - 5 seconds
        
        OutboundQueueGapMin = 1, -- Delay for after last inbound message was processed
        OutboundQueueGapWidth = 4, -- Random delay after the min time
        
        -- Request queue delay 1 - 5 seconds
        
        RequestQueueGapMin = 1,  -- Delay for after last inbound or outbound message was processed
        RequestQueueGapWidth = 4, -- Random delay after the min time
        
        GuildUpdateAutoConfig = 2,
        
        CheckDatabaseTrust = 10,
        GuildRosterUpdate = 1,
        
        -- Maximum age for an update is two minutes
        
        MaximumUpdateAge = 120,
};

gGroupCalendar_EnableUpdates = false;  -- Don't allow the user to send updates to any databases
                                       -- until we're sure that his databases are up-to-date themselves
gGroupCalendar_EnableSelfUpdates = false; -- Don't allow the user to send updates for their own databases
                                          -- until well after they're sure nobody has updates for them

function CalendarNetwork_GetChannelStatus()
        return gGroupCalendar_Channel.Status;
end

function CalendarNetwork_SetChannel(pChannel, pPassword)
        -- Leave the channel and return if the new channel is nil
        
        if not pChannel then
                CalendarNetwork_LeaveChannel();
                return true;
        end
        
        -- Just return if nothing is actually changing
        
        if gGroupCalendar_Channel.Name
        and strupper(gGroupCalendar_Channel.Name) == strupper(pChannel)
        and gGroupCalendar_Channel.Password == pPassword
        and gGroupCalendar_Channel.Status == "Connected" then
                return false; -- return false to indicate that no change was made
        end
        
        -- Leave the old channel and join the new one
        
        CalendarNetwork_LeaveChannel();
        
        return CalendarNetwork_JoinChannel(pChannel, pPassword);
end

function CalendarNetwork_JoinChannel(pChannelName, pPassword)
        if not pChannelName
        or pChannelName == ""
        or gGroupCalendar_Channel.Disconnected then
                return false;
        end
        
        -- There seems to be some sort of bug with passworded channels getting
        -- screwed up if the UI is reloaded and the solution appears to be to
        -- leave and re-join the channel.  Therefore, the code below...
        
        LeaveChannelByName(pChannelName);
        CalendarNetwork_LeftChannel();
        
        CalendarNetwork_SetChannelStatus("Initializing");
        
        CalendarNetwork_QueueTask(
                        CalendarNetwork_JoinChannel2,
                        {mChannelName = pChannelName, mPassword = pPassword},
                        gCalendarNetwork_RequestDelay.JoinChannel2,
                        "JOINCHANNEL2");
        
        return true;
end

function CalendarNetwork_JoinChannelFailed()
        if gGroupCalendar_Channel.GotTooManyChannelsMessage then
                CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cTooManyChannels);
        else
                CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cJoinChannelFailed);
        end
end

function CalendarNetwork_LeftChannel()
        gGroupCalendar_Channel.Name = nil;
        gGroupCalendar_Channel.Password = nil;
        gGroupCalendar_Channel.NameLower = nil;
        gGroupCalendar_Channel.ID = nil;
        
        CalendarNetwork_SetChannelStatus("Disconnected");
end

function CalendarNetwork_SetChannelStatus(pStatus, pStatusMessage)
        gGroupCalendar_Channel.Status = pStatus;
        gGroupCalendar_Channel.StatusMessage = pStatusMessage;
        
        GroupCalendar_ChannelChanged();
end

function CalendarNetwork_SystemMessage(pMessage)
        if pMessage == ERR_TOO_MANY_CHAT_CHANNELS then
                gGroupCalendar_Channel.GotTooManyChannelsMessage = true;
                
                if gGroupCalendar_Channel.Status == "Error" then
                        CalendarNetwork_SetChannelStatus("Error", pMessage);
                end
        elseif pMessage == INSTANCE_SAVED then
                -- Fetch the raid info so the reset events can be logged on
                -- the calendar
                
                RequestRaidInfo();
        elseif pMessage == NO_RAID_INSTANCES_SAVED then
                EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase);
        elseif pMessage == RAID_INSTANCE_INFO_HDR then
                -- Remove existing events since they will be replaced by new incoming info
                
                EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase);
        else
                local   vStartIndex, vEndIndex, vName, vID, vDays, vHours, vMinutes, vSeconds = string.find(pMessage, "(.+) %(ID=(%x+)%): (%d+)d (%d+)h (%d+)m (%d+)s");
                
                if vStartIndex then
                        -- Calculate the number of seconds until the instance resets
                        
                        local   vRemainingDateTime60 = tonumber(vDays) * gCalendarSecondsPerDay
                                             + Calendar_ConvertHMSToTime60(tonumber(vHours), tonumber(vMinutes), tonumber(vSeconds));
                        
                        local   vServerResetDate, vServerResetTime = Calendar_GetServerDateTimeFromSecondsOffset(vRemainingDateTime60);
                        
                        EventDatabase_ScheduleSavedInstanceEvent(gGroupCalendar_UserDatabase, vName, vServerResetDate, vServerResetTime);
                end
        end
end

function CalendarNetwork_JoinChannel2(pParams)
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("CalendarNetwork_JoinChannel: "..pParams.mChannelName);
        end
        
        gGroupCalendar_Channel.GotTooManyChannelsMessage = false;
        gGroupCalendar_Channel.Name = pParams.mChannelName;
        
        local   vChannelName = GetChannelName(pParams.mChannelName);
        local   vZoneChannel;
        local   vChannelAlreadyExists = false;
        
        if vChannelName and vChannelName ~= 0 then
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("Found existing channel "..pParams.mChannelName.." ("..vChannelName..")");
                end
                
                vChannelName = pParams.mChannelName;
                vChannelAlreadyExists = true;
        else
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("Channel "..pParams.mChannelName.." not found ("..vChannelName..") joining channel...");
                end
                
                vZoneChannel, vChannelName = JoinChannelByName(pParams.mChannelName, pParams.mPassword, DEFAULT_CHAT_FRAME:GetID());
                
                if not vZoneChannel then
                        CalendarNetwork_JoinChannelFailed();
                        return false;
                end
                
                if not vChannelName then
                        vChannelName = pParams.mChannelName;
                end
        end
        
        -- Remove the channel from the chat frame so the user doesn't
        -- have to watch all that data comm. (if we're running debug
        -- code then leave the setting alone)
        
        ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, pParams.mChannelName);
        
        if gCalendar_DebugFrame then
                ChatFrame_AddChannel(gCalendar_DebugFrame, pParams.mChannelName);
        end
        
        gGroupCalendar_Channel.Password = pParams.mPassword;
        gGroupCalendar_Channel.NameLower = strlower(pParams.mChannelName);
        gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name);
        
        if not gGroupCalendar_Channel.ID
        or gGroupCalendar_Channel.ID == 0 then
                gGroupCalendar_Channel.ID = nil;
                
                CalendarNetwork_JoinChannelFailed();
                
                return false;
        end
        
        if vChannelAlreadyExists then
                CalendarNetwork_JoinedChannel();
        end
        
        return true;
end

function CalendarNetwork_LeaveChannel()
        if gGroupCalendar_Channel.Name
        and gGroupCalendar_Channel.ID
        and gGroupCalendar_Channel.Status ~= "Suspended" then
                LeaveChannelByName(gGroupCalendar_Channel.Name);
                CalendarNetwork_LeftChannel();
        end
end

function CalendarNetwork_JoinedChannel()
        CalendarNetwork_SetChannelStatus("Connected");
        
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("CalendarNetwork_JoinChannel succeeded channel "..gGroupCalendar_Channel.Name.." ID "..gGroupCalendar_Channel.ID);
        end

        -- Send update requests/notices

        CalendarNetwork_QueueTask(CalendarNetwork_SendNotices, nil, gCalendarNetwork_RequestDelay.SelfUpdateRequest, "SELFUPDATE");
end

function CalendarNetwork_ChannelNotice(pChannelMessage, pChannelName, pChannelID, pActualChannelName)
        -- Decode the channel name if it's in the nn. <channel name> format
        
        local   vChannelName = pChannelName;
        
        for vFoundNumber, vFoundName in string.gfind(pChannelName, "(%d)%. (.+)") do
                vChannelName = vFoundName;
        end
        
        -- Just leave if it's not a channel we're interested in
        
        local   vIsDataChannel = strlower(vChannelName) == gGroupCalendar_Channel.NameLower;
        
        --
        
        if pChannelMessage == "YOU_JOINED" then
                -- If it's one of the system channels, then shorten the initialization
                -- timer if one is present
                
                if pChannelID == 1 then
                        CalendarNetwork_SystemChannelJoined();
                end
                
                if not vIsDataChannel then
                        return;
                end
                
                if pChannelID > 0 then
                        CalendarNetwork_JoinedChannel();
                else
                        -- Joining failed
                        
                        CalendarNetwork_JoinChannelFailed();
                end
                
        elseif pChannelMessage == "YOU_LEFT" then
                if not vIsDataChannel then
                        return;
                end
                
                if not gGroupCalendar_Channel.ID then
                        return;
                end
                
                CalendarNetwork_LeftChannel();
                
        elseif pChannelMessage == "WRONG_PASSWORD" then
                if not vIsDataChannel then
                        return;
                end
                
                CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cWrongPassword);
        end
end

function CalendarNetwork_SuspendChannel()
        if gGroupCalendar_Channel.Status == "Suspended"
        or not gGroupCalendar_Channel.Name
        or not gGroupCalendar_Channel.ID then
                return;
        end
        
        LeaveChannelByName(gGroupCalendar_Channel.Name);
        
        gGroupCalendar_Channel.ID = nil;
        CalendarNetwork_SetChannelStatus("Suspended");
end

function CalendarNetwork_ResumeChannel()
        if not gGroupCalendar_Channel.Status ~= "Suspended" then
                return;
        end
        
        JoinChannelByName(gGroupCalendar_Channel.Name, gGroupCalendar_Channel.Password, DEFAULT_CHAT_FRAME:GetID());
        gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name);
        
        ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, gGroupCalendar_Channel.Name);
        
        CalendarNetwork_SetChannelStatus("Connected");
end

function CalendarNetwork_UnpackIndexedList(...)
        local   vList = {};
        
        for vIndex = 1, arg.n, 2 do
                vList[tonumber(arg[vIndex])] = arg[vIndex + 1];
        end
        
        return vList;
end

function CalendarNetwork_GetChannelList()
        local   vChannelList = CalendarNetwork_UnpackIndexedList(GetChannelList());
        
        return vChannelList;
end

function CalendarNetwork_CalendarLoaded()
        -- Set up a deferred initialization
        
        CalendarNetwork_SetChannelStatus("Starting");
        
        -- See if there are system channels yet and use
        -- the shorter delay if there are
        
        local   vDelay = gCalendarNetwork_RequestDelay.Init;
        
        local   vChannelList = CalendarNetwork_GetChannelList();
        
        if vChannelList[1] then
                vDelay = gCalendarNetwork_RequestDelay.ShortInit;
        end
        
        --
        
        CalendarNetwork_QueueTask(CalendarNetwork_Initialize, nil, vDelay, "INIT");
end

function CalendarNetwork_SystemChannelJoined()
        CalendarNetwork_SetTaskDelay("INIT", gCalendarNetwork_RequestDelay.ShortInit);
end

function CalendarNetwork_ProcessCommandString(pSender, pTrustLevel, pCommandString, pCurrentTimeStamp)
        local   vCommand = CalendarNetwork_ParseCommandString(pCommandString);
        
        if not vCommand then
                if gGroupCalendar_Settings.Debug then
                        Calendar_DebugMessage("ProcessCommandString: Couldn't parse ["..pSender.."]:"..pCommandString);
                end
                
                return false;
        end
        
        return CalendarNetwork_ProcessCommand(pSender, pTrustLevel, vCommand, pCurrentTimeStamp);
end

function CalendarNetwork_ProcessCommand(pSender, pTrustLevel, pCommand, pCurrentTimeStamp)
        -- Age out old updates which haven't been seen in a while
        
        local   vMinimumUpdateTime = pCurrentTimeStamp - gCalendarNetwork_RequestDelay.MaximumUpdateAge;
        
        CalendarNetwork_KillOldUpdates("DB", gGroupCalendar_Queue.DatabaseUpdates, vMinimumUpdateTime);
        CalendarNetwork_KillOldUpdates("RAT", gGroupCalendar_Queue.RSVPUpdates, vMinimumUpdateTime);
        
        --
        
        local   vOpcode = pCommand[1].opcode;
        local   vOperands = pCommand[1].operands;
        
        table.remove(pCommand, 1);
        
        if vOpcode == "DB" then
                local   vUserName = vOperands[1];
                local   vDatabaseID = tonumber(vOperands[2]);
                local   vRevision = tonumber(vOperands[3]);
                local   vAuthRevision = tonumber(vOperands[4]);
                
                -- If the sender is using wildcards, fetch the path from
                -- the update records
                
                if vUserName == "*" then
                        local   vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender);
                        
                        if not vDatabaseUpdate then
                                return;
                        end
                        
                        vUserName = vDatabaseUpdate.mUserName;
                        vDatabaseID = vDatabaseUpdate.mDatabaseID;
                        vRevision = vDatabaseUpdate.mRevision;
                        vAuthRevision = nil;
                end
                
                --
                
                if not vRevision then
                        vRevision = 0;
                end
                
                if CalendarTrust_UserIsTrusted(vUserName) then -- only accept databases for trusted users (or our own)
                        CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand);
                else
                        if gGroupCalendar_Settings.DebugTrust then
                                Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for DB command");
                        end
                end
        elseif vOpcode == "RAT" then
                local   vUserName = vOperands[1];
                local   vDatabaseID = tonumber(vOperands[2]);
                local   vRevision = tonumber(vOperands[3]);
                local   vAuthRevision = tonumber(vOperands[4]);
                
                -- If the sender is using wildcards, fetch the path from
                -- the update records
                
                if vUserName == "*" then
                        local   vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender);
                        
                        if not vRSVPUpdate then
                                return;
                        end
                        
                        vUserName = vRSVPUpdate.mUserName;
                        vDatabaseID = vRSVPUpdate.mDatabaseID;
                        vRevision = vRSVPUpdate.mRevision;
                        vAuthRevision = nil;
                end
                
                --
                
                if not vRevision then
                        vRevision = 0;
                end
                
                if CalendarTrust_UserIsTrustedForRSVPs(vUserName) then -- only accept RSVP for trusted users (or our own)
                        CalendarNetwork_ProcessRSVPCommand(pSender, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand);
                else
                        if gGroupCalendar_Settings.DebugTrust then
                                Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for RAT command");
                        end
                end
        elseif vOpcode == "GLD" then
                local   vGuildName = vOperands[1];
                local   vMinRank = tonumber(vOperands[2]);
                
                CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, vGuildName, vMinRank, pCommand);
        elseif vOpcode == "ALL" then
                CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand);
        elseif vOpcode == "VER" then
                local   vDatabase = EventDatabase_GetDatabase(pSender, true);
                
                if vDatabase then
                        vDatabase.AddonVersion = vOperands[1];
                end
        else
                if gGroupCalendar_Settings.Debug then
                        Calendar_DebugMessage("ProcessCommand: Unknown opcode "..vOpcode);
                end
        end
end

function CalendarNetwork_ProcessChangesRFU(pChanges, pDatabaseTag, pPlayerOwned, pPriority, pUserName, pDatabaseID, pRevision, pAuthRevision)
        if gGroupCalendar_Settings.DebugUpdates then
                Calendar_DebugMessage(pDatabaseTag.." changes update requested for: "..pUserName..","..pRevision);
        end
        
        -- Cancel a queued RFU for the same database if it's redundant
        
        CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision)
        
        -- Just bail out if we don't have the requested database
        
        if not pChanges then
                return;
        end
        
        -- Create the request
        
        local           vRequest;
        local           vFromRevision;
        local           vForceUpdate = false;
        
        if pChanges.ID ~= pDatabaseID then
                if pPlayerOwned
                or pChanges.ID > pDatabaseID then
                        vFromRevision = 0;
                        vForceUpdate = true;
                else
                        if gGroupCalendar_Settings.DebugUpdates then
                                Calendar_DebugMessage("Not sending "..pDatabaseTag.." update: Requested database isn't available for "..pUserName..","..pDatabaseID);
                        end
                        
                        return;
                end
        else
                if pPlayerOwned
                and pAuthRevision
                and pAuthRevision < pRevision then
                        vFromRevision = pAuthRevision;
                else
                        vFromRevision = pRevision;
                end
        end
        
        vRequest = CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName);
        
        if vRequest then
                if vFromRevision < vRequest.mRevision then
                        if gGroupCalendar_Settings.DebugUpdates then
                                Calendar_DebugMessage("Changing existing "..pDatabaseTag.." request to revision "..vFromRevision.." for "..pUserName);
                        end
                        
                        vRequest.mRevision = vFromRevision;
                else
                        if gGroupCalendar_Settings.DebugUpdates then
                                Calendar_DebugMessage("Existing request for "..pDatabaseTag.." "..pUserName..","..pRevision);
                        end
                end
                
                CalendarNetwork_SetRequestPriority(vRequest, pPriority);
        elseif pChanges.Revision > vFromRevision
        or vForceUpdate then
                vRequest = {};
                
                vRequest.mOpcode = pDatabaseTag.."_UPD";
                vRequest.mUserName = pUserName;
                vRequest.mDatabaseID = pChanges.ID;
                vRequest.mRevision = vFromRevision;
                
                -- Determine a delay
                
                local   vDelay;
                
                if pPlayerOwned then
                        vDelay = gCalendarNetwork_RequestDelay.OwnedUpdate;
                else
                        vDelay = gCalendarNetwork_RequestDelay.ProxyUpdateMin + math.random() * gCalendarNetwork_RequestDelay.ProxyUpdateRange;
                end
                
                CalendarNetwork_QueueRequest(vRequest, vDelay, pPriority);
        end
end

function CalendarNetwork_GetDatabaseChanges(pUserName, pCreate)
        local   vDatabase = EventDatabase_GetDatabase(pUserName, pCreate);
        
        if vDatabase then
                return vDatabase, vDatabase.Changes, vDatabase.IsPlayerOwned;
        else
                return nil, nil, nil;
        end
end

function CalendarNetwork_GetDatabaseRSVPChanges(pUserName, pCreate)
        local   vDatabase = EventDatabase_GetDatabase(pUserName, pCreate);
        
        if vDatabase then
                return vDatabase, vDatabase.RSVPs, vDatabase.IsPlayerOwned;
        else
                return nil, nil, nil;
        end
end

function CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand)
        local   vOpcode = pCommand[1].opcode;
        local   vOperands = pCommand[1].operands;
        
        table.remove(pCommand, 1);
        
        local   vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseChanges(pUserName, false);
        local   vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName;
        
        if vOpcode ~= "RFU"
        and vOpcode ~= "RFV"
        and pTrustLevel < 2 then
                -- Players with moderate trust levels (ie, fellow guild members) are
                -- only allowed to requeust updates, not provide updates
                
                if gGroupCalendar_Settings.DebugTrust then
                        Calendar_DebugMessage("CalendarNetwork_ProcessDatabaseCommand: Ignoring "..vOpcode.." request from "..pSender);
                end
                
                return;
        end
        
        if vOpcode == "RFU" then
                -- If the sender is seen transmitting an RFU while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersDatabaseUpdates(pSender);
                
                --
                
                if vChanges then
                        -- Give the owner high-priority to help ensure that he can roam successfully
                        
                        local   vPriority;
                        
                        if pSender == pUserName then
                                vPriority = 1;
                        else
                                vPriority = 2;
                        end
                        
                        CalendarNetwork_ProcessChangesRFU(vChanges, "DB", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision);
                end
        elseif vOpcode == "RFV" then
                -- If the sender is seen transmitting an RFV while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersDatabaseUpdates(pSender);
                
                --
                
                if vIsThisPlayerOwned then
                        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
                end
        elseif vOpcode == "NOU" then
                -- If the sender is seen transmitting an NOU while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersDatabaseUpdates(pSender);
                
                --
                
                local           vCurrentRevision = 0;
                local           vDatabaseIDChanged = false;
                
                if not vIsThisPlayerOwned then
                        CalendarNetwork_CancelRedundantNOURequests(vChanges, "DB", pSender, pUserName, pDatabaseID, pRevision);
                end
                
                if not vDatabase then
                        vDatabase = EventDatabase_AssumeDatabase(pUserName);
                end
                
                if vDatabase then
                        -- Ignore external updates to our own databases
                        
                        if vDatabase.IsPlayerOwned then
                                -- If someone sent out a NOU for one of our databases and it's older than
                                -- ours and ours is now empty, send out a DEL to let them know that it's newer
                                -- and empty now
                                
                                if CalendarChanges_IsEmpty(vChanges) then
                                        CalendarNetwork_SendEmptyChanges(vChanges, "DB", pUserName);
                                end
                                
                                return;
                        end
                        
                        -- Ignore the update if the ID is older than the one we have
                        -- unless it's from the owner
                        
                        if vChanges
                        and pDatabaseID < vChanges.ID
                        and pSender ~= vDatabase.UserName then
                                return;
                        end
                        
                        -- Force a RFU if the ID is changing
                        
                        if not vChanges
                        or vChanges.ID ~= pDatabaseID then
                                vDatabaseIDChanged = true;
                        else
                                vCurrentRevision = vChanges.Revision;
                                
                                if not vCurrentRevision then
                                        Calendar_ErrorMessage("Changes for "..pUserName.." has nil revision");
                                        return;
                                end
                        end
                end
                
                if vDatabaseIDChanged
                or vCurrentRevision < pRevision then
                        CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, pRevision);
                end
        elseif vOpcode == "UPD" then
                -- If the sender is seen transmitting an UPD while he has another database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName);
                
                -- Begin a database update
                
                local   vSinceRevision = tonumber(vOperands[1]);
                
                if not vSinceRevision then
                        if gGroupCalendar_Settings.DebugErrors then
                                Calendar_DebugMessage("GroupCalendar: DB UPD received from "..pSender.." for "..pUserName.." with no SinceRevision");
                        end
                        
                        return;
                end
                
                if not vIsThisPlayerOwned then
                        -- If we're waiting to request this same update, cancel the request
                        
                        CalendarNetwork_CancelRedundantRFURequest("DB", pUserName, pDatabaseID, vSinceRevision);

                        -- If we're waiting to send this same update, cancel the request
                        
                        CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, vSinceRevision);
                end
                
                CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);

        elseif vOpcode == "DEL" then
                -- Delete the database since it's empty
                
                if not vIsThisPlayerOwned then
                        CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, 0);
                end
        
                CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision);
                
        elseif vOpcode == "EVT" then
                -- Event data
                
                local   vEventID = tonumber(vOperands[1]);
                
                CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, vEventID, pCommand);
                
        elseif vOpcode == "END" then
                -- End a database update
                
                local   vSinceRevision = tonumber(vOperands[1]);
                
                CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
        end
end

function CalendarNetwork_ProcessRSVPCommand(pSender, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand)
        local   vOpcode = pCommand[1].opcode;
        local   vOperands = pCommand[1].operands;
        local   vOperandString = pCommand[1].operandString;
        
        table.remove(pCommand, 1);
        
        local   vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseRSVPChanges(pUserName, false);
        local   vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName;
        
        if vOpcode == "RFU" then
                -- If the sender is seen transmitting an RFU while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersRSVPUpdates(pSender);
                
                --
                
                if vChanges then
                        -- Give the owner high-priority to help ensure that he can roam successfully
                        
                        local   vPriority;
                        
                        if pSender == pUserName then
                                vPriority = 1;
                        else
                                vPriority = 2;
                        end
                        
                        CalendarNetwork_ProcessChangesRFU(vChanges, "RAT", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision);
                end
        elseif vOpcode == "NOU" then
                -- If the sender is seen transmitting a NOU while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersRSVPUpdates(pSender);
                
                --
                
                local           vCurrentRevision = 0;
                local           vDatabaseIDChanged = false;
                
                if not vIsThisPlayerOwned then
                        CalendarNetwork_CancelRedundantNOURequests(vChanges, "RAT", pSender, pUserName, pDatabaseID, pRevision);
                end
                
                if vDatabase then
                        -- Ignore external updates to our own databases
                        
                        if vDatabase.IsPlayerOwned then
                                -- If someone sent out a NOU for one of our databases and it's older than
                                -- ours and ours is now empty, send out a DEL to let them know that it's newer
                                -- and empty now
                                
                                if CalendarChanges_IsEmpty(vChanges) then
                                        CalendarNetwork_SendEmptyChanges(vChanges, "RAT", pUserName);
                                end
                                
                                return;
                        end

                        -- Ignore the update if the ID is older than the one we have
                        -- unless it's from the owner
                        
                        if vChanges
                        and pDatabaseID < vChanges.ID
                        and pSender ~= vDatabase.UserName then
                                return;
                        end
                        
                        -- Purge the existing database if the ID is changing
                        
                        if not vChanges
                        or vChanges.ID ~= pDatabaseID then
                                vDatabaseIDChanged = true;
                        else
                                vCurrentRevision = vChanges.Revision;
                        end
                end
                
                if vDatabaseIDChanged
                or vCurrentRevision < pRevision then
                        CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, pRevision);
                end
        elseif vOpcode == "UPD" then
                -- If the sender is seen transmitting an UPD while he has a database update
                -- pending then it probably means he d/c'd.  Kill the database update in that
                -- case
                
                CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName);
                
                --
                
                -- Begin a RSVP database update
                
                local   vSinceRevision = tonumber(vOperands[1]);
                
                if not vSinceRevision then
                        if gGroupCalendar_Settings.DebugErrors then
                                Calendar_DebugMessage("GroupCalendar: RAT UPD received from "..pSender.." for "..pUserName.." with no SinceRevision");
                        end
                        
                        return;
                end
                
                if not vIsThisPlayerOwned then
                        -- If we're waiting to ask for an update to this database, cancel the request
                        
                        CalendarNetwork_CancelRedundantRFURequest("RAT", pUserName, pDatabaseID, vSinceRevision);
                        
                        -- If we're waiting to send this same update, cancel the request
                        
                        CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, vSinceRevision);
                end
                
                CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
                
        elseif vOpcode == "DEL" then
                -- Delete the database since it's empty
                
                if not vIsThisPlayerOwned then
                        CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, 0);
                end
                
                CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision);
                
        elseif vOpcode == "EVT" then
                -- Event data
                
                CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vOperandString);
                
        elseif vOpcode == "END" then
                -- End a RSVP update
                
                local   vSinceRevision = tonumber(vOperands[1]);
                
                CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
        end
end

function CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, pGuildName, pMinRank, pCommand)
        -- Ignore guild commands if they're not directed at the player's guild
        -- or not at the player's rank
        
        if pGuildName ~= gGroupCalendar_PlayerGuild
        or gGroupCalendar_PlayerGuildRank > pMinRank then
                return;
        end
        
        -- Pass it on to the ALL handler for further processing
        
        CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand);
end

function CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand)
        local   vOpcode = pCommand[1].opcode;
        local   vOperands = pCommand[1].operands;

        table.remove(pCommand, 1);
        
        if vOpcode == "RFU" then
                CalendarNetwork_SendAllRevisionNotices();
        elseif vOpcode == "RFV" then
                CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
        end
end

function CalendarNetwork_DatabaseIsNewer(pDatabaseID1, pRevision1, pDatabaseID2, pRevision2)
        if pDatabaseID2 > pDatabaseID1 then
                return true;
        elseif pDatabaseID2 < pDatabaseID1 then
                return false;
        else
                return pRevision2 > pRevision1;
        end
end

function CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, pDatabase, pChanges)
        -- Updates for our own databases are always worse if
        -- we're on the toon
        
        if pDatabase.IsPlayerOwned and pUserName == gGroupCalendar_PlayerName then
                return false;
        end
        
        -- Updates from the owner are always better than ours
        -- as are updates when our list is empty
        
        if string.lower(pSender) == string.lower(pUserName)
        or not pChanges then
                return true;
        end
        
        -- If the revision is higher than what we have it's better
        
        return CalendarNetwork_DatabaseIsNewer(pChanges.ID, pChanges.Revision, pDatabaseID, pRevision);
end

function CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision)
        -- Get the database
        
        local           vDatabase = EventDatabase_GetDatabase(pUserName, false);
        
        -- Nothing to do if we don't even have the database
        
        if not vDatabase then
                return;
        end
        
        -- Bail out if our stuff is better
        
        if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.Changes) then
                return;
        end
        
        -- Empty the specified changelist
        
        EventDatabase_PurgeDatabase(vDatabase, pDatabaseID);
        vDatabase.Changes.Revision = pRevision;
end

function CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
        -- If the same sender has a pending update already in progress, kill it
        
        CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName);
        
        -- Get the database
        
        local           vDatabase = EventDatabase_GetDatabase(pUserName, false);
        
        -- Ignore updates for our own databases

        if vDatabase
        and vDatabase.IsPlayerOwned
        and not gGroupCalendar_Settings.AllowSelfUpdate then
                return;
        end
        
        local   vChanges;
        
        if vDatabase then
                vChanges = vDatabase.Changes;
        else
                vChanges = nil;
        end
        
        -- Ignore the update if it isn't from the owner and
        -- our database ID is newer
        
        if string.lower(pSender) ~= string.lower(pUserName)
        and vChanges then
                if (vChanges.ID > pDatabaseID)
                or (vChanges.ID == pDatabaseID
                and vChanges.Revision >= pRevision) then
                        return;
                end
        end
        
        -- If it's from the owner and isn't the same revision as ours,
        -- then delete the database.  If the update is from revision
        -- zero go ahead with the update otherwise ignore it and request
        -- and update from zero
        
        if string.lower(pSender) == string.lower(pUserName)
        and vChanges
        and vChanges.ID ~= pDatabaseID then
                EventDatabase_PurgeDatabase(vDatabase, pDatabaseID);
                
                if pSinceRevision ~= 0 then
                        -- Request an update and exit
                        
                        CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, 0)
                        return;
                end
        end
        
        -- See if there's another update for the same database already in progress
        
        local   vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(nil, pUserName, nil);
        
        if vDatabaseUpdate then
                -- If the new update is from the owner or
                -- the old one isn't from the owner and is a higher revision,
                -- then cancel the old update
                
                if string.lower(pSender) == string.lower(pUserName)
                or (vDatabaseUpdate.mSender ~= vDatabaseUpdate.mUserName
                and CalendarNetwork_DatabaseIsNewer(vDatabaseUpdate.mDatabaseID, vDatabaseUpdate.mRevision, pDatabaseID, pRevision))  then
                        CalendarNetwork_CancelDatabaseUpdate(nil, pUserName, nil);
                
                -- Otherwise cancel this one
                
                else
                        return;
                end
        end
        
        if gGroupCalendar_Settings.DebugUpdates then
                Calendar_DebugMessage("CalendarNetwork_BeginDatabaseUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
        end

        -- Can't accept updates which don't cover the last revision we received
        
        if vChanges
        and pSinceRevision > vChanges.Revision then
                -- Ask for another copy of the update starting with the revision we need
                
                CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, vChanges.Revision)
                return;
        end
        
        -- Create the database update record
        
        vDatabaseUpdate = {};
        
        vDatabaseUpdate.mSender = pSender;
        vDatabaseUpdate.mUserName = pUserName;
        vDatabaseUpdate.mDatabaseID = pDatabaseID;
        vDatabaseUpdate.mRevision = pRevision;
        vDatabaseUpdate.mSinceRevision = pSinceRevision;
        vDatabaseUpdate.mChanges = {};
        vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
        
        table.insert(gGroupCalendar_Queue.DatabaseUpdates, vDatabaseUpdate);
end

function CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventID, pCommand)
        local   vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID);
        
        if not vDatabaseUpdate then
                return;
        end
        
        -- Bump the update time
        
        vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
        
        --
        
        local   vChanges = vDatabaseUpdate.mChanges[pRevision];
        
        if not vChanges then
                vChanges = {};
                vDatabaseUpdate.mChanges[pRevision] = vChanges;
        end
        
        -- Reconstruct the change string
        
        local   vChangeString = "EVT:"..pEventID;
        
        for vIndex, vCommand in pCommand do
                vChangeString = vChangeString.."/"..vCommand.opcode;
                
                if vCommand.operandString and vCommand.operandString ~= "" then
                        vChangeString = vChangeString..":"..vCommand.operandString;
                end
        end
        
        -- Save the string
        
        table.insert(vChanges, vChangeString);
end

function CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
        local   vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true);
        
        if not vDatabaseUpdate then
                return;
        end
        
        -- Sanity check: make sure the sinceRevision field matches the original UPD message
        
        if vDatabaseUpdate.mSinceRevision ~= pSinceRevision then
                return;
        end
        
        -- The update was received successfully.
        -- Copy the changes to the change list
        
        if gGroupCalendar_Settings.DebugUpdates then
                Calendar_DebugMessage("CalendarNetwork_EndDatabaseUpdate: Process update for "..pUserName..","..pDatabaseID..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
        end
        
        local   vDatabase = EventDatabase_GetDatabase(pUserName, true);
        local   vDatabaseChanges = vDatabase.Changes;
        local   vReconstructDatabase = false;
        
        -- If the update is for one of our own databases process it
        -- separately
        
        if vDatabase.IsPlayerOwned then
                CalendarNetwork_AskProcessSelfUpdate(vDatabaseUpdate);
                return;
        end
        
        -- Process the update
        
        CalendarNetwork_ProcessDatabaseUpdate(vDatabaseUpdate, false);
end

function CalendarNetwork_ProcessDatabaseUpdate(pDatabaseUpdate, pForceReconstruct)
        local   vIsOwnerUpdate = pDatabaseUpdate.mSender == pDatabaseUpdate.mUserName;
        local   vDatabase = EventDatabase_GetDatabase(pDatabaseUpdate.mUserName, true);
        local   vDatabaseChanges = vDatabase.Changes;
        local   vReconstructDatabase = pForceReconstruct;
        
        if not vDatabaseChanges
        or vDatabaseChanges.ID ~= pDatabaseUpdate.mDatabaseID then
                vDatabase.Changes = CalendarChanges_New(pDatabaseUpdate.mDatabaseID);
                vDatabaseChanges = vDatabase.Changes;
        end
        
        for vRevision = pDatabaseUpdate.mSinceRevision + 1, pDatabaseUpdate.mRevision do
                local   vChanges = pDatabaseUpdate.mChanges[vRevision];
                
                -- If the revision is newer than what we have, insert the new data
                
                if vRevision > vDatabaseChanges.Revision then
                        CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges);
                        
                        if vChanges then
                                EventDatabase_ExecuteChangeList(vDatabase, vChanges, true);
                        end
                
                -- If the revision overlaps what we have and the update is from
                -- the owner, then compare the data to make sure it's intact.  If
                -- it doesn't match then update the changes and flag the database
                -- for reconstruction
                
                elseif vIsOwnerUpdate then
                        -- If we're not reconstructing then compare the owner's changes to what
                        -- we've gotten before and see if they match.  Switch to reconstruction
                        -- mode if there's a discrepancy
                        
                        if not vReconstructDatabase then
                                local   vChangeList = CalendarChanges_GetChangeList(vDatabaseChanges, vRevision);
                                
                                if ((vChanges ~= nil) ~= (vChangeList ~= nil))
                                or (vChangeList ~= nil and table.getn(vChangeList) ~= table.getn(vChanges)) then
                                        vReconstructDatabase = true;
                                        
                                        if gGroupCalendar_Settings.DebugReconstruct then
                                                Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because changes for revision "..vRevision.." are different lengths");
                                        end
                                elseif vChanges ~= nil then
                                        for vChangeIndex, vChange in vChanges do
                                                local   vOldChange = vChangeList[vChangeIndex];
                                                
                                                if vOldChange ~= vChange then
                                                        vReconstructDatabase = true;
                                                        
                                                        if gGroupCalendar_Settings.DebugReconstruct then
                                                                Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because change "..vChangeIndex.." for revision "..vRevision.." doesn't match");
                                                                Calendar_DebugMessage("Previously: "..vOldChange);
                                                                Calendar_DebugMessage("Now: "..vChange);
                                                        end
                                                        break;
                                                end
                                        end
                                end
                        end
                        
                        -- Just copy the changes over if we're in re-construction mode
                        
                        if vReconstructDatabase then
                                CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges);
                        end
                end
        end
        
        -- Make sure the current revision stamp matches the update
        
        vDatabaseChanges.Revision = pDatabaseUpdate.mRevision;
        
        -- Update AuthRevision if the update came from the owner
        
        if vIsOwnerUpdate
        and pDatabaseUpdate.mSinceRevision <= vDatabaseChanges.AuthRevision then
                vDatabaseChanges.AuthRevision = pDatabaseUpdate.mRevision;
        end
        
        if vReconstructDatabase then
                EventDatabase_ReconstructDatabase(vDatabase);
        else
                GroupCalendar_MajorDatabaseChange(vDatabase);
        end
end

StaticPopupDialogs["CONFIRM_CALENDAR_SELF_UPDATE"] = {
        text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg),
        button1 = TEXT(GroupCalendar_cUpdate),
        button2 = TEXT(CANCEL),
        OnAccept = function() CalendarNetwork_ProcessSelfUpdate(); end,
        OnCancel = function() CalendarNetwork_RejectSelfUpdate(); end,
        timeout = 0,
        whileDead = 1,
        hideOnEscape = 1,
        showAlert = 1,
};

gGroupCalendar_DidAskSelfUpdate = false;
gGroupCalendar_SelfDatabaseUpdate = nil;

function CalendarNetwork_AskProcessSelfUpdate(pDatabaseUpdate)
        -- Only ask once
        
        if gGroupCalendar_DidAskSelfUpdate then
                return;
        end
        
        gGroupCalendar_DidAskSelfUpdate = true;
        
        gGroupCalendar_SelfDatabaseUpdate = pDatabaseUpdate;
        
        -- Format the message
        
        local   vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfUpdateParamFormat, pDatabaseUpdate);
        
        -- Show the dialog
        
        StaticPopup_Show("CONFIRM_CALENDAR_SELF_UPDATE", vMessage);
end

StaticPopupDialogs["CONFIRM_CALENDAR_SELF_RSVP_UPDATE"] = {
        text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg),
        button1 = TEXT(GroupCalendar_cUpdate),
        button2 = TEXT(CANCEL),
        OnAccept = function() CalendarNetwork_ProcessSelfRSVPUpdate(); end,
        OnCancel = function() CalendarNetwork_RejectSelfRSVPUpdate(); end,
        timeout = 0,
        whileDead = 1,
        hideOnEscape = 1,
        showAlert = 1,
};

gGroupCalendar_DidAskSelfRSVPUpdate = false;
gGroupCalendar_SelfRSVPUpdate = nil;

function CalendarNetwork_AskProcessSelfRSVPUpdate(pRSVPUpdate)
        -- Only ask once
        
        if gGroupCalendar_DidAskSelfRSVPUpdate then
                return;
        end
        
        gGroupCalendar_DidAskSelfRSVPUpdate = true;
        gGroupCalendar_SelfRSVPUpdate = pRSVPUpdate;
        
        -- Format the message
        
        local   vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfRSVPUpdateParamFormat, pRSVPUpdate);
        
        -- Show the dialog
        
        StaticPopup_Show("CONFIRM_CALENDAR_SELF_RSVP_UPDATE", vMessage);
end

function CalendarNetwork_ProcessSelfUpdate()
        CalendarNetwork_ProcessDatabaseUpdate(gGroupCalendar_SelfDatabaseUpdate, true);
end

function CalendarNetwork_RejectSelfUpdate()
        local   vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfDatabaseUpdate.mUserName, true);
        
        if not vDatabase then
                return;
        end
        
        EventDatabase_RebuildDatabase(vDatabase);
end

function CalendarNetwork_CancelDatabaseUpdate(pSender, pUserName, pDatabaseID)
        CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true);
end

function CalendarNetwork_FindOpenEventChangeRecord(pChanges, pEventID)
        for vIndex, vChange in pChanges do
                if vChange.mEventID == pEventID
                and vChange.mOpen then
                        return vChange;
                end
        end
        
        return nil;
end

function CalendarNetwork_KillSendersDatabaseUpdates(pSender)
        local   vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, nil, nil, true);
        
        if not vDatabaseUpdate
        or vDatabaseUpdate.mUserName == pDontRequestForUserName then
                return;
        end
        
        CalendarNetwork_RequestUpdateForUser(vDatabaseUpdate.mUserName, "DB");
end

function CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, pDelete)
        for vIndex, vDatabaseUpdate in gGroupCalendar_Queue.DatabaseUpdates do
                if (pSender == nil or vDatabaseUpdate.mSender == pSender)
                and (pUserName == nil or vDatabaseUpdate.mUserName == pUserName)
                and (pDatabaseID == nil or vDatabaseUpdate.mDatabaseID == pDatabaseID) then
                        -- Delete the update if requested
                        
                        if pDelete then
                                table.remove(gGroupCalendar_Queue.DatabaseUpdates, vIndex);
                        end
                        
                        return vDatabaseUpdate;
                end
        end
        
        return nil;
end

function CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision)
        -- Get the database
        
        local           vDatabase = EventDatabase_GetDatabase(pUserName, false);
        
        -- Nothing to do if we don't even have the database
        
        if not vDatabase then
                return;
        end
        
        -- Bail out if our stuff is better
        
        if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.RSVPs) then
                return;
        end
        
        -- Empty the specified changelist
        
        EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID);
        vDatabase.RSVPs.Revision = pRevision;
end

function CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
        -- If the same sender has a pending update already in progress, kill it
        
        CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName);
        
        -- Get the database
        
        local           vDatabase = EventDatabase_GetDatabase(pUserName, false);
        
        -- Don't listen to updates for our own databases
        
        if vDatabase
        and vDatabase.IsPlayerOwned
        and not gGroupCalendar_Settings.AllowSelfUpdate then
                return;
        end
        
        local           vChanges;
        
        if vDatabase then
                vChanges = vDatabase.RSVPs;
        else
                vChanges = nil;
        end
        
        -- Ignore the update if it isn't from the owner and
        -- our ID is newer
        
        if string.lower(pSender) ~= string.lower(pUserName)
        and vChanges then
                if (vChanges.ID > pDatabaseID)
                or (vChanges.ID == pDatabaseID
                and vChanges.Revision >= pRevision) then
                        return;
                end
        end

        -- If it's from the owner and isn't the same revision as ours,
        -- then delete the database.  If the update is from revision
        -- zero go ahead with the update otherwise ignore it and request
        -- and update from zero
        
        if string.lower(pSender) == string.lower(pUserName)
        and vChanges
        and vChanges.ID ~= pDatabaseID then
                EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID);
                
                if pSinceRevision ~= 0 then
                        -- Request an update and exit
                        
                        CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, 0)
                        return;
                end
        end
        
        -- See if there's another update for the same database already in progress
        
        local   vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(nil, pUserName, nil);
        
        if vRSVPUpdate then
                -- If the new update is from the owner or
                -- the old one isn't from the owner and is a higher revision,
                -- then cancel the old update
                
                if string.lower(pSender) == string.lower(pUserName)
                or (vRSVPUpdate.mSender ~= vRSVPUpdate.mUserName
                and CalendarNetwork_DatabaseIsNewer(vRSVPUpdate.mDatabaseID, vRSVPUpdate.mRevision, pDatabaseID, pRevision))  then
                        CalendarNetwork_CancelRSVPUpdate(nil, pUserName, nil);
                
                -- Otherwise cancel this one
                
                else
                        return;
                end
        end
        
        if gGroupCalendar_Settings.DebugUpdates then
                Calendar_DebugMessage("CalendarNetwork_BeginRSVPUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
        end

        -- Can't accept updates which don't cover the last revision we received
        
        if vChanges
        and pSinceRevision > vChanges.Revision then
                -- Ask for another copy of the update starting with the revision we need
                
                CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, vChanges.Revision);
                return;
        end
        
        -- Create the update record
        
        vRSVPUpdate = {};
        
        vRSVPUpdate.mSender = pSender;
        vRSVPUpdate.mUserName = pUserName;
        vRSVPUpdate.mDatabaseID = pDatabaseID;
        vRSVPUpdate.mRevision = pRevision;
        vRSVPUpdate.mSinceRevision = pSinceRevision;
        vRSVPUpdate.mChanges = {};
        vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
        
        table.insert(gGroupCalendar_Queue.RSVPUpdates, vRSVPUpdate);
end

function CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventFields)
        local   vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID);
        
        if not vRSVPUpdate then
                if gGroupCalendar_Settings.DebugUpdates then
                        Calendar_DebugMessage("CalendarNetwork_InsertRSVPUpdate: Ignoring update for "..pUserName..","..pDatabaseID..": Update not found");
                end
                return;
        end
        
        -- Bump the update time
        
        vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
        
        --
        
        local   vChanges = vRSVPUpdate.mChanges[pRevision];
        
        if not vChanges then
                vChanges = {};
                vRSVPUpdate.mChanges[pRevision] = vChanges;
        end
        
        -- Process the event command
        
        local   vChangeString = "EVT:"..pEventFields;
        
        table.insert(vChanges, vChangeString);
end

function CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
        local   vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true);
        
        if not vRSVPUpdate then
                return;
        end
        
        -- Sanity check: make sure the sinceRevision field matches the original UPD message
        
        if vRSVPUpdate.mSinceRevision ~= pSinceRevision then
                return;
        end
        
        -- The update was received successfully.
        -- Process the commands in the update
        
        if gGroupCalendar_Settings.DebugUpdates then
                Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Process update for "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
        end
        
        local   vDatabase = EventDatabase_GetDatabase(pUserName, true);
        local   vRSVPChanges = vDatabase.RSVPs;
        local   vReconstructRSVPs = false;
        
        -- If the update is for one of our own databases process it
        -- separately
        
        if vDatabase.IsPlayerOwned then
                CalendarNetwork_AskProcessSelfRSVPUpdate(vRSVPUpdate);
                return;
        end
        
        -- Process the update
        
        CalendarNetwork_ProcessRSVPUpdate(vRSVPUpdate, false);
end

function CalendarNetwork_ProcessRSVPUpdate(pRSVPUpdate, pForceReconstruct)
        local   vIsOwnerUpdate = pRSVPUpdate.mSender == pRSVPUpdate.mUserName;
        local   vDatabase = EventDatabase_GetDatabase(pRSVPUpdate.mUserName, true);
        local   vRSVPChanges = vDatabase.RSVPs;
        
        if not vRSVPChanges
        or vRSVPChanges.ID ~= pRSVPUpdate.mDatabaseID then
                vDatabase.RSVPs = CalendarChanges_New(pRSVPUpdate.mDatabaseID);
                vRSVPChanges = vDatabase.RSVPs;
        end
        
        for vRevision = pRSVPUpdate.mSinceRevision + 1, pRSVPUpdate.mRevision do
                local   vChanges = pRSVPUpdate.mChanges[vRevision];
                
                -- If the revision is newer than what we have, insert the new data
                
                if vRevision > vRSVPChanges.Revision then
                        CalendarChanges_SetChangeList(vRSVPChanges, vRevision, vChanges);
                        CalendarChanges_Close(vRSVPChanges, vRevision);
                        
                        if vChanges then
                                EventDatabase_ExecuteRSVPChangeList(vDatabase, vChanges, true);
                        end
                elseif vIsOwnerUpdate and vRevision > vRSVPChanges.AuthRevision then
                        if gGroupCalendar_Settings.DebugUpdates then
                                Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring owner update for "..vRevision..": Not implemented");
                        end
                else
                        if gGroupCalendar_Settings.DebugUpdates then
                                Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring revision "..vRevision..": Already exists");
                        end
                end
        end
        
        vRSVPChanges.Revision = pRSVPUpdate.mRevision;
        
        -- Update AuthRevision if the update came from the owner
        
        if vIsOwnerUpdate
        and pRSVPUpdate.mSinceRevision <= vRSVPChanges.AuthRevision then
                vRSVPChanges.AuthRevision = pRSVPUpdate.mRevision;
        end
end

function CalendarNetwork_ProcessSelfRSVPUpdate()
        CalendarNetwork_ProcessRSVPUpdate(gGroupCalendar_SelfRSVPUpdate, true);
end

function CalendarNetwork_RejectSelfRSVPUpdate()
        local   vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfRSVPUpdate.mUserName, true);
        
        if not vDatabase then
                return;
        end
        
        EventDatabase_RebuildRSVPs(vDatabase);
end

function CalendarNetwork_CancelRSVPUpdate(pSender, pUserName, pDatabaseID)
        CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true);
end

function CalendarNetwork_KillSendersRSVPUpdates(pSender, pDontRequestForUserName)
        local   vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, nil, nil, pDelete);
        
        if not vRSVPUpdate
        or vRSVPUpdate.mUserName == pDontRequestForUserName then
                return;
        end
        
        CalendarNetwork_RequestUpdateForUser(vRSVPUpdate.mUserName, "RAT");
end

function CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, pDelete)
        for vIndex, vRSVPUpdate in gGroupCalendar_Queue.RSVPUpdates do
                if (pSender == nil or vRSVPUpdate.mSender == pSender)
                and (pUserName == nil or vRSVPUpdate.mUserName == pUserName)
                and (pDatabaseID == nil or vRSVPUpdate.mDatabaseID == pDatabaseID) then
                        -- Delete the update if requested
                        
                        if pDelete then
                                table.remove(gGroupCalendar_Queue.RSVPUpdates,vIndex);
                        end
                        
                        return vRSVPUpdate;
                end
        end
        
        return nil;
end

function CalendarNetwork_KillOldUpdates(pDatabaseTag, pUpdates, pMinimumTime)
        local   vIndex = 1;
        local   vNumUpdates = table.getn(pUpdates);
        
        while vIndex <= vNumUpdates do
                local   vUpdate = pUpdates[vIndex];
                
                if vUpdate.mLastMessageTime < pMinimumTime then
                        table.remove(pUpdates, vIndex);
                        vNumUpdates = vNumUpdates - 1;
                        
                        -- Re-request the update
                        
                        CalendarNetwork_RequestUpdateForUser(vUpdate.mUserName, pDatabaseTag, false);
                else
                        vIndex = vIndex + 1;
                end
        end
end

function CalendarNetwork_ParseCommandString(pCommandString)
        
        -- Verify the command begins with the message prefix
        
        if strsub(pCommandString, 1, gGroupCalendar_MessagePrefixLength) ~= gGroupCalendar_MessagePrefix then
                return nil;
        end
        
        local   vCommandString = strsub(pCommandString, gGroupCalendar_MessagePrefixLength);
        
        return CalendarNetwork_ParseCommandSubString(vCommandString)
end

function CalendarNetwork_ParseCommandSubString(pCommandString)
        -- Break the command into parts
        
        local   vCommand = {};
        
        for vOpcode, vOperands in string.gfind(pCommandString, "/(%w+):*([^/]*)") do
                local   vOperation = {};
                
                vOperation.opcode = vOpcode;
                vOperation.operandString = vOperands;
                vOperation.operands = CalendarNetwork_ParseParameterString(vOperands);
                
                table.insert(vCommand, vOperation);
        end
        
        return vCommand;
end

function CalendarNetwork_ParseParameterString(pParameterString)
        local   vParameters = {};
        local   vIndex = 0;
        local   vFound = true;
        local   vStartIndex = 1;
        
        while vFound do
                local   vEndIndex;
                
                vFound, vEndIndex, vParameter = string.find(pParameterString, "([^,]*),", vStartIndex);
                
                vIndex = vIndex + 1;
                
                if not vFound then
                        vParameters[vIndex] = string.sub(pParameterString, vStartIndex);
                        break;
                end
                
                vParameters[vIndex] = vParameter;
                vStartIndex = vEndIndex + 1;
        end
        
        return vParameters;
end

function CalendarNetwork_NewEvent(pDatabase, pEvent)
        -- Don't record private events in the change history
        
        if pEvent.mPrivate then
                return;
        end
        
        -- Append a change record for the event
        
        local   vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
        
        EventDatabase_AppendNewEvent(vChangeList, pEvent, EventDatabase_GetEventPath(pEvent));
end

function CalendarNetwork_EventChanged(pDatabase, pEvent, pChangedFields)
        -- Don't record private events in the change history
        
        if pEvent.mPrivate then
                return;
        end
        
        -- Append a change record for the event
        
        local   vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
        
        EventDatabase_AppendEventUpdate(
                        vChangeList,
                        pEvent,
                        EventDatabase_GetEventPath(pEvent),
                        pChangedFields);
end

function CalendarNetwork_RemovingEvent(pDatabase, pEvent)
        -- Don't record private events in the change history
        
        if pEvent.mPrivate then
                return;
        end
        
        -- Remove any references to the event from the change history

        EventDatabase_RemoveEventChanges(pDatabase, pEvent);
        
        -- Insert a delete event
        
        local   vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
        
        table.insert(vChangeList, EventDatabase_GetEventPath(pEvent).."DEL");
end

function CalendarNetwork_SendRevisionChanged(pChanges, pLabel, pUserName)
        -- Just leave if there's no channel to communicate on
        -- or no changes to announce
        
        if not gGroupCalendar_Channel.ID
        or not pChanges then
                return;
        end
        
        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."NOU");
end

function CalendarNetwork_DBRevisionChanged(pDatabase)
        CalendarNetwork_SendRevisionChanged(pDatabase.Changes, "DB", pDatabase.UserName);
end

function CalendarNetwork_RSVPRevisionChanged(pDatabase)
        CalendarNetwork_SendRevisionChanged(pDatabase.RSVPs, "RAT", pDatabase.UserName);
end

function CalendarNetwork_RequestAllUpdate()
        -- Just leave if there's no channel to communicate on
        
        if not gGroupCalendar_Channel.ID then
                return;
        end
        
        -- Send the request immmediately since delaying it for channel silence will only
        -- increase the number of times that everyone has to transmit their NOU responses
        
        CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."ALL/RFU");
end

function CalendarNetwork_RequestGuildUpdate(pGuildName, pMinRank)
        -- Just leave if there's no channel to communicate on
        
        if not gGroupCalendar_Channel.ID then
                return;
        end
        
        -- Send the request immmediately since delaying it for channel silence will only
        -- increase the number of times that everyone has to transmit their NOU responses
        
        if pMinRank then
                CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName..","..pMinRank.."/RFU");
        else
                CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName.."/RFU");
        end
end

function CalendarNetwork_RequestUpdateForUser(pUserName, pDatabaseTag, pRequestImmediately)
        local   vDatabase = EventDatabase_GetDatabase(pUserName);

        if not vDatabase then
                return;
        end
        
        local   vChanges;
        
        if pDatabaseTag == "DB" then
                vChanges = vDatabase.Changes;
        elseif pDatabaseTag == "RAT" then
                vChanges = vDatabase.RSVPs;
        else
                Calendar_ErrorMessage("CalendarNetwork_RequestUpdateForUser: Unknown database tag "..pDatabaseTag);
                return;
        end
        
        if not vChanges then
                return;
        end
        
        CalendarNetwork_RequestUpdate(vDatabase, vChanges, pDatabaseTag, pRequestImmediately);
end

function CalendarNetwork_RequestUpdate(pDatabase, pChanges, pDatabaseTag, pRequestImmediately)
        -- Just leave if there's no channel to communicate on
        
        if not gGroupCalendar_Channel.ID then
                return;
        end
        
        local   vID, vRevision;
        
        if pChanges then
                vID = pChanges.ID;
                vRevision = pChanges.Revision;
        else
                vID = 0;
                vRevision = 0;
        end
        
        if pRequestImmediately then
                CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pDatabaseTag, pDatabase.UserName, vID, vRevision, 0).."RFU");
        else
                CalendarNetwork_QueueRFURequest(pDatabase.UserName, pDatabaseTag, vID, vRevision);
        end
end

function CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName)
        local   vID, vRevision;
        
        if pChanges then
                vID = pChanges.ID;
                vRevision = pChanges.Revision;
        else
                vID = Calendar_GetCurrentDateTimeUT60();
                vRevision = 0;
        end
        
        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, vID, vRevision).."DEL");
end

function CalendarNetwork_SendChanges(pChanges, pLabel, pUserName, pLockdown, pSinceRevision)
        if pLockdown then
                CalendarChanges_LockdownCurrentChangeList(pChanges);
        end
        
        if CalendarChanges_IsEmpty(pChanges) then
                CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName);
                return;
        end
        
        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."UPD:"..pSinceRevision);
        
        for vRevision = pSinceRevision + 1, pChanges.Revision do
                local   vRevisionPath = CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, vRevision);
                local   vChangeList = pChanges.ChangeList[vRevision];
                
                if vChangeList then
                        vChangeList.IsOpen = nil; -- Make sure IsOpen is cleared, a bug may have caused it to remain open
                        
                        for vIndex, vChange in vChangeList do
                                CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..vRevisionPath..vChange);
                        end
                end
        end
        
        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."END:"..pSinceRevision);
end

function CalendarNetwork_QueueOutboundMessage(pMessage)
        table.insert(gGroupCalendar_Queue.OutboundMessages, pMessage);
        
        GroupCalendar_StartUpdateTimer();
end

function CalendarNetwork_QueueInboundMessage(pSender, pTrustLevel, pMessage)
        table.insert(gGroupCalendar_Queue.InboundMessages, {mSender = pSender, mTrustLevel = pTrustLevel, mMessage = pMessage});
        
        if table.getn(gGroupCalendar_Queue.InboundMessages) == 1 then
                gGroupCalendar_Queue.InboundDelay = 0;
        end
        
        GroupCalendar_StartUpdateTimer();
end

function CalendarNetwork_QueueTask(pTaskFunc, pTaskParam, pDelay, pTaskID)
        local   vTask = {mTaskFunc = pTaskFunc, mTaskParam = pTaskParam, mDelay = pDelay, mID = pTaskID};
        
        -- Ignore tasks with duplicate IDs
        
        if pTaskID then
                for vIndex, vTask in gGroupCalendar_Queue.Tasks do
                        if vTask.mID == pTaskID then
                                return;
                        end
                end
        end
        
        -- Insert the task
        
        table.insert(gGroupCalendar_Queue.Tasks, vTask);
        
        CalendarNetwork_UpdateTaskQueueDelay();
        
        return true;
end

function CalendarNetwork_UpdateTaskQueueDelay()
        local   vDelay = nil;
        
        for vIndex, vTask in gGroupCalendar_Queue.Tasks do
                if not vDelay
                or vTask.mDelay < vDelay then
                        vDelay = vTask.mDelay;
                end
        end
        
        gGroupCalendar_Queue.TasksDelay = vDelay;
        
        if vDelay then
                GroupCalendar_StartUpdateTimer();
        end
end

function CalendarNetwork_SetTaskDelay(pTaskID, pDelay)
        -- Ignore tasks with duplicate IDs
        
        for vIndex, vTask in gGroupCalendar_Queue.Tasks do
                if vTask.mID == pTaskID then
                        vTask.mDelay = pDelay;
                        CalendarNetwork_UpdateTaskQueueDelay();
                        return;
                end
        end
end

function CalendarNetwork_QueueRequest(pRequest, pDelay, pPriority)
        pRequest.mDelay = pDelay;
        
        if gGroupCalendar_Settings.DebugQueues then
                Calendar_DebugMessage("CalendarNetwork_QueueRequest: "..pRequest.mOpcode.." in "..pDelay.." seconds");
        end
        
        if not pPriority then
                pPriority = 2;
        end
        
        table.insert(gGroupCalendar_Queue.Requests[pPriority], pRequest);
        
        local   vTotalRequests = 0;
        
        for vPriority, vRequestQueue in gGroupCalendar_Queue.Requests do
                vTotalRequests = vTotalRequests + table.getn(gGroupCalendar_Queue.Requests[vPriority]);
        end
        
        if vTotalRequests == 1
        or (pRequest.mDelay < gGroupCalendar_Queue.RequestsDelay
        and pRequest.mDelay < gCalendarNetwork_RequestDelay.RequestQueue) then
                gGroupCalendar_Queue.RequestsDelay = pRequest.mDelay;
        end
        
        GroupCalendar_StartUpdateTimer();
        
        return true;
end

function CalendarNetwork_SetRequestPriority(pRequest, pPriority)
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest == pRequest then
                                if vPriority ~= pPriority then
                                        table.remove(vRequests, vIndex);
                                        table.insert(gGroupCalendar_Queue.Requests[pPriority], vRequest);
                                end
                                return;
                        end
                end
        end
end

function CalendarNetwork_QueueUniqueOpcodeRequest(pRequest, pDelay)
        -- Remove an existing request with the same opcode
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest.mOpcode == pRequest.mOpcode then
                                table.remove(vRequests, vIndex);
                                break;
                        end
                end
        end
        
        return CalendarNetwork_QueueRequest(pRequest, pDelay);
end

function CalendarNetwork_QueueUniqueUserRequest(pRequest, pDelay)
        -- Remove an existing request with the same opcode and user name
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest.mOpcode == pRequest.mOpcode
                        and vRequest.mUserName == pRequest.mUserName then
                                table.remove(vRequests, vIndex);
                                break;
                        end
                end
        end
        
        return CalendarNetwork_QueueRequest(pRequest, pDelay);
end

function CalendarNetwork_ArrayContainsArray(pArray, pSubArray)
        for vFieldName, vFieldValue in pSubArray do
                if pArray[vFieldName] ~= vFieldValue then
                        return false;
                end
        end
        
        return true;
end

function CalendarNetwork_FindRequest(pRequest)
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if CalendarNetwork_ArrayContainsArray(vRequest, pRequest) then
                                return vRequest;
                        end
                end
        end
        
        return nil;
end

function CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName)
        return CalendarNetwork_FindRequest({mOpcode = pDatabaseTag.."_UPD", mUserName = pUserName});
end

function CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pFromRevision)
        local   vOpcode = pDatabaseTag.."_RFU";
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest.mOpcode == vOpcode
                        and vRequest.mUserName == pUserName then
                                if vRequest.mDatabaseID == pDatabaseID
                                and pFromRevision <= vRequest.mRevision then
                                        if gGroupCalendar_Settings.DebugUpdates then
                                                Calendar_DebugMessage("Removing redundant RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision);
                                        end
                                        
                                        table.remove(vRequests, vIndex);
                                        return false; -- Return false to indicate there is no longer an RFU for this user
                                else
                                        if gGroupCalendar_Settings.DebugUpdates then
                                                Calendar_DebugMessage("Keeping RFU request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision);
                                                Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision);
                                        end
                                end
                                
                                if gGroupCalendar_Settings.DebugUpdates then
                                        Calendar_DebugMessage("RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision.." already exists");
                                end
                                
                                return true; -- Return true to indicate there is an RFU for this user
                        end
                end
        end
        
        return false; -- Return false to indicate no RFU for this user was found
end

function CalendarNetwork_NeedsUpdateTimer()
        if gGroupCalendar_Queue.TasksDelay then
                return true;
        end
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                if table.getn(vRequests) > 0 then
                        return true;
                end
        end
        
        if table.getn(gGroupCalendar_Queue.InboundMessages) > 0 then
                return true;
        end

        if table.getn(gGroupCalendar_Queue.OutboundMessages) > 0 then
                return true;
        end
        
        return false;
end

function CalendarNetwork_ProcessTaskQueue(pElapsed)
        if not gGroupCalendar_Queue.TasksDelay then
                return;
        end
        
        gGroupCalendar_Queue.TasksDelay = gGroupCalendar_Queue.TasksDelay - pElapsed;
        gGroupCalendar_Queue.TasksElapsed = gGroupCalendar_Queue.TasksElapsed + pElapsed;
        
        if gGroupCalendar_Queue.TasksDelay <= 0 then
                local   vNumTasks = table.getn(gGroupCalendar_Queue.Tasks);
                local   vIndex = 1;
                
                gGroupCalendar_Queue.TasksDelay = nil;
                
                local   vLatencyStartTime;
                
                if gGroupCalendar_Settings.DebugLatency then
                        vLatencyStartTime = GetTime();
                end
                
                while vIndex <= vNumTasks do
                        local   vTask = gGroupCalendar_Queue.Tasks[vIndex];
                        
                        vTask.mDelay = vTask.mDelay - gGroupCalendar_Queue.TasksElapsed;
                        
                        if vTask.mDelay <= 0 then
                                table.remove(gGroupCalendar_Queue.Tasks, vIndex);
                                vNumTasks = vNumTasks - 1;
                                
                                -- Perform the task
                                
                                vTask.mTaskFunc(vTask.mTaskParam);

                        else
                                if not gGroupCalendar_Queue.TasksDelay
                                or vTask.mDelay < gGroupCalendar_Queue.TasksDelay then
                                        gGroupCalendar_Queue.TasksDelay = vTask.mDelay;
                                end
                                
                                vIndex = vIndex + 1;
                        end
                end
                
                if gGroupCalendar_Settings.DebugLatency then
                        local   vElapsed = GetTime() - vLatencyStartTime;
                        
                        if vElapsed > 0.1 then
                                Calendar_DebugMessage("Tasks took "..vElapsed.."s to execute ("..vNumTasks.." tasks)");
                        end
                end
                
                gGroupCalendar_Queue.TasksElapsed = 0;
        end
end

function CalendarNetwork_ProcessInboundQueue(pElapsed, pCurrentTimeStamp)
        local   vNumInboundMessages = table.getn(gGroupCalendar_Queue.InboundMessages);
        
        if vNumInboundMessages > 0 then
                local   vCollisionDetected = false;
                
                gGroupCalendar_Queue.InboundDelay = gGroupCalendar_Queue.InboundDelay - pElapsed;
                
                if gGroupCalendar_Queue.InboundDelay <= 0 then
                        -- Process one message
                        
                        local   vMessage = gGroupCalendar_Queue.InboundMessages[1];
                        
                        table.remove(gGroupCalendar_Queue.InboundMessages, 1);
                        
                        local   vLatencyStartTime;
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                vLatencyStartTime = GetTime();
                        end
                        
                        CalendarNetwork_ProcessCommandString(vMessage.mSender, vMessage.mTrustLevel, vMessage.mMessage, pCurrentTimeStamp);
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                local   vElapsed = GetTime() - vLatencyStartTime;
                                
                                if vElapsed > 0.1 then
                                        Calendar_DebugMessage("Inbound message took "..vElapsed.."s to process");
                                        Calendar_DumpArray("Message", vMessage);
                                end
                        end

                        gGroupCalendar_Queue.InboundDelay = gCalendarNetwork_RequestDelay.InboundQueue;
                        
                        if gGroupCalendar_Queue.InboundLastSender ~= vMessage.mSender then
                                if gGroupCalendar_Queue.InboundTimeSinceLastMessage < gCalendarNetwork_RequestDelay.OutboundQueueGapMin then
                                        -- Collision between other senders detected
                                        
                                        if gGroupCalendar_Settings.DebugQueues then
                                                Calendar_DebugMessage("Collision detected between "..gGroupCalendar_Queue.InboundLastSender.." and "..vMessage.mSender);
                                        end
                                        
                                        vCollisionDetected = true;
                                end
                                
                                gGroupCalendar_Queue.InboundLastSender = vMessage.mSender;
                        end
                        
                        gGroupCalendar_Queue.InboundTimeSinceLastMessage = 0;
                end
                
                -- Terminate processing while there are any pending inbound messages and delay further
                -- processing of the outbound and request queues
                
                -- If there was a collision and we *weren't* part of it then increase the range to
                -- avoid allowing us to become a part of the next collision.  This should gradually eliminate
                -- transmittors from the collisions until someone successfully takes over the wire.
                -- This probably seems really counterintuitive, but the idea is to eliminate players who
                -- didn't participate in a collision from attempting to try again.  By doing this, each collision
                -- should eliminate a significant number of players from the accident, leaving only the actual
                -- victims to try again.  The next collision will then eliminate several of those players
                -- until within a few seconds only one player remains and that player will then have control
                -- and finish his transmission.
                
                local   vOutboundQueueGapMin = gCalendarNetwork_RequestDelay.OutboundQueueGapMin;
                local   vRequestQueueGapMin = gCalendarNetwork_RequestDelay.RequestQueueGapMin;
                
                if vCollisionDetected
                and gGroupCalendar_Queue.OutboundTimeSinceLastMessage > gCalendarNetwork_RequestDelay.OutboundQueueGapMin then
                        -- Collision detected between other players so use a longer gap to make sure
                        -- we're not part of the next collision

                        vOutboundQueueGapMin = vOutboundQueueGapMin + gCalendarNetwork_RequestDelay.OutboundQueueGapWidth;
                        vRequestQueueGapMin = vRequestQueueGapMin  + gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
                end
                
                vRandom = math.random();
                
                if gGroupCalendar_Queue.OutboundDelay < vOutboundQueueGapMin then
                        gGroupCalendar_Queue.OutboundDelay = vOutboundQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.OutboundQueueGapWidth;
                end
                
                if gGroupCalendar_Queue.RequestsDelay < vRequestQueueGapMin then
                        gGroupCalendar_Queue.RequestsDelay = vRequestQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
                end
                
                return true;
        end
        
        return false;
end

function CalendarNetwork_ProcessOutboundQueue(pElapsed)
        local   vNumOutboundMessages = table.getn(gGroupCalendar_Queue.OutboundMessages);
        
        if vNumOutboundMessages > 0 then
                gGroupCalendar_Queue.OutboundDelay = gGroupCalendar_Queue.OutboundDelay - pElapsed;
                
                if gGroupCalendar_Queue.OutboundDelay <= 0 then
                        local   vLatencyStartTime;
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                vLatencyStartTime = GetTime();
                        end
                        
                        -- Send one message

                        local   vMessage = gGroupCalendar_Queue.OutboundMessages[1];
                        
                        table.remove(gGroupCalendar_Queue.OutboundMessages, 1);
                        
                        CalendarNetwork_SendMessage(vMessage);
                        
                        gGroupCalendar_Queue.OutboundDelay = gCalendarNetwork_RequestDelay.OutboundQueue;
                        
                        -- Reset the time since last message to figure out if we're part of a collision
                        
                        gGroupCalendar_Queue.OutboundTimeSinceLastMessage = 0;
                        
                        --
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                local   vElapsed = GetTime() - vLatencyStartTime;
                                
                                if vElapsed > 0.1 then
                                        Calendar_DebugMessage("Outbound message took "..vElapsed.."s to process");
                                        Calendar_DebugMessage(vMessage);
                                end
                        end
                        
                        -- Stop processing if this isn't the last outbound message, otherwise
                        -- go ahead and allow the request queue to be processed
                        
                        if vNumOutboundMessages == 1 then
                                gGroupCalendar_Queue.RequestsDelay = 0;
                        else
                                return true;
                        end
                else
                        -- Terminate processing while there are any pending outbound messages and delay further
                        -- processing of the request queues
                        
                        gGroupCalendar_Queue.RequestsDelay = gCalendarNetwork_RequestDelay.RequestQueueGapMin + math.random() * gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
                        return true;
                end
        end
        
        return false;
end

GroupCalendar_cUpdateRequestOpcodes =
{
        ["DB_UPD"] = true,
        ["RAT_UPD"] = true,
        ["DB_NOU"] = true,
        ["RAT_NOU"] = true
};

function CalendarNetwork_CanProcessRequest(pRequest)
        if not GroupCalendar_cUpdateRequestOpcodes[pRequest.mOpCode] then
                return true;
        end
        
        if not gGroupCalendar_EnableUpdates then
                return false;
        end
        
        if pRequest.mUserName then
                local   vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false);
                
                if vDatabase
                and vDatabase.IsPlayerOwned
                and not gGroupCalendar_EnableSelfUpdates then
                        return false;
                end
        end
        
        return true;    
end

function CalendarNetwork_ProcessRequestQueue(pElapsed, pSuppressProcessing)
        local   vNumRequests = 0;
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                vNumRequests = vNumRequests + table.getn(vRequests);
        end
        
        if vNumRequests > 0 then
                gGroupCalendar_Queue.RequestsDelay = gGroupCalendar_Queue.RequestsDelay - pElapsed;
                gGroupCalendar_Queue.RequestsElapsed = gGroupCalendar_Queue.RequestsElapsed + pElapsed;
                
                if gGroupCalendar_Queue.RequestsDelay <= 0 then
                        -- Process one request
                        
                        local   vDidProcessRequest = pSuppressProcessing;
                        local   vMinDelayForNextRequest = nil;
                        
                        local   vLatencyStartTime;
                        local   vTotalRequests = 0;
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                vLatencyStartTime = GetTime();
                        end
                        
                        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                                local   vIndex = 1;
                                local   vNumRequestsThisQueue = table.getn(vRequests);
                                
                                vTotalRequests = vTotalRequests + vNumRequestsThisQueue;
                                
                                while vIndex <= vNumRequestsThisQueue do
                                        local   vRequest = vRequests[vIndex];
                                        
                                        if vRequest.mDelay > 0 then
                                                vRequest.mDelay = vRequest.mDelay - gGroupCalendar_Queue.RequestsElapsed;
                                                
                                                if vRequest.mDelay < 0 then
                                                        vRequest.mDelay = 0;
                                                end
                                        end
                                        
                                        if vRequest.mDelay == 0
                                        and not vDidProcessRequest then
                                                table.remove(vRequests, vIndex);
                                                vNumRequestsThisQueue = vNumRequestsThisQueue - 1;
                                                
                                                CalendarNetwork_ProcessRequest(vRequest);
                                                
                                                vDidProcessRequest = true;
                                        else
                                                if not vMinDelayForNextRequest
                                                or vRequest.mDelay < vMinDelayForNextRequest then
                                                        vMinDelayForNextRequest = vRequest.mDelay;
                                                end
                                                
                                                vIndex = vIndex + 1;
                                        end
                                end -- while vIndex
                        end -- for vPriority
                        
                        if gGroupCalendar_Settings.DebugLatency then
                                local   vElapsed = GetTime() - vLatencyStartTime;
                                
                                if vElapsed > 0.1 then
                                        Calendar_DebugMessage("Requests took "..vElapsed.."s to process ("..vTotalRequests.." total requests)");
                                end
                        end
                        
                        if not vMinDelayForNextRequest
                        or vMinDelayForNextRequest < gCalendarNetwork_RequestDelay.RequestQueue then
                                vMinDelayForNextRequest = gCalendarNetwork_RequestDelay.RequestQueue;
                        end
                        
                        gGroupCalendar_Queue.RequestsDelay = vMinDelayForNextRequest;
                        gGroupCalendar_Queue.RequestsElapsed = 0;
                end
        end
end

function CalendarNetwork_ProcessQueues(pElapsed)
        -- Get the current time stamp
        
        local   vCurrentTimeStamp = Calendar_GetCurrentLocalDateTimeStamp();

        -- Process tasks
        
        CalendarNetwork_ProcessTaskQueue(pElapsed);
        
        -- Update the collision detection counters
        
        gGroupCalendar_Queue.InboundTimeSinceLastMessage = gGroupCalendar_Queue.InboundTimeSinceLastMessage + pElapsed;
        gGroupCalendar_Queue.OutboundTimeSinceLastMessage = gGroupCalendar_Queue.OutboundTimeSinceLastMessage + pElapsed;
        
        -- Process inbound messages
        
        local   vSuppressRequests = false;
        
        if CalendarNetwork_ProcessInboundQueue(pElapsed, vCurrentTimeStamp) then
                vSuppressRequests = true;
        
        elseif CalendarNetwork_ProcessOutboundQueue(pElapsed) then
                vSuppressRequests = true;
        end
        
        -- Process pending requests if there are no outbound messages pending
        
        CalendarNetwork_ProcessRequestQueue(pElapsed, vSuppressRequests);
end

function CalendarNetwork_ProcessRequest(pRequest)
        local   vDatabase;
        
        if pRequest.mUserName then
                vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false);
        else
                vDatabase = nil;
        end
        
        if pRequest.mOpcode == "DB_UPD" then
                if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
                        CalendarNetwork_SendChanges(vDatabase.Changes, "DB", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision);
                end

        elseif pRequest.mOpcode == "RAT_UPD" then
                if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
                        CalendarNetwork_SendChanges(vDatabase.RSVPs, "RAT", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision);
                end

        elseif pRequest.mOpcode == "DB_NOU" then
                if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
                        CalendarNetwork_DBRevisionChanged(vDatabase);
                end

        elseif pRequest.mOpcode == "RAT_NOU" then
                if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
                        CalendarNetwork_RSVPRevisionChanged(vDatabase);
                end

        elseif pRequest.mOpcode == "DB_RFU" then
                local   vCurrentRevision;
                local   vAuthRevision;
                
                if vDatabase and vDatabase.Changes then
                        vCurrentRevision = vDatabase.Changes.Revision;
                        vAuthRevision = vDatabase.Changes.AuthRevision;
                else
                        vCurrentRevision = 0;
                        vAuthRevision = nil;
                end
                
                if vCurrentRevision < pRequest.mRevision
                or vCurrentRevision == 0 then
                        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetDBRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU");
                end

        elseif pRequest.mOpcode == "RAT_RFU" then
                local   vCurrentRevision;
                local   vAuthRevision;
                
                if vDatabase and vDatabase.RSVPs then
                        vCurrentRevision = vDatabase.RSVPs.Revision;
                        vAuthRevision = vDatabase.RSVPs.AuthRevision;
                else
                        vCurrentRevision = 0;
                        vAuthRevision = nil;
                end
                
                if vCurrentRevision < pRequest.mRevision
                or vCurrentRevision == 0 then
                        CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetRSVPRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU");
                end
        
        elseif pRequest.mOpcode == "AUTOCONFIG" then
                CalendarNetwork_DoAutoConfig(pRequest.mCheckDatabaseTrust);
        
        elseif pRequest.mOpcode == "DBTRUST" then
                EventDatabase_CheckDatabaseTrust();
        
        elseif pRequest.mOpcode == "OWNEDNOTICES" then
                gGroupCalendar_EnableUpdates = true;
                gGroupCalendar_EnableSelfUpdates = true;
                CalendarNetwork_SendOwnedDatabaseUpdateNotices();
        end
end

function CalendarNetwork_Initialize()
        gGroupCalendar_Initialized = true;
        
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("GroupCalendar Initializing: Starting initialization");
        end

        CalendarNetwork_PlayerGuildChanged();

        -- Go ahead and do manual channel configuration now, automatic
        -- config will be handled once the player guild gets set and
        -- the roster gets loaded

        if not gGroupCalendar_PlayerSettings.Channel.AutoConfig then
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("GroupCalendar INIT: Starting up data channel (manual configuration)");
                end
                
                if gGroupCalendar_PlayerSettings.Channel.Name then
                        CalendarNetwork_SetChannel(
                                        gGroupCalendar_PlayerSettings.Channel.Name,
                                        gGroupCalendar_PlayerSettings.Channel.Password);
                else
                        CalendarNetwork_SetChannelStatus("Disconnected");
                        Calendar_DebugMessage("GroupCalendar channel is not set");
                end
        end

        Calendar_GetPrimaryTradeskills();
end

function CalendarNetwork_SendNotices()
        -- If trust isn't available yet just defer the notices
        
        if not CalendarTrust_TrustCheckingAvailable() then
                gGroupCalendar_SendNoticesOnRosterUpdate = true;
                return;
        end
        
        --
        
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("Sending notices");
        end

        CalendarNetwork_RequestAllDatabaseUpdates();

        -- Request databases for trusted players who haven't sent one yet

        if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                CalendarNetwork_RequestMissingDatabases();
        end

        -- Schedule a request to send out owned database notices in two minutes

        CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "OWNEDNOTICES"}, gCalendarNetwork_RequestDelay.OwnedNotices);
end

function CalendarNetwork_RequestAllDatabaseUpdates()
        -- Immediately send a notice of our version
        
        CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
        
        -- Immediately request updates to our own databases
        
        for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
                if EventDatabase_DatabaseIsVisible(vDatabase)
                and vDatabase.IsPlayerOwned then
                        CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", true);
                        CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", true);
                end
        end
        
        -- Request updates to all other databases after a delay
        
        CalendarNetwork_QueueTask(CalendarNetwork_RequestExternalDatabaseUpdates, nil, gCalendarNetwork_RequestDelay.ExternalUpdateRequest, "EXTERNALUPDATE");
end

function CalendarNetwork_RequestExternalDatabaseUpdates()
        if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                CalendarNetwork_RequestAllUpdate();

        elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies
        and gGroupCalendar_PlayerGuild then
                CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank);
        end
        
        if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
                        if EventDatabase_DatabaseIsVisible(vDatabase) then
                                if not vDatabase.IsPlayerOwned
                                and (not gGroupCalendar_PlayerSettings.Security.TrustGuildies
                                or vDatabase.Guild ~= gGroupCalendar_PlayerGuild) then
                                        CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", false);
                                        CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", false);
                                end
                        end
                end
        end
end

function CalendarNetwork_SendOwnedDatabaseUpdateNotices()
        for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
                if EventDatabase_DatabaseIsVisible(vDatabase)
                and vDatabase.IsPlayerOwned then
                        if not CalendarChanges_IsEmpty(vDatabase.Changes) then
                                CalendarNetwork_DBRevisionChanged(vDatabase);
                        end
                        
                        if not CalendarChanges_IsEmpty(vDatabase.RSVPs) then
                                CalendarNetwork_RSVPRevisionChanged(vDatabase);
                        end
                end
        end
end

gCalendarNetwork_GuildMemberRankCache = nil;

function CalendarNetwork_FlushCaches()
        gCalendarNetwork_GuildMemberRankCache = nil;
        gCalendarNetwork_UserTrustCache = {};
end

function CalendarNetwork_GetGuildRosterCache()
        if gCalendarNetwork_GuildMemberRankCache then
                return gCalendarNetwork_GuildMemberRankCache;
        end
        
        -- Clear the cache
        
        gCalendarNetwork_GuildMemberRankCache = {};
        
        -- Scan the roster and collect the info
        
        local           vNumGuildMembers = GetNumGuildMembers(true);
        
        for vIndex = 1, vNumGuildMembers do
                local   vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(vIndex);
                
                if vName then -- Have to check for name in case a guild member gets booted while querying the roster
                        local   vMemberInfo =
                        {
                                Name = vName,
                                RankIndex = vRankIndex,
                                Level = vLevel,
                                Class = vClass,
                                Zone = vZone,
                                OfficerNote = vOfficerNote,
                                Online = vOnline
                        };
                        
                        gCalendarNetwork_GuildMemberRankCache[strupper(vName)] = vMemberInfo;
                end
        end
        
        -- Dump any cached trust info
        
        gCalendarNetwork_UserTrustCache = {};
        
        return gCalendarNetwork_GuildMemberRankCache;
end

local   gGroupCalendar_SentLoadGuildRoster = false;

function CalendarNetwork_LoadGuildRosterTask()
        if IsInGuild() then
                GuildRoster();
        end
        
        -- Schedule another task to load the roster again
        -- in four minutes
        
        CalendarNetwork_QueueTask(
                        CalendarNetwork_LoadGuildRosterTask, nil,
                        240, "GUILDROSTER");
end

function CalendarNetwork_LoadGuildRoster()
        if not IsInGuild()
        or GetNumGuildMembers() > 0
        or gGroupCalendar_SentLoadGuildRoster then
                return;
        end
        
        gGroupCalendar_SentLoadGuildRoster = true;
        
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("CalendarNetwork_LoadGuildRoster: Loading");
        end
        
        CalendarNetwork_LoadGuildRosterTask();
end

function CalendarNetwork_UserIsInSameGuild(pUserName)
        if not IsInGuild() then
                return false, nil;
        end
        
        -- Build the roster
        
        if GetNumGuildMembers() == 0 then
                CalendarNetwork_LoadGuildRoster();
                return false, nil; -- have to return false for now since we don't really know
        end
        
        -- Search for the member
        
        local           vUpperUserName = strupper(pUserName);
        local           vRosterCache = CalendarNetwork_GetGuildRosterCache();
        local           vMemberInfo = vRosterCache[vUpperUserName];
        
        if not vMemberInfo then
                return false, nil;
        end
        
        return true, vMemberInfo.RankIndex;
end

function CalendarNetwork_SendAllRevisionNotices()
        -- Use the same delay for all the requests so that the requests will all go out together
        -- once they start getting processed
        
        local   vDelay = gCalendarNetwork_RequestDelay.ProxyNOUMin + math.random() * gCalendarNetwork_RequestDelay.ProxyNOURange;
        local   vOwnedDelay = math.random() * gCalendarNetwork_RequestDelay.OwnedNOURange;
        
        for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
                if EventDatabase_DatabaseIsVisible(vDatabase) then
                        local           vRequest = {mOpcode = "DB_NOU", mUserName = vDatabase.UserName};
                        
                        if vDatabase.IsPlayerOwned then
                                CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay);
                        else
                                CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay);
                        end
                        
                        local           vRequest = {mOpcode = "RAT_NOU", mUserName = vDatabase.UserName};
                        
                        if vDatabase.IsPlayerOwned then
                                CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay);
                        else
                                CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay);
                        end
                end
        end
end

function CalendarNetwork_CancelRedundantUPDRequests(pChanges, pDatabaseTag, pUserName, pDatabaseID, pRevision, pSinceRevision)
        local   vOpcode = pDatabaseTag.."_UPD";
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest.mOpcode == vOpcode
                        and vRequest.mUserName == pUserName then
                                -- Theirs is better if:
                                --   - Their database ID is higher
                                --   - Their database ID is the same and
                                --         - Their fromRevision is lower or the same
                                --         - Their toRevision is higher or the same
                                
                                if pDatabaseID > vRequest.mDatabaseID
                                or (pDatabaseID == vRequest.mDatabaseID
                                and pSinceRevision <= vRequest.mRevision
                                and (not pChanges or pRevision  >= pChanges.Revision)) then
                                        if gGroupCalendar_Settings.DebugQueues then
                                                Calendar_DebugMessage("Removing UPD request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision);
                                        end
                                        
                                        table.remove(vRequests, vIndex);
                                else
                                        if gGroupCalendar_Settings.DebugQueues then
                                                Calendar_DebugMessage("Keeping UPD request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision.." to "..pChanges.Revision);
                                                Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision);
                                        end
                                end
                                
                                return;
                        end
                end -- for vIndex
        end -- for vPriority
end

function CalendarNetwork_CancelRedundantNOURequests(pChanges, pDatabaseTag, pSender, pUserName, pDatabaseID, pRevision)
        local   vOpcode = pDatabaseTag.."_NOU";
        
        for vPriority, vRequests in gGroupCalendar_Queue.Requests do
                for vIndex, vRequest in vRequests do
                        if vRequest.mOpcode == vOpcode
                        and vRequest.mUserName == pUserName then
                                -- Cancel our update if the revision they're advertising is equal
                                -- to or better than ours or if they're the owner
                                
                                local           vTheirsIsBetter;
                                
                                if pSender == pUserName then
                                        vTheirsIsBetter = true;
                                else
                                        vTheirsIsBetter = not pChanges
                                                                                or pDatabaseID > pChanges.ID
                                                                                or (pDatabaseID == pChanges.ID and pRevision >= pChanges.Revision);
                                end
                                
                                if vTheirsIsBetter then
                                        if gGroupCalendar_Settings.DebugQueues then
                                                Calendar_DebugMessage("Removing NOU request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision);
                                        end
                                        
                                        table.remove(vRequests, vIndex);
                                else
                                        if gGroupCalendar_Settings.DebugQueues then
                                                Calendar_DebugMessage("Keeping NOU request for "..pDatabaseTag.." "..pUserName..","..pChanges.ID..","..pChanges.Revision);
                                                Calendar_DebugMessage("Better than "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision);
                                        end
                                end
                                
                                return;
                        end
                end -- for vIndex
        end -- for vPriority
end

function CalendarNetwork_SendMessage(pMessage)
        -- Just leave if there's no channel to communicate on
        
        if not gGroupCalendar_Channel.ID then
                return;
        end
        
        local   vSavedAutoClearAFK = GetCVar("autoClearAFK");
        SetCVar("autoClearAFK", 0);
        
        SendChatMessage(Calendar_EscapeChatString(pMessage), "CHANNEL", nil, gGroupCalendar_Channel.ID);
        
        SetCVar("autoClearAFK", vSavedAutoClearAFK);
end

function CalendarNetwork_ChannelMessageReceived(pSender, pMessage)
        local   vTrustLevel = CalendarTrust_GetUserTrustLevel(pSender);
        
        if vTrustLevel  > 0 then
                CalendarNetwork_QueueInboundMessage(pSender, vTrustLevel, pMessage);
        else
                if gGroupCalendar_Settings.DebugTrust then
                        Calendar_DebugMessage("ChannelMessageReceived: "..pSender.." is not trusted");
                end
        end
end

function CalendarNetwork_GetGuildMemberIndex(pPlayerName)
        local           vUpperUserName = strupper(pPlayerName);
        local           vNumGuildMembers = GetNumGuildMembers(true);
        
        for vIndex = 1, vNumGuildMembers do
                local   vName = GetGuildRosterInfo(vIndex);
                
                if strupper(vName) == vUpperUserName then
                        return vIndex;
                end
        end
        
        return nil;
end

function CalendarNetwork_SetAutoConfigData(pPlayerName)
        if not CanEditPublicNote()
        or not gGroupCalendar_PlayerSettings.Channel.Name then
                return false;
        end
        
        local           vMemberIndex = CalendarNetwork_GetGuildMemberIndex(pPlayerName);
        
        if not vMemberIndex then
                return false;
        end
        
        CalendarNetwork_RemoveAllAutoConfigData(pPlayerName);
        
        local           vNote = "["..gGroupCalendar_MessagePrefix.."C:"..gGroupCalendar_PlayerSettings.Channel.Name;
        
        if gGroupCalendar_PlayerSettings.Channel.Password then
                vNote = vNote..","..gGroupCalendar_PlayerSettings.Channel.Password
        end
        
        -- Add the trust group
        
        local           vTrustGroup = CalendarTrust_GetCurrentTrustGroup();
        
        vNote = vNote.."/T:"..vTrustGroup;
        
        -- Add the trust rank
        
        if gGroupCalendar_PlayerSettings.Security.TrustGuildies
        and gGroupCalendar_PlayerSettings.Security.MinTrustedRank then
                vNote = vNote..","..gGroupCalendar_PlayerSettings.Security.MinTrustedRank;
        end
        
        vNote = vNote.."]";
        
        if gGroupCalendar_Settings.DebugConfig then
                Calendar_DebugMessage("Setting auto-config data on player "..pPlayerName);
        end
        
        GuildRosterSetPublicNote(vMemberIndex, vNote);
        
        return true;
end

function CalendarNetwork_RemoveAllAutoConfigData(pExcludePlayerName)
        -- Remove it from the GuildInfoText
        
        local   vGuildInfoText = GetGuildInfoText();
        local   vStartIndex, vEndIndex = string.find(vGuildInfoText, "%["..gGroupCalendar_MessagePrefix.."[^%]]+%]");
        
        if vStartIndex then
                vGuildInfoText = string.sub(vGuildInfoText, 1, vStartIndex - 1)..string.sub(vGuildInfoText, vEndIndex + 1);
                SetGuildInfoText(vGuildInfoText);
        end
        
        -- Remove it from player notes
        
        local   vIndex = CalendarNetwork_FindAutoConfigData(nil, pExcludePlayerName);
        
        while vIndex do
                CalendarNetwork_RemoveAutoConfigData(vIndex);
                vIndex = CalendarNetwork_FindAutoConfigData(vIndex + 1, pExcludePlayerName);
        end
end

function CalendarNetwork_RemoveAutoConfigData(pMemberIndex)
        if not pMemberIndex then
                Calendar_DebugMessage("CalendarNetwork_RemoveAutoConfigData: nil index");
                return;
        end
        
        local   vName, vNote = CalendarNetwork_GetGuildPublicNote(pMemberIndex);
        
        if not vNote then
                return false;
        end
        
        if gGroupCalendar_Settings.DebugConfig then
                Calendar_DebugMessage("Removing auto-config data from player "..vName);
        end
        
        local   vPatternString = "%["..gGroupCalendar_MessagePrefix0.."[^%]]+%]";
        
        vNote = string.gsub(vNote, vPatternString, "");
        
        GuildRosterSetPublicNote(pMemberIndex, vNote);
        
        return true;
end

function CalendarNetwork_GetGuildPublicNote(pMemberIndex)
        local   vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(pMemberIndex);
        
        return vName, vNote;
end

function CalendarNetwork_GetAutoConfigData()
        local   vGuildInfoText = GetGuildInfoText();
        local   vConfigSource = GroupCalendar_cGuildInfoConfigSource;
        
        local   vStartIndex, vEndIndex, vConfigString = string.find(vGuildInfoText, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]");
        
        if not vStartIndex then
                local   vIndex = CalendarNetwork_FindAutoConfigData();
                
                if not vIndex then
                        return nil;
                end
                
                local   vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex);
                
                if not vNote then
                        return nil;
                end
                
                vStartIndex, vEndIndex, vConfigString = string.find(vNote, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]");
                
                if not vStartIndex then
                        return nil;
                end
                
                vConfigSource = vName;
        end
        
        return CalendarNetwork_ParseCommandString(vConfigString), vConfigSource;
end

function CalendarNetwork_FindAutoConfigData(pStartingIndex, pExcludePlayerName)
        -- Build the roster
        
        if GetNumGuildMembers() == 0 then
                CalendarNetwork_LoadGuildRoster();
                return nil;
        end
        
        -- Search for the member
        
        local           vNumGuildMembers = GetNumGuildMembers(true);
        local           vStartingIndex;
        
        if pStartingIndex then
                vStartingIndex = pStartingIndex;
        else
                vStartingIndex = 1;
        end
        
        for vIndex = vStartingIndex, vNumGuildMembers do
                local   vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex);
                
                if vNote
                and (not pExcludePlayerName or string.lower(vName) ~= string.lower(pExcludePlayerName))
                and strfind(vNote, "%["..gGroupCalendar_MessagePrefix0) then
                        if gGroupCalendar_Settings.DebugConfig then
                                Calendar_DebugMessage("Found auto-config data on player "..vName);
                        end
                        
                        return vIndex, vName;
                end
        end
        
        return nil;
end

function CalendarNetwork_ScheduleCheckDatabaseTrust(pDelay)
        CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "DBTRUST"}, pDelay);
end

function CalendarNetwork_ScheduleAutoConfig(pDelay, pCheckDatabaseTrust)
        if gGroupCalendar_Channel.Disconnected then
                return;
        end
        
        local   vRequest = CalendarNetwork_FindRequest({mOpcode = "AUTOCONFIG"});
        
        if vRequest then
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Rescheduling existing request");
                end
                
                if pDelay < vRequest.mDelay then
                        vRequest.mDelay = pDelay;
                end
                
                if pCheckDatabaseTrust then
                        vRequest.mCheckDatabaseTrust = true;
                end
        else
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Scheduling request");
                end
                
                CalendarNetwork_QueueRequest({mOpcode = "AUTOCONFIG", mCheckDatabaseTrust = pCheckDatabaseTrust}, pDelay);
        end
end

function CalendarNetwork_DoAutoConfig(pCheckDatabaseTrust)
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("CalendarNetwork_DoAutoConfig: Performing auto-config");
        end
        
        local   vCommand, vConfigSource = CalendarNetwork_GetAutoConfigData();
        
        if not vCommand then
                CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cAutoConfigNotFound);
                return false;
        end
        
        local           vChannel = nil;
        local           vPassword = nil;
        local           vMinTrustedRank = nil;
        local           vTrustGroup = nil;
        
        while vCommand[1] ~= nil do
                local   vOpcode = vCommand[1].opcode;
                local   vOperands = vCommand[1].operands;
                
                table.remove(vCommand, 1);
                
                if vOpcode == "CHN"
                or vOpcode == "C" then
                        vChannel = vOperands[1];
                        vPassword = vOperands[2];
                        
                elseif vOpcode == "RNK" then
                        vMinTrustedRank = tonumber(vOperands[1]);
                elseif vOpcode == "T" then
                        vTrustGroup = tonumber(vOperands[1]);
                        vMinTrustedRank = tonumber(vOperands[2]);
                end
        end
        
        local   vAutoPlayerChanged = gGroupCalendar_Channel.AutoPlayer ~= vConfigSource;
        
        gGroupCalendar_Channel.AutoPlayer = vConfigSource;
        
        if vAutoPlayerChanged then
                GroupCalendar_ChannelChanged(); -- Send out a change notice just so the calendar knows the player changed
        end
        
        CalendarNetwork_SetChannel(vChannel, vPassword);
        
        -- Update the trust settings
        
        local   vTrustChanged = false;
        local   vCurrentTrustGroup = CalendarTrust_GetCurrentTrustGroup();
        
        if vTrustGroup ~= nil then
                if vCurrentTrustGroup ~= vTrustGroup then
                        vTrustChanged = true;
                end
        else
                vTrustGroup = vCurrentTrustGroup;
        end
        
        if vMinTrustedRank ~= nil then
                if gGroupCalendar_PlayerSettings.Security.MinTrustedRank ~= vMinTrustedRank then
                        vTrustChanged = true;
                end
        else
                vMinTrustedRank = gGroupCalendar_PlayerSettings.Security.MinTrustedRank;
        end
        
        if vTrustChanged then
                CalendarTrust_SetCurrentTrustGroup(vTrustGroup, vMinTrustedRank);
        elseif pCheckDatabaseTrust then
                EventDatabase_CheckDatabaseTrust();
        end
        
        return true;
end

function CalendarTrust_GetCurrentTrustGroup()
        if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                return 2;
        elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies then
                return 1;
        else
                return 0;
        end
end

function CalendarTrust_SetCurrentTrustGroup(pTrustGroup, pMinRank)
        if pTrustGroup == 2 then
                gGroupCalendar_PlayerSettings.Security.TrustAnyone = true;
                gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
        elseif pTrustGroup == 1 then
                gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
                gGroupCalendar_PlayerSettings.Security.TrustGuildies = true;
        else
                gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
                gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
        end
        
        gGroupCalendar_PlayerSettings.Security.MinTrustedRank =  pMinRank;
        
        CalendarTrust_TrustSettingsChanged();
end

function CalendarTrust_TrustSettingsChanged()
        CalendarNetwork_FlushCaches();
        
        EventDatabase_CheckDatabaseTrust(); -- Delete databases owned by players we no longer trust
        CalendarNetwork_RequestMissingDatabases(); -- Request databases for newly trusted players
        
        if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                CalendarNetwork_RequestAllUpdate();
        else
                if gGroupCalendar_PlayerSettings.Security.TrustGuildies
                and gGroupCalendar_PlayerGuild then
                        CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank);
                end
        end
        
        CalendarNetwork_SendAllRevisionNotices(); -- Send out revision notices since trusted players may want to know now
end

function CalendarTrust_TrustCheckingAvailable()
        if not gGroupCalendar_PlayerSettings.Security.TrustGuildies then
                return true;
        end
        
        -- Doesn't matter if they're not in a guild
        
        if not IsInGuild() then
                return true;
        end

        -- If trust is guild members only then verify that the roster has been loaded
        
        return GetNumGuildMembers() > 0;
end

function CalendarTrust_GetUserTrustLevel(pUserName)
        local   vUserTrustInfo = gCalendarNetwork_UserTrustCache[pUserName];
        
        if not vUserTrustInfo then
                vUserTrustInfo = {};
                vUserTrustInfo.mTrustLevel = CalendarTrust_CalcUserTrust(pUserName);
                gCalendarNetwork_UserTrustCache[pUserName] = vUserTrustInfo;
        end
        
        return vUserTrustInfo.mTrustLevel;
end

function CalendarTrust_UserIsTrusted(pUserName)
        return CalendarTrust_GetUserTrustLevel(pUserName) == 2;
end

function CalendarTrust_UserIsTrustedForRSVPs(pUserName)
        return CalendarTrust_GetUserTrustLevel(pUserName) >= 1;
end

function CalendarTrust_CalcUserTrust(pUserName)
        -- If the user is one of our own characters, then trust them completely
        
        local   vDatabase = EventDatabase_GetDatabase(pUserName, false);
        
        if vDatabase
        and vDatabase.IsPlayerOwned then
                if gGroupCalendar_Settings.DebugTrust then
                        Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Implicit trust for "..pUserName);
                end
                
                return 2;
        end

        local   vPlayerSecurity = gGroupCalendar_PlayerSettings.Security.Player[pUserName];
        
        -- See if they're explicity allowed/forbidden
        
        if vPlayerSecurity ~= nil then
                if vPlayerSecurity == 1 then
                        -- Trusted
                        
                        if gGroupCalendar_Settings.DebugTrust then
                                Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Explicit trust for "..pUserName);
                        end
                        
                        return 2;
                elseif vPlayerSecurity == 2 then
                        -- Excluded
                        
                        if gGroupCalendar_Settings.DebugTrust then
                                Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." explicity excluded");
                        end
                        
                        return 0;
                else
                        Calendar_DebugMessage("GroupCalendar: Unknown player security setting of "..vPlayerSecurity.." for "..pUserName);
                end
        end
        
        -- Return true if we'll allow anyone in the channel
        
        if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
                if gGroupCalendar_Settings.DebugTrust then
                        Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (all trusted)");
                end
                
                return 2;
        end
        
        -- Return true if they're in the same guild and of sufficient rank
        
        if gGroupCalendar_PlayerSettings.Security.TrustGuildies then
                local   vIsInGuild, vGuildRank = CalendarNetwork_UserIsInSameGuild(pUserName);
                
                if vIsInGuild then
                        if not gGroupCalendar_PlayerSettings.Security.MinTrustedRank
                        or vGuildRank <= gGroupCalendar_PlayerSettings.Security.MinTrustedRank then
                                if gGroupCalendar_Settings.DebugTrust then
                                        Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (guild member)");
                                end
                                
                                return 2;
                        else
                                if gGroupCalendar_Settings.DebugTrust then
                                        Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." partially trusted (guild member)");
                                end
                                
                                return 1;
                        end
                end
        end
        
        -- Failed all tests
                
        if gGroupCalendar_Settings.DebugTrust then
                Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." not trusted (all tests failed)");
        end
        
        return 0;
end

function CalendarTrust_GetNumTrustedPlayers(pTrustSetting)
        local   vNumPlayers = 0;
        
        for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
                if vPlayerSecurity == pTrustSetting then
                        vNumPlayers = vNumPlayers + 1;
                end
        end
        
        return vNumPlayers;
end

function CalendarTrust_GetIndexedTrustedPlayers(pTrustSetting, pIndex)
        local   vPlayerIndex = 1;
        
        for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
                if vPlayerSecurity == pTrustSetting then
                        if vPlayerIndex == pIndex then
                                return vPlayerName;
                        end
                        
                        vPlayerIndex = vPlayerIndex + 1;
                end
        end
        
        return nil;
end

function CalendarNetwork_RequestMissingDatabases()
        -- For each player we explicitly trust, see if there's a database for
        -- them yet.  If not, request one
        
        for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
                if vPlayerSecurity == 1 then
                        -- Found a trusted player, see if they have a database
                        
                        local   vDatabase = EventDatabase_GetDatabase(vPlayerName, false);
                        
                        if not vDatabase then
                                CalendarNetwork_QueueRFURequest(vPlayerName, "DB", 0, 0);
                                CalendarNetwork_QueueRFURequest(vPlayerName, "RAT", 0, 0);
                        end
                end
        end
        
        return nil;
end

function CalendarNetwork_QueueRFURequest(pUserName, pDatabaseTag, pDatabaseID, pRevision)
        if CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision) then
                return;
        end
        
        local   vRequest =
        {
                mOpcode = pDatabaseTag.."_RFU",
                mUserName = pUserName,
                mDatabaseID = pDatabaseID,
                mRevision = pRevision,
        }
        
        CalendarNetwork_QueueRequest(vRequest, gCalendarNetwork_RequestDelay.RFUMin + math.random() * gCalendarNetwork_RequestDelay.RFURange);
end

function CalendarNetwork_PlayerGuildChanged()
        -- Update the guild in the database
        
        if gGroupCalendar_UserDatabase then
                gGroupCalendar_UserDatabase.Guild = gGroupCalendar_PlayerGuild;
        end
        
        -- Just return if we're not initialized yet
        
        if not gGroupCalendar_Initialized then
                EventDatabase_UpdateGuildRankCache();
                return;
        end
        
        -- Clear the roster load flag
        
        gGroupCalendar_SentLoadGuildRoster = false;
        
        CalendarNetwork_FlushCaches();
        
        -- If the player is unguilded then simply leave the data
        -- channel if it was auto-configured, flush any databases
        -- which are no longer trusted and exit
        
        if not IsInGuild() then
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("PlayerGuildChanged: Player is now unguilded");
                end
                
                if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
                        CalendarNetwork_LeaveChannel();
                end
                
                EventDatabase_CheckDatabaseTrust();
                
                return;
        end
        
        -- The player is in a new guild or has changed guilds, so
        -- schedule a roster update if necessary
        
        if GetNumGuildMembers() > 0 then
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("PlayerGuildChanged: Roster is already loaded, calling GuildRosterChanged()");
                end
                
                CalendarNetwork_GuildRosterChanged();
        end
        
        -- Force the roster to reload or to start loading
        
        CalendarNetwork_LoadGuildRosterTask();
end

function CalendarNetwork_GuildRosterChanged()
        if gGroupCalendar_Settings.DebugInit then
                Calendar_DebugMessage("CalendarNetwork_GuildRosterChanged: Checking changes");
        end
        
        EventDatabase_UpdateGuildRankCache();
        
        CalendarNetwork_FlushCaches();

        if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
                CalendarNetwork_ScheduleAutoConfig(gCalendarNetwork_RequestDelay.GuildUpdateAutoConfig, true);
        else
                if gGroupCalendar_Settings.DebugInit then
                        Calendar_DebugMessage("PlayerGuildChanged: Channel is set for manual config, verifying database trust");
                end
                
                CalendarNetwork_ScheduleCheckDatabaseTrust(5);
        end
        
        -- Start sending notices now if we were waiting for a roster update
        
        if gGroupCalendar_SendNoticesOnRosterUpdate then
                gGroupCalendar_SendNoticesOnRosterUpdate = nil;
                CalendarNetwork_SendNotices();
        end
end

function CalendarNetwork_CheckPlayerGuild()
        local   vPlayerGuild;

        if IsInGuild() then
                vPlayerGuild, _, gGroupCalendar_PlayerGuildRank = GetGuildInfo("player");
                
                -- Just return if the server is lagging and the guild info
                -- isn't available yet
                
                if not vPlayerGuild then
                        return;
                end
        else
                vPlayerGuild = nil;
                gGroupCalendar_PlayerGuildRank = nil;
        end

        if gGroupCalendar_PlayerGuild ~= vPlayerGuild then
                gGroupCalendar_PlayerGuild = vPlayerGuild;
                
                CalendarNetwork_PlayerGuildChanged();
        end
end