private static void saveCollectionToFileCache(
     IonConfig config, Collection collection, Context context) throws IOException {
   String collectionUrl = IonPageUrls.getCollectionUrl(config);
   File filePath = FilePaths.getCollectionJsonPath(collectionUrl, config, context);
   String collectionJson = GsonHolder.getInstance().toJson(new CollectionResponse(collection));
   FileUtils.writeTextToFile(collectionJson, filePath);
 }
 private static FileWithMeta getFilePath(
     ArchiveIndex fileInfo, File collectionFolderTemp, IonConfig config, Context context) {
   File targetFile;
   IonRequestType type = null;
   String url = fileInfo.url;
   String pageIdentifier = null;
   String filename = FilePaths.getFileName(url);
   try {
     // check URL is a collections or pages call
     IonRequestInfo requestInfo = IonPageUrls.analyze(url, config);
     pageIdentifier = requestInfo.pageIdentifier;
     type = requestInfo.requestType;
     targetFile = FilePaths.getFilePath(url, config, context, true);
   } catch (NoIonPagesRequestException e) {
     IonLog.w(TAG, "URL " + url + " cannot be handled properly. Is it invalid?");
     targetFile = new File(collectionFolderTemp, filename);
   }
   return new FileWithMeta(targetFile, type, fileInfo, pageIdentifier);
 }
  /**
   * Untar an input file into an output file.
   *
   * <p>The output file is created in the output folder, having the same name as the input file,
   * minus the '.tar' extension.
   *
   * @param archiveFile input TAR file
   * @param lastModified when collection has been last modified
   * @throws FileNotFoundException
   * @throws ArchiveException
   */
  private static List<FileWithMeta> performUnTar(
      File archiveFile,
      IonConfig config,
      Collection collection,
      String lastModified,
      Context context)
      throws FileNotFoundException, IOException, ArchiveException {
    File collectionFolder = FilePaths.getCollectionFolderPath(config, context);
    File collectionFolderTemp = FilePaths.getTempFilePath(collectionFolder);

    final List<FileWithMeta> untaredFiles = new LinkedList<>();

    InputStream is = null;
    TarArchiveInputStream debInputStream = null;
    try {
      IonLog.d(
          TAG,
          String.format(
              "Untaring %s to dir %s.", archiveFile.getPath(), collectionFolder.getPath()));

      is = new FileInputStream(archiveFile);
      debInputStream =
          (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);

      TarArchiveEntry entry;
      List<ArchiveIndex> index = null;
      boolean indexHasBeenRead = false;
      while ((entry = (TarArchiveEntry) debInputStream.getNextEntry()) != null) {
        if (!indexHasBeenRead) {
          // get index.json
          InputStreamReader inputStreamReader = new InputStreamReader(debInputStream, "UTF-8");
          index =
              Arrays.asList(
                  GsonHolder.getInstance().fromJson(inputStreamReader, ArchiveIndex[].class));
          indexHasBeenRead = true;
          continue;
        }

        // write the "content" files
        if (!entry.isDirectory()) {
          String archiveFileName = entry.getName();
          ArchiveIndex fileInfo = ArchiveIndex.getByName(archiveFileName, index);
          if (fileInfo == null) {
            IonLog.w(
                TAG, "Skipping " + entry.getName() + " because it was not found in index.json.");
            continue;
          }

          IonLog.i(TAG, fileInfo.url);
          FileWithMeta fileWithMeta = getFilePath(fileInfo, collectionFolderTemp, config, context);
          File targetFile = fileWithMeta.file;
          FileUtils.createDir(targetFile.getParentFile());

          targetFile = FileUtils.writeToFile(debInputStream, targetFile);

          if (targetFile != null) {
            untaredFiles.add(fileWithMeta);
          }
        }
      }
    } finally {
      // finished reading TAR archive
      if (is != null) {
        is.close();
      }
      if (debInputStream != null) {
        debInputStream.close();
      }
      if (archiveFile != null && archiveFile.exists()) {
        archiveFile.delete();
      }
    }

    // if lastModified date was not passed, look if cache index entry exists for collection and
    // retrieve it from there
    if (collection != null && lastModified == null) {
      CollectionCacheIndex collectionCacheIndex = CollectionCacheIndex.retrieve(config, context);
      lastModified = collectionCacheIndex == null ? null : collectionCacheIndex.getLastModified();
      IonLog.d(TAG, "Restoring last_modified from cache index: " + lastModified);
    }

    // delete old cache index entries of the collection in shared preferences and in memory cache
    CacheIndexStore.clearCollection(config, context);
    MemoryCache.clear();

    // replace collection folder (containing json files) - deletes old file cache
    boolean jsonWriteSuccess = FileUtils.move(collectionFolderTemp, collectionFolder, true);
    if (!jsonWriteSuccess) {
      throw new IOException("JSON files could not be moved to final path.");
    }

    // replace media files in collection
    File mediaFolderTemp = FilePaths.getMediaFolderPath(config, context, true);
    File mediaFolder = FilePaths.getMediaFolderPath(config, context, false);
    if (mediaFolderTemp.exists()) {
      boolean mediaWriteSuccess = FileUtils.move(mediaFolderTemp, mediaFolder, true);
      if (!mediaWriteSuccess) {
        throw new IOException("Media files could not be moved to final path.");
      }
    } else {
      IonLog.w(TAG, "No media files were contained in archive.");
    }

    // add collection to file cache again
    if (collection != null) {
      MemoryCache.saveCollection(collection, config, context);
      try {
        saveCollectionToFileCache(config, collection, context);
        CollectionCacheIndex.save(config, context, lastModified);
      } catch (IOException e) {
        IonLog.e("ION Archive", "Collection could not be saved.");
        IonLog.ex(e);
      }
    }

    // cache index entries are not written yet at this point
    return untaredFiles;
  }