캘린더 회의 변경사항 동기화 중

사용자는 자유롭게 Google Calendar 일정을 업데이트하거나 삭제할 수 있습니다. 사용자가 회의를 만든 후 이벤트를 업데이트하면 부가기능이 회의 데이터를 업데이트하여 변경사항에 응답해야 할 수 있습니다. 타사 회의 시스템에서 이벤트 데이터를 추적해야 하는 경우 이벤트 변경에 따라 회의를 업데이트하지 않으면 회의를 사용할 수 없게 되고 사용자 환경이 저하될 수 있습니다.

Google Calendar 일정의 변경사항으로 회의 데이터를 업데이트된 상태로 유지하는 프로세스를 동기화라고 합니다. 특정 캘린더의 이벤트가 변경될 때마다 실행되는 Apps Script 설치 가능한 트리거를 만들어 이벤트 변경사항을 동기화할 수 있습니다. 하지만 트리거는 변경된 이벤트를 보고하지 않으며, 사용자가 만든 회의가 있는 이벤트로만 이를 제한할 수 없습니다. 대신 마지막 동기화 이후 캘린더에 적용된 모든 변경사항의 목록을 요청하고 이벤트 목록을 필터링한 후 적절히 업데이트해야 합니다.

일반적인 동기화 절차는 다음과 같습니다.

  1. 사용자가 회의를 처음 만들면 동기화 프로세스가 초기화됩니다.
  2. 사용자가 캘린더 이벤트 중 하나를 만들거나 업데이트하거나 삭제할 때마다 트리거가 부가기능 프로젝트에서 트리거 함수를 실행합니다.
  3. 트리거 함수는 마지막 동기화 이후의 이벤트 변경사항 집합을 검사하여 연결된 서드 파티 회의를 업데이트해야 하는지 확인합니다.
  4. 필요한 경우 서드 파티 API 요청을 통해 회의를 업데이트합니다.
  5. 다음 트리거 실행 시 캘린더의 최근 변경사항만 검사하면 되도록 새 동기화 토큰이 저장됩니다.

동기화 초기화

부가기능이 서드 파티 시스템에서 회의를 만들면 이 캘린더의 이벤트 변경사항에 응답하는 설치 가능한 트리거를 생성해야 합니다(트리거가 아직 없는 경우).

트리거를 만든 후 초기 동기화 토큰을 만들어 초기화를 완료해야 합니다. 트리거 함수를 직접 실행하면 됩니다.

Calendar 트리거 만들기

동기화하려면 부가기능에서 회의가 연결된 캘린더 일정이 변경되는 경우를 감지해야 합니다. 이렇게 하려면 EventUpdated 설치 가능한 트리거를 만듭니다. 부가기능에는 캘린더당 하나의 트리거만 필요하며 프로그래매틱 방식으로 만들 수 있습니다.

사용자가 부가기능을 사용하기 시작하므로 사용자가 첫 회의를 만들 때 트리거를 만드는 것이 좋습니다. 회의를 생성하고 오류가 없는지 확인한 후 부가기능에서 이 사용자에게 트리거가 있는지, 트리거가 생성되지 않았는지 확인해야 합니다.

동기화 트리거 함수 구현

트리거 함수는 Apps Script에서 트리거를 실행하는 조건을 감지하면 실행됩니다. EventUpdated Calendar 트리거: 사용자가 지정된 캘린더에서 일정을 생성, 수정 또는 삭제할 때 실행됩니다.

부가기능에서 사용하는 트리거 함수를 구현해야 합니다. 이 트리거 함수는 다음을 실행해야 합니다.

  1. syncToken를 사용하여 Calendar 고급 서비스 Calendar.Events.list() 호출을 수행하여 마지막 동기화 이후 변경된 이벤트 목록을 가져옵니다. 동기화 토큰을 사용하면 부가기능에서 검사해야 하는 이벤트 수를 줄일 수 있습니다.

    트리거 함수가 유효한 동기화 토큰 없이 실행되면 전체 동기화로 백오프됩니다. 전체 동기화는 지정된 기간 내의 모든 이벤트를 검색하여 유효한 새 동기화 토큰을 생성하려고 시도합니다.

  2. 수정된 각 이벤트를 검사하여 연결된 서드 파티 회의가 있는지 확인합니다.
  3. 일정에 회의가 있는 경우 변경사항을 조사하여 일정을 확인합니다. 변경사항에 따라 관련 회의를 수정해야 할 수도 있습니다. 예를 들어 이벤트가 삭제되면 부가기능으로 인해 회의도 삭제될 수 있습니다.
  4. 회의에 필요한 변경사항은 서드 파티 시스템에 대한 API 호출을 통해 이루어집니다.
  5. 필요한 모든 사항을 변경한 후 Calendar.Events.list() 메서드에서 반환한 nextSyncToken를 저장합니다. 이 동기화 토큰은 Calendar.Events.list() 호출에서 반환된 결과의 마지막 페이지에서 찾을 수 있습니다.

Google Calendar 일정 업데이트

경우에 따라 동기화를 수행할 때 Google Calendar 일정을 업데이트해야 할 수 있습니다. 이렇게 하려면 적절한 Google Calendar 고급 서비스 요청으로 일정을 업데이트합니다. If-Match 헤더와 함께 조건부 업데이트를 사용해야 합니다. 이렇게 하면 변경사항이 다른 클라이언트의 사용자가 동시에 실행한 변경사항을 실수로 덮어쓰는 것을 방지할 수 있습니다.

