/**
   * Download new content manifest from server.
   *
   * @param config new application config from which we will take content url
   * @return new content manifest
   */
  private ContentManifest downloadContentManifest(ApplicationConfig config) {
    final String contentUrl = config.getContentConfig().getContentUrl();
    if (TextUtils.isEmpty(contentUrl)) {
      Log.d("CHCP", "Content url is not set in your application config! Can't load updates.");
      return null;
    }

    final String url = URLUtility.construct(contentUrl, filesStructure.manifestFileName());
    DownloadResult<ContentManifest> downloadResult = new ContentManifestDownloader(url).download();
    if (downloadResult.error != null) {
      Log.d("CHCP", "Failed to download content manifest");
      return null;
    }

    return downloadResult.value;
  }
  /**
   * Download from server new and update files.
   *
   * @param newAppConfig new application config, from which we will use content url
   * @param diff manifest difference from which we will know, what files to download
   * @return <code>true</code> if files are loaded; <code>false</code> - otherwise
   */
  private boolean downloadNewAndChagedFiles(ApplicationConfig newAppConfig, ManifestDiff diff) {
    final String contentUrl = newAppConfig.getContentConfig().getContentUrl();
    if (TextUtils.isEmpty(contentUrl)) {
      Log.d("CHCP", "Content url is not set in your application config! Can't load updates.");
      return false;
    }

    List<ManifestFile> downloadFiles = diff.getUpdateFiles();

    boolean isFinishedWithSuccess = true;
    try {
      FileDownloader.downloadFiles(filesStructure.downloadFolder(), contentUrl, downloadFiles);
    } catch (IOException e) {
      e.printStackTrace();
      isFinishedWithSuccess = false;
    }

    return isFinishedWithSuccess;
  }
  @Override
  public void run() {
    Log.d("CHCP", "Starting loader worker " + getWorkerId());
    // initialize before running
    if (!init()) {
      return;
    }

    // wait for possible installation process to end;
    // otherwise we can end up loading same stuff twice
    waitForInstallationToFinish();

    // download new application config
    ApplicationConfig newAppConfig = downloadApplicationConfig();
    if (newAppConfig == null) {
      dispatchErrorEvent(ChcpError.FAILED_TO_DOWNLOAD_APPLICATION_CONFIG, null);
      return;
    }

    // check if there is a new content version available
    if (newAppConfig
        .getContentConfig()
        .getReleaseVersion()
        .equals(oldAppConfig.getContentConfig().getReleaseVersion())) {
      dispatchNothingToUpdateEvent(newAppConfig);
      return;
    }

    // check if current native version supports new content
    if (newAppConfig.getContentConfig().getMinimumNativeVersion() > appBuildVersion) {
      dispatchErrorEvent(ChcpError.APPLICATION_BUILD_VERSION_TOO_LOW, newAppConfig);
      return;
    }

    // download new content manifest
    ContentManifest newContentManifest = downloadContentManifest(newAppConfig);
    if (newContentManifest == null) {
      dispatchErrorEvent(ChcpError.FAILED_TO_DOWNLOAD_CONTENT_MANIFEST, newAppConfig);
      return;
    }

    // find files that were updated
    ManifestDiff diff = oldManifest.calculateDifference(newContentManifest);
    if (diff.isEmpty()) {
      manifestStorage.storeInFolder(newContentManifest, filesStructure.wwwFolder());
      appConfigStorage.storeInFolder(newAppConfig, filesStructure.wwwFolder());
      dispatchNothingToUpdateEvent(newAppConfig);

      return;
    }

    recreateDownloadFolder(filesStructure.downloadFolder());

    // download files
    boolean isDownloaded = downloadNewAndChagedFiles(newAppConfig, diff);
    if (!isDownloaded) {
      cleanUp();
      dispatchErrorEvent(ChcpError.FAILED_TO_DOWNLOAD_UPDATE_FILES, newAppConfig);

      return;
    }

    // store configs
    manifestStorage.storeInFolder(newContentManifest, filesStructure.downloadFolder());
    appConfigStorage.storeInFolder(newAppConfig, filesStructure.downloadFolder());

    waitForInstallationToFinish();

    // copy all loaded content to installation folder
    boolean isReadyToInstall = moveDownloadedContentToInstallationFolder();
    if (!isReadyToInstall) {
      cleanUp();
      dispatchErrorEvent(
          ChcpError.FAILED_TO_MOVE_LOADED_FILES_TO_INSTALLATION_FOLDER, newAppConfig);
      return;
    }

    cleanUp();

    // notify that we are done
    dispatchSuccessEvent(newAppConfig);

    Log.d("CHCP", "Loader worker " + getWorkerId() + " has finished");
  }