code: public void uploadProject(
        final Project project,
        final int version,
        final File localFile,
        final User uploader) {
      byte[] md5 = null;
      if (!(this.storage instanceof DatabaseStorage)) {
        md5 = computeHash(localFile);
      }
      final StorageMetadata metadata = new StorageMetadata(
          project.getId(),
          version,
          uploader.getUserId(),
          md5);
      log.info(String.format("Adding archive to storage. Meta:%s File: %s[%d bytes]",
          metadata, localFile.getName(), localFile.length()));
  
      /* upload to storage */
      final String resourceId = this.storage.put(metadata, localFile);
  
      /* Add metadata to db */
      // TODO spyne: remove hack. Database storage should go through the same flow
      if (!(this.storage instanceof DatabaseStorage)) {
        this.projectLoader.addProjectVersion(
            project.getId(),
            version,
            localFile,
            uploader.getUserId(),
            requireNonNull(md5),
            requireNonNull(resourceId)
        );
        log.info(String.format("Added project metadata to DB. Meta:%s File: %s[%d bytes] URI: %s",
            metadata, localFile.getName(), localFile.length(), resourceId));
      }
    }
docs: API to a project file into Azkaban Storage
  
  TODO clean up interface
  
  @param project project
  @param version The new version to be uploaded
  @param localFile local file
  @param uploader the user who uploaded