다음 예는 캘린더 일정 및 관련 회의의 동기화를 설정하는 방법을 보여줍니다.

/**
 *  Initializes syncing of conference data by creating a sync trigger and
 *  sync token if either does not exist yet.
 *
 *  @param {String} calendarId The ID of the Google Calendar.
 */
function initializeSyncing(calendarId) {
  // Create a syncing trigger if it doesn't exist yet.
  createSyncTrigger(calendarId);

  // Perform an event sync to create the initial sync token.
  syncEvents({'calendarId': calendarId});
}

/**
 *  Creates a sync trigger if it does not exist yet.
 *
 *  @param {String} calendarId The ID of the Google Calendar.
 */
function createSyncTrigger(calendarId) {
  // Check to see if the trigger already exists; if does, return.
  var allTriggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < allTriggers.length; i++) {
    var trigger = allTriggers[i];
    if (trigger.getTriggerSourceId() == calendarId) {
      return;
    }
  }

  // Trigger does not exist, so create it. The trigger calls the
  // 'syncEvents()' trigger function when it fires.
  var trigger = ScriptApp.newTrigger('syncEvents')
      .forUserCalendar(calendarId)
      .onEventUpdated()
      .create();
}

/**
 *  Sync events for the given calendar; this is the syncing trigger
 *  function. If a sync token already exists, this retrieves all events
 *  that have been modified since the last sync, then checks each to see
 *  if an associated conference needs to be updated and makes any required
 *  changes. If the sync token does not exist or is invalid, this
 *  retrieves future events modified in the last 24 hours instead. In
 *  either case, a new sync token is created and stored.
 *
 *  @param {Object} e If called by a event updated trigger, this object
 *      contains the Google Calendar ID, authorization mode, and
 *      calling trigger ID. Only the calendar ID is actually used here,
 *      however.
 */
function syncEvents(e) {
  var calendarId = e.calendarId;
  var properties = PropertiesService.getUserProperties();
  var syncToken = properties.getProperty('syncToken');

  var options;
  if (syncToken) {
    // There's an existing sync token, so configure the following event
    // retrieval request to only get events that have been modified
    // since the last sync.
    options = {
      syncToken: syncToken
    };
  } else {
    // No sync token, so configure to do a 'full' sync instead. In this
    // example only recently updated events are retrieved in a full sync.
    // A larger time window can be examined during a full sync, but this
    // slows down the script execution. Consider the trade-offs while
    // designing your add-on.
    var now = new Date();
    var yesterday = new Date();
    yesterday.setDate(now.getDate() - 1);
    options = {
      timeMin: now.toISOString(),          // Events that start after now...
      updatedMin: yesterday.toISOString(), // ...and were modified recently
      maxResults: 50,   // Max. number of results per page of responses
      orderBy: 'updated'
    }
  }

  // Examine the list of updated events since last sync (or all events
  // modified after yesterday if the sync token is missing or invalid), and
  // update any associated conferences as required.
  var events;
  var pageToken;
  do {
    try {
      options.pageToken = pageToken;
      events = Calendar.Events.list(calendarId, options);
    } catch (err) {
      // Check to see if the sync token was invalidated by the server;
      // if so, perform a full sync instead.
      if (err.message ===
            "Sync token is no longer valid, a full sync is required.") {
        properties.deleteProperty('syncToken');
        syncEvents(e);
        return;
      } else {
        throw new Error(err.message);
      }
    }

    // Read through the list of returned events looking for conferences
    // to update.
    if (events.items && events.items.length > 0) {
      for (var i = 0; i < events.items.length; i++) {
         var calEvent = events.items[i];
         // Check to see if there is a record of this event has a
         // conference that needs updating.
         if (eventHasConference(calEvent)) {
           updateConference(calEvent, calEvent.conferenceData.conferenceId);
         }
      }
    }

    pageToken = events.nextPageToken;
  } while (pageToken);

  // Record the new sync token.
  if (events.nextSyncToken) {
    properties.setProperty('syncToken', events.nextSyncToken);
  }
}

/**
 *  Returns true if the specified event has an associated conference
 *  of the type managed by this add-on; retuns false otherwise.
 *
 *  @param {Object} calEvent The Google Calendar event object, as defined by
 *      the Calendar API.
 *  @return {boolean}
 */
function eventHasConference(calEvent) {
  var name = calEvent.conferenceData.conferenceSolution.name || null;

  // This version checks if the conference data solution name matches the
  // one of the solution names used by the add-on. Alternatively you could
  // check the solution's entry point URIs or other solution-specific
  // information.
  if (name) {
    if (name === "My Web Conference" ||
        name === "My Recorded Web Conference") {
      return true;
    }
  }
  return false;
}

/**
 *  Update a conference based on new Google Calendar event information.
 *  The exact implementation of this function is highly dependant on the
 *  details of the third-party conferencing system, so only a rough outline
 *  is shown here.
 *
 *  @param {Object} calEvent The Google Calendar event object, as defined by
 *      the Calendar API.
 *  @param {String} conferenceId The ID used to identify the conference on
 *      the third-party conferencing system.
 */
function updateConference(calEvent, conferenceId) {
  // Check edge case: the event was cancelled
  if (calEvent.status === 'cancelled' || eventHasConference(calEvent)) {
    // Use the third-party API to delete the conference too.


  } else {
    // Extract any necessary information from the event object, then
    // make the appropriate third-party API requests to update the
    // conference with that information.

  }
}