@Override
  public void start() {
    File logFile = Logger.getLogFile();

    // TRICKY: make sure the github_oauth2 token has been set
    int githubTokenIdentifier =
        AppContext.context()
            .getResources()
            .getIdentifier("github_oauth2", "string", AppContext.context().getPackageName());
    String githubUrl =
        AppContext.context().getResources().getString(R.string.github_bug_report_repo);

    if (githubTokenIdentifier != 0) {
      GithubReporter reporter =
          new GithubReporter(
              AppContext.context(),
              githubUrl,
              AppContext.context().getResources().getString(githubTokenIdentifier));
      reporter.reportBug(mNotes, logFile);

      // empty the log
      try {
        FileUtils.write(logFile, "");
      } catch (IOException e) {
        e.printStackTrace();
      }

      Logger.i(this.getClass().getName(), "Submitted bug report");
    } else if (githubTokenIdentifier == 0) {
      Logger.w(this.getClass().getName(), "the github oauth2 token is missing");
    }
  }
 /**
  * Merges chunks found in a target translation Project that do not exist in the source translation
  * to a sibling chunk so that no data is lost.
  *
  * @param library
  * @param targetTranslation target translation to merge
  * @return
  */
 public static boolean migrateChunkChanges(
     final Library library, final TargetTranslation targetTranslation) {
   try {
     Logger.i(
         TargetTranslationMigrator.class.getName(),
         "Migrating chunks in target translation " + targetTranslation.getProjectId());
     final SourceTranslation sourceTranslation =
         library.getDefaultSourceTranslation(targetTranslation.getProjectId(), "en");
     if (sourceTranslation == null) {
       Logger.w(
           TargetTranslationMigrator.class.getName(),
           "Could not find a source translation for the target translation "
               + targetTranslation.getId());
       return false;
     }
     if (targetTranslation.getPath().exists()) {
       boolean migrationSuccess = true;
       // perform the chunk migration on each chapter of the target translation
       for (ChapterTranslation chapterTranslation : targetTranslation.getChapterTranslations()) {
         Chapter chapter = library.getChapter(sourceTranslation, chapterTranslation.getId());
         if (chapter != null) {
           boolean success =
               mergeInvalidChunksInChapter(library, sourceTranslation, targetTranslation, chapter);
           migrationSuccess = migrationSuccess && success;
         }
       }
       return migrationSuccess;
     }
   } catch (Exception e) {
     Logger.e(
         TargetTranslationMigrator.class.getName(),
         "Failed to merge the chunks in the target translation "
             + targetTranslation.getProjectId());
   }
   return false;
 }
  /**
   * Merges invalid chunks found in the target translation with a valid sibling chunk in order to
   * preserve translation data. Merged chunks are marked as not finished to force translators to
   * review the changes.
   *
   * @param library
   * @param sourceTranslation
   * @param targetTranslation
   * @param chapter
   * @return
   */
  private static boolean mergeInvalidChunksInChapter(
      final Library library,
      final SourceTranslation sourceTranslation,
      final TargetTranslation targetTranslation,
      final Chapter chapter) {
    boolean success = true;
    Logger.i(
        TargetTranslationMigrator.class.getName(),
        "Searching chapter " + chapter.getId() + " for invalid chunks ");
    // TRICKY: the translation format doesn't matter for migrating
    FrameTranslation[] frameTranslations =
        targetTranslation.getFrameTranslations(chapter.getId(), TranslationFormat.DEFAULT);
    String invalidChunks = "";
    Frame lastValidFrame = null;
    for (FrameTranslation frameTranslation : frameTranslations) {
      Frame frame = library.getFrame(sourceTranslation, chapter.getId(), frameTranslation.getId());
      if (frame != null) {
        lastValidFrame = frame;
        // merge invalid frames into the existing frame
        if (!invalidChunks.isEmpty()) {
          targetTranslation.applyFrameTranslation(
              frameTranslation, invalidChunks + frameTranslation.body);
          invalidChunks = "";
          targetTranslation.reopenFrame(frame);
        }
      } else if (!frameTranslation.body.trim().isEmpty()) {
        if (lastValidFrame == null) {
          // collect invalid frame
          invalidChunks += frameTranslation.body + CHUNK_MERGE_MARKER;
        } else { // if last frame is not null, then append invalid chunk to it
          FrameTranslation lastFrameTranslation =
              targetTranslation.getFrameTranslation(lastValidFrame);
          targetTranslation.applyFrameTranslation(
              lastFrameTranslation,
              lastFrameTranslation.body + CHUNK_MERGE_MARKER + frameTranslation.body);
          targetTranslation.reopenFrame(lastValidFrame);
        }
        targetTranslation.applyFrameTranslation(frameTranslation, ""); // clear out old data
      }
    }
    // clean up remaining invalid chunks
    if (!invalidChunks.isEmpty()) {
      if (lastValidFrame == null) {
        // push remaining invalid chunks onto the first available frame
        String[] frameslugs = library.getFrameSlugs(sourceTranslation, chapter.getId());
        if (frameslugs.length > 0) {
          lastValidFrame = library.getFrame(sourceTranslation, chapter.getId(), frameslugs[0]);
        } else {
          Logger.w(
              TargetTranslationMigrator.class.getName(),
              "No frames were found for chapter " + chapter.getId());
        }
      }

      if (lastValidFrame != null) {
        FrameTranslation frameTranslation = targetTranslation.getFrameTranslation(lastValidFrame);
        targetTranslation.applyFrameTranslation(
            frameTranslation, invalidChunks + CHUNK_MERGE_MARKER + frameTranslation.body);
        targetTranslation.reopenFrame(lastValidFrame);
      }
    }

    return success;
  }
  /** Performs the backup if nessesary */
  private void runBackup() {
    boolean backupPerformed = false;
    Translator translator = AppContext.getTranslator();
    TargetTranslation[] targetTranslations = translator.getTargetTranslations();
    for (TargetTranslation t : targetTranslations) {

      // commit pending changes
      try {
        t.commit();
      } catch (Exception e) {
        Logger.e(this.getClass().getName(), "Failed to commit changes before backing up", e);
        continue;
      }

      // run backup if there are translations
      if (t.numTranslated() > 0) {

        // retreive commit hash
        String tag;
        try {
          tag = t.getCommitHash();
        } catch (Exception e) {
          Logger.w(this.getClass().getName(), "Failed to read commit hash", e);
          continue;
        }

        // check if backup is required
        if (tag != null) {
          File primaryBackupDir =
              new File(AppContext.getPublicDirectory(), "backups/" + t.getId() + "/");
          File primaryBackupFile =
              new File(primaryBackupDir, tag + "." + Translator.ARCHIVE_EXTENSION);
          File downloadBackupDir =
              new File(AppContext.getPublicDownloadsDirectory(), "backups/" + t.getId() + "/");
          File downloadBackupFile =
              new File(downloadBackupDir, tag + "." + Translator.ARCHIVE_EXTENSION);
          // e.g. ../../backups/uw-obs-de/[commit hash].tstudio
          if (!downloadBackupFile.exists()) {

            // peform backup
            File archive =
                new File(
                    AppContext.getPublicDownloadsDirectory(),
                    t.getId() + ".temp." + Translator.ARCHIVE_EXTENSION);
            try {
              translator.exportArchive(t, archive);
            } catch (Exception e) {
              Logger.e(
                  this.getClass().getName(),
                  "Failed to export the target translation " + t.getId(),
                  e);
              continue;
            }
            if (archive.exists() && archive.isFile()) {
              // move into backup
              FileUtils.deleteQuietly(downloadBackupDir);
              FileUtils.deleteQuietly(primaryBackupDir);
              downloadBackupDir.mkdirs();
              primaryBackupDir.mkdirs();
              try {
                // backup to downloads directory
                FileUtils.copyFile(archive, downloadBackupFile);
                // backup to a slightly less public area (used for auto restore)
                FileUtils.copyFile(archive, primaryBackupFile);
                backupPerformed = true;
              } catch (IOException e) {
                Logger.e(
                    this.getClass().getName(),
                    "Failed to copy the backup archive for target translation: " + t.getId(),
                    e);
              }
              archive.delete();
            } else {
              Logger.w(
                  this.getClass().getName(),
                  "Failed to export the target translation: " + t.getId());
            }
          }
        } else {
          Logger.w(this.getClass().getName(), "Could not find the commit hash");
        }
      }
    }

    if (backupPerformed) {
      onBackupComplete();
    }
  }