<commit_before>/*
 * Copyright (C) 2013 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.simonvt.cathode.service;

import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.text.format.Time;
import android.util.LongSparseArray;
import java.util.ArrayList;
import java.util.List;
import net.simonvt.cathode.R;
import net.simonvt.cathode.provider.DatabaseContract.EpisodeColumns;
import net.simonvt.cathode.provider.DatabaseContract.ShowColumns;
import net.simonvt.cathode.provider.ProviderSchematic.Episodes;
import net.simonvt.cathode.provider.ProviderSchematic.Shows;
import net.simonvt.cathode.settings.Permissions;
import net.simonvt.cathode.settings.Settings;
import net.simonvt.cathode.util.DataHelper;
import net.simonvt.cathode.util.DateUtils;
import net.simonvt.schematic.Cursors;
import timber.log.Timber;

public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {

  private static class Event {

    long id;

    long start;

    long end;

    long episodeId;

    public Event(Cursor c) {
      id = Cursors.getLong(c, CalendarContract.Events._ID);
      start = Cursors.getLong(c, CalendarContract.Events.DTSTART);
      end = Cursors.getLong(c, CalendarContract.Events.DTEND);
      episodeId = Cursors.getLong(c, CalendarContract.Events.SYNC_DATA1);
    }
  }

  private static final long INVALID_ID = -1L;

  private Context context;

  public CalendarSyncAdapter(Context context) {
    super(context, true);
    this.context = context;
  }

  @Override public void onPerformSync(Account account, Bundle extras, String authority,
      ContentProviderClient provider, SyncResult syncResult) {
    Timber.d("onPerformSync");

    if (!Permissions.hasCalendarPermission(context)) {
      Timber.d("Calendar permission not granted");
      return;
    }

    final long calendarId = getCalendar(account);
    if (calendarId == INVALID_ID) {
      return;
    }

    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
    final boolean syncCalendar = settings.getBoolean(Settings.CALENDAR_SYNC, false);
    if (!syncCalendar) {
      Timber.d("Deleting calendar");
      //noinspection MissingPermission
      context.getContentResolver()
          .delete(CalendarContract.Events.CONTENT_URI, CalendarContract.Events.CALENDAR_ID + "=?",
              new String[] {
                  String.valueOf(calendarId),
              });

      Uri calenderUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
          .build();
      context.getContentResolver().delete(calenderUri, null, null);
      return;
    }

    //noinspection MissingPermission
    Cursor c =
        context.getContentResolver().query(CalendarContract.Events.CONTENT_URI, new String[] {
            CalendarContract.Events._ID, CalendarContract.Events.DTSTART,
            CalendarContract.Events.DTEND, CalendarContract.Events.SYNC_DATA1,
        }, CalendarContract.Events.CALENDAR_ID + "=?", new String[] {
            String.valueOf(calendarId),
        }, null);

    if (c == null) {
      return;
    }

    LongSparseArray<Event> events = new LongSparseArray<>();
    while (c.moveToNext()) {
      events.put(Cursors.getLong(c, CalendarContract.Events.SYNC_DATA1), new Event(c));
    }
    c.close();

    final long time = System.currentTimeMillis();

    ArrayList<ContentProviderOperation> ops = new ArrayList<>();

    Cursor shows = context.getContentResolver().query(Shows.SHOWS, new String[] {
        ShowColumns.ID, ShowColumns.TITLE, ShowColumns.RUNTIME,
    }, "("
        + ShowColumns.WATCHED_COUNT
        + ">0 OR "
        + ShowColumns.IN_WATCHLIST_COUNT
        + ">0 OR "
        + ShowColumns.IN_COLLECTION_COUNT
        + ">0"
        + ") AND "
        + ShowColumns.HIDDEN_CALENDAR
        + "=0"
        + " AND "
        + ShowColumns.NEEDS_SYNC
        + "=0", null, null);

    while (shows.moveToNext()) {
      final long showId = Cursors.getLong(shows, ShowColumns.ID);
      final String showTitle = Cursors.getString(shows, ShowColumns.TITLE);
      final long runtime = Cursors.getLong(shows, ShowColumns.RUNTIME);

      Cursor episodes = context.getContentResolver().query(Episodes.fromShow(showId), new String[] {
          EpisodeColumns.ID, EpisodeColumns.TITLE, EpisodeColumns.SEASON, EpisodeColumns.EPISODE,
          EpisodeColumns.FIRST_AIRED,
      }, EpisodeColumns.FIRST_AIRED + ">? AND " + EpisodeColumns.NEEDS_SYNC + "=0", new String[] {
          String.valueOf(time - 30 * DateUtils.DAY_IN_MILLIS),
      }, null);

      while (episodes.moveToNext()) {
        final long episodeId = Cursors.getLong(episodes, EpisodeColumns.ID);
        final int season = Cursors.getInt(episodes, EpisodeColumns.SEASON);
        final int episode = Cursors.getInt(episodes, EpisodeColumns.EPISODE);
        final String title = DataHelper.getEpisodeTitle(context, episodes, season, episode);
        final long firstAired = Cursors.getLong(episodes, EpisodeColumns.FIRST_AIRED);

        String eventTitle = showTitle + " - " + season + "x" + episode + " " + title;

        addEvent(ops, events, calendarId, episodeId, eventTitle, firstAired, runtime);
      }
      episodes.close();
    }
    shows.close();

    Cursor watchlistShows = context.getContentResolver().query(Shows.SHOWS_WATCHLIST, new String[] {
        ShowColumns.ID, ShowColumns.TITLE, ShowColumns.RUNTIME,
    }, null, null, null);

    while (watchlistShows.moveToNext()) {
      final long showId = Cursors.getLong(watchlistShows, ShowColumns.ID);
      final String showTitle = Cursors.getString(watchlistShows, ShowColumns.TITLE);
      final long runtime = Cursors.getLong(watchlistShows, ShowColumns.RUNTIME);

      Cursor episodes = context.getContentResolver().query(Episodes.fromShow(showId), new String[] {
          EpisodeColumns.ID, EpisodeColumns.TITLE, EpisodeColumns.SEASON, EpisodeColumns.EPISODE,
          EpisodeColumns.FIRST_AIRED,
      }, EpisodeColumns.FIRST_AIRED
          + ">? AND "
          + EpisodeColumns.EPISODE
          + "=1 AND "
          + EpisodeColumns.SEASON
          + ">0", new String[] {
          String.valueOf(time - 30 * DateUtils.DAY_IN_MILLIS),
      }, EpisodeColumns.SEASON + " ASC LIMIT 1");

      if (episodes.moveToFirst()) {
        final long episodeId = Cursors.getLong(episodes, EpisodeColumns.ID);
        final int season = Cursors.getInt(episodes, EpisodeColumns.SEASON);
        final int episode = Cursors.getInt(episodes, EpisodeColumns.EPISODE);
        final String title = DataHelper.getEpisodeTitle(context, episodes, season, episode);
        final long firstAired = Cursors.getLong(episodes, EpisodeColumns.FIRST_AIRED);

        String eventTitle = showTitle + " - " + season + "x" + episode + " " + title;

        addEvent(ops, events, calendarId, episodeId, eventTitle, firstAired, runtime);
      }

      episodes.close();
    }

    watchlistShows.close();

    for (int i = 0, size = events.size(); i < size; i++) {
      Event event = events.valueAt(i);
      ContentProviderOperation op =
          ContentProviderOperation.newDelete(CalendarContract.Events.CONTENT_URI)
              .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                  String.valueOf(event.id),
              })
              .build();
      ops.add(op);
    }

    try {
      context.getContentResolver().applyBatch(CalendarContract.AUTHORITY, ops);
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (OperationApplicationException e) {
      e.printStackTrace();
    }
  }

  private void addEvent(List<ContentProviderOperation> ops, LongSparseArray<Event> events,
      long calendarId, long episodeId, String eventTitle, long firstAired, long runtime) {
    Event event = events.get(episodeId);
    if (event != null) {
      ContentProviderOperation op =
          ContentProviderOperation.newUpdate(CalendarContract.Events.CONTENT_URI)
              .withValue(CalendarContract.Events.DTSTART, firstAired)
              .withValue(CalendarContract.Events.DTEND,
                  firstAired + runtime * DateUtils.MINUTE_IN_MILLIS)
              .withValue(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC)
              .withValue(CalendarContract.Events.TITLE, eventTitle)
              .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                  String.valueOf(event.id),
              })
              .build();
      ops.add(op);
      events.remove(episodeId);
    } else {
      ContentProviderOperation op = ContentProviderOperation.newInsert(
          CalendarContract.Events.CONTENT_URI.buildUpon()
              .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
              .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME,
                  context.getString(R.string.accountName))
              .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE,
                  context.getString(R.string.accountType))
              .build())
          .withValue(CalendarContract.Events.DTSTART, firstAired)
          .withValue(CalendarContract.Events.DTEND,
              firstAired + runtime * DateUtils.MINUTE_IN_MILLIS)
          .withValue(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC)
          .withValue(CalendarContract.Events.TITLE, eventTitle)
          .withValue(CalendarContract.Events.SYNC_DATA1, episodeId)
          .withValue(CalendarContract.Events.CALENDAR_ID, calendarId)
          .build();
      ops.add(op);
    }
  }

  private long getCalendar(Account account) {
    return getCalendar(account, true);
  }

  private long getCalendar(Account account, boolean recall) {
    Cursor c = null;
    try {
      Uri calenderUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
          .build();
      c = context.getContentResolver().query(calenderUri, new String[] {
          BaseColumns._ID,
      }, null, null, null);

      if (c == null) {
        return INVALID_ID;
      }

      if (c.moveToNext()) {
        return c.getLong(0);
      } else {
        if (!recall) {
          return INVALID_ID;
        }

        ArrayList<ContentProviderOperation> operationList = new ArrayList<>();

        ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
            CalendarContract.Calendars.CONTENT_URI.buildUpon()
                .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
                .build());
        builder.withValue(CalendarContract.Calendars.ACCOUNT_NAME, account.name);
        builder.withValue(CalendarContract.Calendars.ACCOUNT_TYPE, account.type);
        builder.withValue(CalendarContract.Calendars.NAME, context.getString(R.string.app_name));
        builder.withValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
            context.getString(R.string.calendarName));
        builder.withValue(CalendarContract.Calendars.CALENDAR_COLOR, 0xD51007);
        builder.withValue(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL,
            CalendarContract.Calendars.CAL_ACCESS_READ);
        builder.withValue(CalendarContract.Calendars.OWNER_ACCOUNT, account.name);
        builder.withValue(CalendarContract.Calendars.SYNC_EVENTS, 1);
        operationList.add(builder.build());
        try {
          context.getContentResolver().applyBatch(CalendarContract.AUTHORITY, operationList);
        } catch (Exception e) {
          e.printStackTrace();
          return INVALID_ID;
        }
        return getCalendar(account, false);
      }
    } finally {
      if (c != null) c.close();
    }
  }
}
<commit_msg>Ensure no duplicate episodes or episodes with no id in calendar.<commit_after>/*
 * Copyright (C) 2013 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.simonvt.cathode.service;

import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.text.format.Time;
import android.util.LongSparseArray;
import java.util.ArrayList;
import java.util.List;
import net.simonvt.cathode.R;
import net.simonvt.cathode.provider.DatabaseContract.EpisodeColumns;
import net.simonvt.cathode.provider.DatabaseContract.ShowColumns;
import net.simonvt.cathode.provider.ProviderSchematic.Episodes;
import net.simonvt.cathode.provider.ProviderSchematic.Shows;
import net.simonvt.cathode.settings.Permissions;
import net.simonvt.cathode.settings.Settings;
import net.simonvt.cathode.util.DataHelper;
import net.simonvt.cathode.util.DateUtils;
import net.simonvt.schematic.Cursors;
import timber.log.Timber;

public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {

  private static class Event {

    long id;

    long start;

    long end;

    long episodeId;

    public Event(Cursor c) {
      id = Cursors.getLong(c, CalendarContract.Events._ID);
      start = Cursors.getLong(c, CalendarContract.Events.DTSTART);
      end = Cursors.getLong(c, CalendarContract.Events.DTEND);
      episodeId = Cursors.getLong(c, CalendarContract.Events.SYNC_DATA1);
    }
  }

  private static final long INVALID_ID = -1L;

  private Context context;

  public CalendarSyncAdapter(Context context) {
    super(context, true);
    this.context = context;
  }

  @Override public void onPerformSync(Account account, Bundle extras, String authority,
      ContentProviderClient provider, SyncResult syncResult) {
    Timber.d("onPerformSync");

    if (!Permissions.hasCalendarPermission(context)) {
      Timber.d("Calendar permission not granted");
      return;
    }

    final long calendarId = getCalendar(account);
    if (calendarId == INVALID_ID) {
      return;
    }

    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
    final boolean syncCalendar = settings.getBoolean(Settings.CALENDAR_SYNC, false);
    if (!syncCalendar) {
      Timber.d("Deleting calendar");
      //noinspection MissingPermission
      context.getContentResolver()
          .delete(CalendarContract.Events.CONTENT_URI, CalendarContract.Events.CALENDAR_ID + "=?",
              new String[] {
                  String.valueOf(calendarId),
              });

      Uri calenderUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
          .build();
      context.getContentResolver().delete(calenderUri, null, null);
      return;
    }

    ArrayList<ContentProviderOperation> ops = new ArrayList<>();

    //noinspection MissingPermission
    Cursor c =
        context.getContentResolver().query(CalendarContract.Events.CONTENT_URI, new String[] {
            CalendarContract.Events._ID, CalendarContract.Events.DTSTART,
            CalendarContract.Events.DTEND, CalendarContract.Events.SYNC_DATA1,
        }, CalendarContract.Events.CALENDAR_ID + "=?", new String[] {
            String.valueOf(calendarId),
        }, null);

    if (c == null) {
      return;
    }

    LongSparseArray<Event> events = new LongSparseArray<>();
    while (c.moveToNext()) {
      final long id = Cursors.getLong(c, CalendarContract.Events._ID);
      final Long episodeId = Cursors.getLongOrNull(c, CalendarContract.Events.SYNC_DATA1);
      if (episodeId != null) {
        Event event = events.get(episodeId);
        Event newEvent = new Event(c);
        if (event == null) {
          events.put(episodeId, newEvent);
        } else {
          ContentProviderOperation op =
              ContentProviderOperation.newDelete(CalendarContract.Events.CONTENT_URI)
                  .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                      String.valueOf(id),
                  })
                  .build();
          ops.add(op);
        }
      } else {
        ContentProviderOperation op =
            ContentProviderOperation.newDelete(CalendarContract.Events.CONTENT_URI)
                .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                    String.valueOf(id),
                })
                .build();
        ops.add(op);
      }
    }
    c.close();

    final long time = System.currentTimeMillis();

    Cursor shows = context.getContentResolver().query(Shows.SHOWS, new String[] {
        ShowColumns.ID, ShowColumns.TITLE, ShowColumns.RUNTIME,
    }, "("
        + ShowColumns.WATCHED_COUNT
        + ">0 OR "
        + ShowColumns.IN_WATCHLIST_COUNT
        + ">0 OR "
        + ShowColumns.IN_COLLECTION_COUNT
        + ">0"
        + ") AND "
        + ShowColumns.HIDDEN_CALENDAR
        + "=0"
        + " AND "
        + ShowColumns.NEEDS_SYNC
        + "=0", null, null);

    while (shows.moveToNext()) {
      final long showId = Cursors.getLong(shows, ShowColumns.ID);
      final String showTitle = Cursors.getString(shows, ShowColumns.TITLE);
      final long runtime = Cursors.getLong(shows, ShowColumns.RUNTIME);

      Cursor episodes = context.getContentResolver().query(Episodes.fromShow(showId), new String[] {
          EpisodeColumns.ID, EpisodeColumns.TITLE, EpisodeColumns.SEASON, EpisodeColumns.EPISODE,
          EpisodeColumns.FIRST_AIRED,
      }, EpisodeColumns.FIRST_AIRED + ">? AND " + EpisodeColumns.NEEDS_SYNC + "=0", new String[] {
          String.valueOf(time - 30 * DateUtils.DAY_IN_MILLIS),
      }, null);

      while (episodes.moveToNext()) {
        final long episodeId = Cursors.getLong(episodes, EpisodeColumns.ID);
        final int season = Cursors.getInt(episodes, EpisodeColumns.SEASON);
        final int episode = Cursors.getInt(episodes, EpisodeColumns.EPISODE);
        final String title = DataHelper.getEpisodeTitle(context, episodes, season, episode);
        final long firstAired = Cursors.getLong(episodes, EpisodeColumns.FIRST_AIRED);

        String eventTitle = showTitle + " - " + season + "x" + episode + " " + title;

        addEvent(ops, events, calendarId, episodeId, eventTitle, firstAired, runtime);
      }
      episodes.close();
    }
    shows.close();

    Cursor watchlistShows = context.getContentResolver().query(Shows.SHOWS_WATCHLIST, new String[] {
        ShowColumns.ID, ShowColumns.TITLE, ShowColumns.RUNTIME,
    }, null, null, null);

    while (watchlistShows.moveToNext()) {
      final long showId = Cursors.getLong(watchlistShows, ShowColumns.ID);
      final String showTitle = Cursors.getString(watchlistShows, ShowColumns.TITLE);
      final long runtime = Cursors.getLong(watchlistShows, ShowColumns.RUNTIME);

      Cursor episodes = context.getContentResolver().query(Episodes.fromShow(showId), new String[] {
          EpisodeColumns.ID, EpisodeColumns.TITLE, EpisodeColumns.SEASON, EpisodeColumns.EPISODE,
          EpisodeColumns.FIRST_AIRED,
      }, EpisodeColumns.FIRST_AIRED
          + ">? AND "
          + EpisodeColumns.EPISODE
          + "=1 AND "
          + EpisodeColumns.SEASON
          + ">0", new String[] {
          String.valueOf(time - 30 * DateUtils.DAY_IN_MILLIS),
      }, EpisodeColumns.SEASON + " ASC LIMIT 1");

      if (episodes.moveToFirst()) {
        final long episodeId = Cursors.getLong(episodes, EpisodeColumns.ID);
        final int season = Cursors.getInt(episodes, EpisodeColumns.SEASON);
        final int episode = Cursors.getInt(episodes, EpisodeColumns.EPISODE);
        final String title = DataHelper.getEpisodeTitle(context, episodes, season, episode);
        final long firstAired = Cursors.getLong(episodes, EpisodeColumns.FIRST_AIRED);

        String eventTitle = showTitle + " - " + season + "x" + episode + " " + title;

        addEvent(ops, events, calendarId, episodeId, eventTitle, firstAired, runtime);
      }

      episodes.close();
    }

    watchlistShows.close();

    for (int i = 0, size = events.size(); i < size; i++) {
      Event event = events.valueAt(i);
      ContentProviderOperation op =
          ContentProviderOperation.newDelete(CalendarContract.Events.CONTENT_URI)
              .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                  String.valueOf(event.id),
              })
              .build();
      ops.add(op);
    }

    try {
      context.getContentResolver().applyBatch(CalendarContract.AUTHORITY, ops);
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (OperationApplicationException e) {
      e.printStackTrace();
    }
  }

  private void addEvent(List<ContentProviderOperation> ops, LongSparseArray<Event> events,
      long calendarId, long episodeId, String eventTitle, long firstAired, long runtime) {
    Event event = events.get(episodeId);
    if (event != null) {
      ContentProviderOperation op =
          ContentProviderOperation.newUpdate(CalendarContract.Events.CONTENT_URI)
              .withValue(CalendarContract.Events.DTSTART, firstAired)
              .withValue(CalendarContract.Events.DTEND,
                  firstAired + runtime * DateUtils.MINUTE_IN_MILLIS)
              .withValue(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC)
              .withValue(CalendarContract.Events.TITLE, eventTitle)
              .withSelection(CalendarContract.Events._ID + "=?", new String[] {
                  String.valueOf(event.id),
              })
              .build();
      ops.add(op);
      events.remove(episodeId);
    } else {
      ContentProviderOperation op = ContentProviderOperation.newInsert(
          CalendarContract.Events.CONTENT_URI.buildUpon()
              .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
              .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME,
                  context.getString(R.string.accountName))
              .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE,
                  context.getString(R.string.accountType))
              .build())
          .withValue(CalendarContract.Events.DTSTART, firstAired)
          .withValue(CalendarContract.Events.DTEND,
              firstAired + runtime * DateUtils.MINUTE_IN_MILLIS)
          .withValue(CalendarContract.Events.EVENT_TIMEZONE, Time.TIMEZONE_UTC)
          .withValue(CalendarContract.Events.TITLE, eventTitle)
          .withValue(CalendarContract.Events.SYNC_DATA1, episodeId)
          .withValue(CalendarContract.Events.CALENDAR_ID, calendarId)
          .build();
      ops.add(op);
    }
  }

  private long getCalendar(Account account) {
    return getCalendar(account, true);
  }

  private long getCalendar(Account account, boolean recall) {
    Cursor c = null;
    try {
      Uri calenderUri = CalendarContract.Calendars.CONTENT_URI.buildUpon()
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
          .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
          .build();
      c = context.getContentResolver().query(calenderUri, new String[] {
          BaseColumns._ID,
      }, null, null, null);

      if (c == null) {
        return INVALID_ID;
      }

      if (c.moveToNext()) {
        return c.getLong(0);
      } else {
        if (!recall) {
          return INVALID_ID;
        }

        ArrayList<ContentProviderOperation> operationList = new ArrayList<>();

        ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
            CalendarContract.Calendars.CONTENT_URI.buildUpon()
                .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account.name)
                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, account.type)
                .build());
        builder.withValue(CalendarContract.Calendars.ACCOUNT_NAME, account.name);
        builder.withValue(CalendarContract.Calendars.ACCOUNT_TYPE, account.type);
        builder.withValue(CalendarContract.Calendars.NAME, context.getString(R.string.app_name));
        builder.withValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
            context.getString(R.string.calendarName));
        builder.withValue(CalendarContract.Calendars.CALENDAR_COLOR, 0xD51007);
        builder.withValue(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL,
            CalendarContract.Calendars.CAL_ACCESS_READ);
        builder.withValue(CalendarContract.Calendars.OWNER_ACCOUNT, account.name);
        builder.withValue(CalendarContract.Calendars.SYNC_EVENTS, 1);
        operationList.add(builder.build());
        try {
          context.getContentResolver().applyBatch(CalendarContract.AUTHORITY, operationList);
        } catch (Exception e) {
          e.printStackTrace();
          return INVALID_ID;
        }
        return getCalendar(account, false);
      }
    } finally {
      if (c != null) c.close();
    }
  }
}

{'fraction_non_alphanumeric': 0.10276017770009467, 'fraction_numerical': 0.0025489767678974584, 'mean_word_length': 3.0740246254264947, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 60, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1892684', 'n_tokens_mistral': 7915, 'n_tokens_neox': 7695, 'n_words': 1447}