All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.leancloud.LCFile Maven / Gradle / Ivy

package cn.leancloud;

import cn.leancloud.annotation.LCClassName;
import cn.leancloud.callback.ProgressCallback;
import cn.leancloud.codec.MDFive;
import cn.leancloud.core.LeanCloud;
import cn.leancloud.cache.FileCache;
import cn.leancloud.cache.PersistenceUtil;
import cn.leancloud.core.AppConfiguration;
import cn.leancloud.core.StorageClient;
import cn.leancloud.core.PaasClient;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

import cn.leancloud.upload.*;
import cn.leancloud.utils.LCUtils;
import cn.leancloud.utils.FileUtil;
import cn.leancloud.utils.StringUtil;
import cn.leancloud.json.JSONObject;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

@LCClassName("_File")
public final class LCFile extends LCObject {
  public static final String CLASS_NAME = "_File";

  private static final String FILE_SUM_KEY = "_checksum";
  private static final String FILE_NAME_KEY = "_name";
  private static final String FILE_LENGTH_KEY = "size";
  private static final String FILE_SOURCE_KEY = "__source";
  private static final String FILE_SOURCE_EXTERNAL = "external";
  private static final String FILE_PATH_PREFIX_KEY = "prefix";

  private static final String THUMBNAIL_FMT = "?imageView/%d/w/%d/h/%d/q/%d/format/%s";

  private static final String KEY_FILE_NAME = "name";
  private static final String KEY_METADATA = "metaData";
  private static final String KEY_URL = "url";
  private static final String KEY_BUCKET = "bucket";
  private static final String KEY_PROVIDER = "provider";
  private static final String KEY_MIME_TYPE = "mime_type";
  private static final String KEY_FILE_KEY = "key";

  public static void setUploadHeader(String key, String value) {
    FileUploader.setUploadHeader(key, value);
  }

  private transient String localPath = ""; // local file used by AVFile(name, file) constructor.

  private transient String cachePath = ""; // file cache path

  /**
   * default constructor.
   */
  public LCFile() {
    super(CLASS_NAME);
    if (AppConfiguration.getDefaultACL() != null) {
      this.acl = new LCACL(AppConfiguration.getDefaultACL());
    }
  }

  /**
   * constructor with name and data.
   * @param name file name.
   * @param data binary data.
   */
  public LCFile(String name, byte[] data) {
    this();
    if (null == data) {
      logger.w("data is illegal(null)");
      throw new IllegalArgumentException("data is illegal(null)");
    }
    internalPut(KEY_FILE_NAME, name);
    addMetaData(FILE_NAME_KEY, name);
    String md5 = MDFive.computeMD5(data);
    localPath = FileCache.getIntance().saveData(md5, data);
    addMetaData(FILE_SUM_KEY, md5);
    addMetaData(FILE_LENGTH_KEY, data.length);
    internalPut(KEY_MIME_TYPE, FileUtil.getMimeTypeFromFilename(name));
  }

  /**
   * constructor with name and local file.
   * @param name file name.
   * @param localFile local file.
   */
  public LCFile(String name, File localFile) {
    this();
    if (null == localFile || !localFile.exists() || !localFile.isFile()) {
      logger.w("local file is illegal");
      throw new IllegalArgumentException("local file is illegal.");
    }
    internalPut(KEY_FILE_NAME, name);
    addMetaData(FILE_NAME_KEY, name);
    String md5 = MDFive.computeFileMD5(localFile);
    localPath = localFile.getAbsolutePath();
    addMetaData(FILE_SUM_KEY, md5);
    addMetaData(FILE_LENGTH_KEY, localFile.length());
    internalPut(KEY_MIME_TYPE, FileUtil.getMimeTypeFromPath(localPath));
  }

  /**
   * constructor with name and external url.
   * @param name file name.
   * @param url external url.
   */
  public LCFile(String name, String url) {
    this(name, url, null);
  }

  /**
   * constructor with name and external url.
   * @param name file name
   * @param url external url.
   * @param metaData additional attributes.
   */
  public LCFile(String name, String url, Map metaData) {
    this(name, url, metaData, true);
  }

  protected LCFile(String name, String url, Map metaData, boolean external) {
    this();
    internalPut(KEY_FILE_NAME, name);
    addMetaData(FILE_NAME_KEY, name);
    internalPut(KEY_URL, url);
    Map meta = new HashMap();
    if (null != metaData) {
      LCUtils.putAllWithNullFilter(meta, metaData);
    }
    if (external) {
      meta.put(FILE_SOURCE_KEY, FILE_SOURCE_EXTERNAL);
    }
    internalPut(KEY_METADATA, meta);
    internalPut(KEY_MIME_TYPE, FileUtil.getMimeTypeFromUrl(url));
  }

  // add for avoiding sonarqube check, idiot sonar
  @Override
  public boolean equals(Object o) {
    return super.equals(o);
  }

  // add for avoiding sonarqube check, idiot sonar
  @Override
  public int hashCode() {
    return super.hashCode();
  }

  private void internalPutDirectly(String key, Object value) {
    this.serverData.put(key, value);
  }

  /**
   * Get AVFile instance from objectId.
   * @param objectId file objectId.
   * @return observable instance.
   */
  public static Observable withObjectIdInBackground(final String objectId) {
    return PaasClient.getStorageClient().fetchFile(null, objectId);
  }

  /**
   * Get file name.
   * @return file name.
   */
  public String getName() {
    return (String) internalGet(KEY_FILE_NAME);
  }

  /**
   * Set file name.
   * @param name file name.
   */
  public void setName(String name) {
    internalPut(KEY_FILE_NAME, name);
  }

  /**
   * set folder path
   * @param path folder path. null or empty string equals to clearPathPrefix.
   */
  public void setPathPrefix(String path) {
    Object externalFlag = getMetaData(FILE_SOURCE_KEY);
    if (FILE_SOURCE_EXTERNAL.equals(externalFlag)) {
      return;
    }
    if (StringUtil.isEmpty(path)) {
      clearPathPrefix();
    } else {
      internalPut(FILE_PATH_PREFIX_KEY, path);
      addMetaData(FILE_PATH_PREFIX_KEY, path);
    }
  }

  /**
   * clear folder path.
   */
  public void clearPathPrefix() {
    Object externalFlag = getMetaData(FILE_SOURCE_KEY);
    if (FILE_SOURCE_EXTERNAL.equals(externalFlag)) {
      return;
    }
    super.remove(FILE_PATH_PREFIX_KEY);
    getMetaData().remove(FILE_PATH_PREFIX_KEY);
  }
  /**
   * Get file meta data.
   * @return meta data.
   */
  public Map getMetaData() {
    Map result = (Map)internalGet(KEY_METADATA);
    if (null == result) {
      result = new HashMap();
      internalPut(KEY_METADATA, result);
    }
    return result;
  }

  /**
   * Set file meta data.
   * @param metaData meta data.
   */
  public void setMetaData(Map metaData) {
    internalPut(KEY_METADATA, metaData);
  }

  /**
   * Add new meta data.
   * @param key meta key.
   * @param val meta value.
   */
  public void addMetaData(String key, Object val) {
    Map metaData = getMetaData();
    metaData.put(key, val);
  }

  /**
   * Get file meta data.
   * @param key meta key.
   * @return meta value.
   */
  public Object getMetaData(String key) {
    return getMetaData().get(key);
  }

  /**
   * Remove file meta data.
   * @param key meta key.
   * @return old value.
   */
  public Object removeMetaData(String key) {
    return getMetaData().remove(key);
  }

  /**
   * Cleanup meta data.
   */
  public void clearMetaData() {
    getMetaData().clear();
  }

  /**
   * Get file size.
   * @return file size.
   */
  public int getSize() {
    Number size = (Number) getMetaData(FILE_LENGTH_KEY);
    if (size != null)
      return size.intValue();
    else
      return -1;
  }

  /**
   * Get file mime type.
   * @return mime type.
   */
  public String getMimeType() {
    return (String) internalGet(KEY_MIME_TYPE);
  }

  /**
   * Set file mime type.
   * @param mimeType mime type.
   */
  public void setMimeType(String mimeType) {
    internalPut(KEY_MIME_TYPE, mimeType);
  }

  /**
   * Get file key.
   * @return file key.
   */
  public String getKey() {
    return (String) internalGet(KEY_FILE_KEY);
  }

  /**
   * Set file key.
   * @param fileKey - fileKey
   * @notice this method needs authentication with masterKey!!
   *
   * File key is a part of url. After specified `fileKey`, the file's url should become `https://domain/fileKey`.
   * With the help of this method, developer can upload file with particular path. For example, upload a robots.txt file as following:
   *     File localFile = new File("./20160704174809.txt");
   *     AVFile file = new AVFile("testfilename", localFile);
   *     file.setKey("robots.txt");
   *     file.saveInBackground().blockingSubscribe();
   *
   */
  void setKey(String fileKey) {
    internalPut(KEY_FILE_KEY, fileKey);
  }

  /**
   * Get file bucket.
   * @return file bucket.
   */
  public String getBucket() {
    return (String) internalGet(KEY_BUCKET);
  }

  /**
   * Get file url.
   * @return file url.
   */
  public String getUrl() {
    return (String) internalGet(KEY_URL);
  }

  /**
   * Get file provider.
   * @return file provider.
   */
  public String getProvider() {
    return (String) internalGet(KEY_PROVIDER);
  }

  /**
   * Set file attribute.
   * @param key attribute key.
   * @param value attribute value.
   * notice: UnsupportedOperationException
   */
  @Override
  public void put(String key, Object value) {
    throw new UnsupportedOperationException("cannot invoke put method in AVFile");
  }

  /**
   * Get file attribute.
   * @param key attribute key.
   * @return attribute value.
   * notice: UnsupportedOperationException
   */
  @Override
  public Object get(String key) {
    throw new UnsupportedOperationException("cannot invoke get method in AVFile");
  }

  /**
   * Remove file attribute.
   * @param key attribute key.
   * notice: UnsupportedOperationException
   */
  @Override
  public void remove(String key) {
    throw new UnsupportedOperationException("cannot invoke remove method in AVFile");
  }

  /**
   * Increment file attribute.
   * @param key attribute key.
   * notice: UnsupportedOperationException
   */
  @Override
  public void increment(String key) {
    throw new UnsupportedOperationException("cannot invoke increment method in AVFile");
  }

  /**
   * Increment file attribute.
   * @param key attribute key.
   * @param value step value.
   * notice: UnsupportedOperationException
   */
  @Override
  public void increment(String key, Number value) {
    throw new UnsupportedOperationException("cannot invoke increment(Number) method in AVFile");
  }

  /**
   * Returns a thumbnail image url using QiNiu endpoints.
   *
   * @param scaleToFit scale param.
   * @param width width.
   * @param height height.
   * @return new url for thumbnail.
   * @see #getThumbnailUrl(boolean, int, int, int, String)
   */
  public String getThumbnailUrl(boolean scaleToFit, int width, int height) {
    return getThumbnailUrl(scaleToFit, width, height, 100, "png");
  }

  /**
   * Returns a thumbnail image url using QiNiu endpoints.
   * @param scaleToFit scale param.
   * @param width width
   * @param height height
   * @param quality quality.
   * @param fmt format string.
   * @return new url for thumbnail.
   */
  public String getThumbnailUrl(boolean scaleToFit, int width, int height, int quality, String fmt) {
    if (LeanCloud.getRegion() == LeanCloud.REGION.NorthAmerica) {
      logger.w("We only support this method for qiniu storage.");
      throw new UnsupportedOperationException("We only support this method for qiniu storage.");
    }
    if (width < 0 || height < 0) {
      logger.w("Invalid width or height.");
      throw new IllegalArgumentException("Invalid width or height.");
    }
    if (quality < 1 || quality > 100) {
      logger.w("Invalid quality,valid range is 0-100.");
      throw new IllegalArgumentException("Invalid quality,valid range is 0-100.");
    }
    if (fmt == null || StringUtil.isEmpty(fmt.trim())) {
      fmt = "png";
    }
    int mode = scaleToFit ? 2 : 1;
    return this.getUrl() + String.format(THUMBNAIL_FMT, mode, width, height, quality, fmt);
  }

  /**
   * Get map data of current file.
   * @return map data.
   */
  public Map toMap() {
    Map result = new HashMap();
    result.put("__type", CLASS_NAME);
    result.put(KEY_METADATA, getMetaData());

    if (!StringUtil.isEmpty(getUrl())) {
      result.put(KEY_URL, getUrl());
    }

    if (!StringUtil.isEmpty(getObjectId())) {
      result.put(LCObject.KEY_OBJECT_ID, getObjectId());
    }

    result.put("id", getName());

    return result;
  }

  /**
   * save to cloud backend.
   * @param keepFileName whether keep file name in url or not.
   * @param progressCallback progress callback.
   */
  public synchronized void saveInBackground(boolean keepFileName, final ProgressCallback progressCallback) {
    saveInBackground(null, keepFileName, progressCallback);
  }

  /**
   * save to cloud in background.
   * @param asAuthenticatedUser explicit user for request authentication.
   * @param keepFileName whether keep file name in url or not.
   * @param progressCallback progress callback.
   *
   * in general, this method should be invoked in lean engine.
   */
  public synchronized void saveInBackground(LCUser asAuthenticatedUser,
                                            boolean keepFileName, final ProgressCallback progressCallback) {
    saveWithProgressCallback(asAuthenticatedUser, keepFileName, progressCallback).subscribe(new Observer() {
      @Override
      public void onSubscribe(Disposable disposable) {

      }

      @Override
      public void onNext(LCFile avFile) {
        if (null != progressCallback) {
          progressCallback.internalDone(100, null);
        }
      }

      @Override
      public void onError(Throwable throwable) {
        if (null != progressCallback) {
          progressCallback.internalDone(90, new LCException(throwable));
        }
      }

      @Override
      public void onComplete() {

      }
    });
  }

  /**
   * save to cloud backend.
   * @param progressCallback progress callback.
   */
  public void saveInBackground(final ProgressCallback progressCallback) {
    saveInBackground(false, progressCallback);
  }

  private Observable directlyCreate(LCUser asAuthenticatedUser, final JSONObject parameters) {
    return PaasClient.getStorageClient().createObject(asAuthenticatedUser,
            this.className, parameters, false, null)
            .map(new Function() {
              @Override
              public LCFile apply(LCObject LCObject) throws Exception {
                LCUtils.putAllWithNullFilter(LCFile.this.serverData, parameters);
                LCFile.this.mergeRawData(LCObject, true);
                LCFile.this.onSaveSuccess();
                return LCFile.this;
              }});
  }

  private boolean isSavingExternalFile() {
    return StringUtil.isEmpty(getObjectId()) && !StringUtil.isEmpty(getUrl());
  }

  private Observable saveWithProgressCallback(final LCUser asAuthenticatedUser,
                                                      boolean keepFileName, final ProgressCallback callback) {
    JSONObject paramData = generateChangedParam();
//    final String fileKey = FileUtil.generateFileKey(this.getName(), keepFileName);
//    paramData.put("key", fileKey);
//    paramData.put("__type", "File");
    if (StringUtil.isEmpty(getObjectId())) {
      if (!StringUtil.isEmpty(getUrl())) {
        return directlyCreate(asAuthenticatedUser, paramData);
      }
      logger.d("createToken params: " + paramData.toJSONString() + ", " + this);
      StorageClient storageClient = PaasClient.getStorageClient();
      Observable result = storageClient.newUploadToken(asAuthenticatedUser, paramData)
              .map(new Function() {
                public LCFile apply(@NonNull FileUploadToken fileUploadToken) throws Exception {
                  logger.d("[Thread:" + Thread.currentThread().getId() + "]" + fileUploadToken.toString() + ", " + LCFile.this);
                  LCFile.this.setObjectId(fileUploadToken.getObjectId());
                  LCFile.this.internalPutDirectly(KEY_OBJECT_ID, fileUploadToken.getObjectId());
                  LCFile.this.internalPutDirectly(KEY_BUCKET, fileUploadToken.getBucket());
                  LCFile.this.internalPutDirectly(KEY_PROVIDER, fileUploadToken.getProvider());
                  LCFile.this.internalPutDirectly(KEY_FILE_KEY, fileUploadToken.getKey());

                  Uploader uploader = new FileUploader(LCFile.this, fileUploadToken, callback);
                  LCFile.this.internalPutDirectly(KEY_URL, fileUploadToken.getUrl());

                  LCException exception = uploader.execute();

                  JSONObject completeResult = JSONObject.Builder.create(null);
                  completeResult.put("result", null == exception);
                  completeResult.put("token",fileUploadToken.getToken());
                  logger.d("file upload result: " + completeResult.toJSONString());
                  try {
                    PaasClient.getStorageClient().fileCallback(asAuthenticatedUser, completeResult);
                    if (null != exception) {
                      logger.w("failed to upload file. cause: " + exception.getMessage());
                      throw exception;
                    } else {
                      return LCFile.this;
                    }
                  } catch (IOException ex) {
                    logger.w(ex);
                    throw ex;
                  }
                }
              });
      result = storageClient.wrapObservable(result);
      return result;
    } else {
      logger.d("file has been upload to cloud, ignore update request.");
      return Observable.just(this);
    }
  }

  /**
   * save to cloud backend.
   * @return  Observable object.
   */
  @Override
  public Observable saveInBackground() {
    return saveInBackground(false);
  }

  /**
   * save to cloud.
   */
  @Override
  public void save() {
    this.saveInBackground().blockingSubscribe();
  }

  /**
   * save to cloud
   * @param asAuthenticatedUser explicit user for request authentication.
   *
   */
  public void save(LCUser asAuthenticatedUser) {
    this.saveInBackground(asAuthenticatedUser, false).blockingSubscribe();
  }

  /**
   * save to cloud backend.
   * @param keepFileName whether keep file name in url or not.
   * @return Observable object.
   */
  public Observable saveInBackground(boolean keepFileName) {
    return saveInBackground(null, keepFileName);
  }

  /**
   * Save eventually(not supported).
   * @throws LCException exception happened.
   *
   * Because that file size is too big, cache the entire data will cost much disk capacity,
   * so we don't support this method at present.
   */
  public void saveEventually() throws LCException {
    saveEventually(null);
  }

  /**
   * Save eventually(not supported).
   * @param asAuthenticatedUser explicit user for request authentication.
   * @throws LCException exception happened.
   *
   * Because that file size is too big, cache the entire data will cost much disk capacity,
   * so we don't support this method at present.
   */
  public void saveEventually(final LCUser asAuthenticatedUser) throws LCException {
    if (isSavingExternalFile()) {
      super.saveEventually(asAuthenticatedUser);
    } else {
      throw new UnsupportedOperationException("AVFile#saveEventually is not allowed," +
              " please save the binary data to temp store and try to save in future.");
    }
  }
  /**
   * save to cloud in background.
   * @param asAuthenticatedUser explicit user for request authentication.
   * @param keepFileName whether keep file name in url or not.
   * @return Observable object.
   *
   * in general, this method should be invoked in lean engine.
   */
  public Observable saveInBackground(LCUser asAuthenticatedUser, boolean keepFileName) {
    return saveWithProgressCallback(asAuthenticatedUser, keepFileName,null);
  }

  /**
   * Get data in blocking mode.
   * @return data bytes.
   */
  //@JSONField(serialize = false)
  public byte[] getData() {
    String filePath = "";
    if(!StringUtil.isEmpty(localPath)) {
      filePath = localPath;
    } else if (!StringUtil.isEmpty(cachePath)) {
      filePath = cachePath;
    } else if (!StringUtil.isEmpty(getUrl())) {
      File cacheFile = FileCache.getIntance().getCacheFile(getUrl());
      if (null == cacheFile || !cacheFile.exists()) {
        FileDownloader downloader = new FileDownloader();
        downloader.execute(getUrl(), cacheFile);
      }
      if (null != cacheFile) {
        filePath = cacheFile.getAbsolutePath();
      }
    }
    if(!StringUtil.isEmpty(filePath)) {
      return PersistenceUtil.sharedInstance().readContentBytesFromFile(new File(filePath));
    }
    return new byte[0];
  }

  /**
   * Get data in async mode.
   * @return observable instance.
   */
  public Observable getDataInBackground() {
    Observable observable = Observable.fromCallable(new Callable() {
      @Override
      public byte[] call() throws Exception {
        return getData();
      }
    });
    if (AppConfiguration.isAsynchronized()) {
      observable = observable.subscribeOn(Schedulers.io());
    }
    AppConfiguration.SchedulerCreator defaultScheduler = AppConfiguration.getDefaultScheduler();
    if (null != defaultScheduler) {
      observable = observable.observeOn(defaultScheduler.create());
    }
    return observable;
  }

  /**
   * Get data stream in blocking mode.
   * @return data stream.
   * @throws Exception for file not found or io problem.
   */
  public InputStream getDataStream() throws Exception {
    String filePath = "";
    if(!StringUtil.isEmpty(localPath)) {
      filePath = localPath;
    } else if (!StringUtil.isEmpty(cachePath)) {
      filePath = cachePath;
    } else if (!FileCache.getIntance().isDisableLocalCache() && !StringUtil.isEmpty(getUrl())) {
      File cacheFile = FileCache.getIntance().getCacheFile(getUrl());
      if (null == cacheFile || !cacheFile.exists()) {
        FileDownloader downloader = new FileDownloader();
        downloader.execute(getUrl(), cacheFile);
      }
      if (null != cacheFile) {
        filePath = cacheFile.getAbsolutePath();
      }
    }
    if(!StringUtil.isEmpty(filePath)) {
      logger.d("dest file path=" + filePath);
      return FileCache.getIntance().getInputStreamFromFile(new File(filePath));
    }
    logger.w("failed to get dataStream.");
    return null;
  }

  /**
   * Get data stream in async mode.
   * @return observable instance.
   */
  public Observable getDataStreamInBackground() {
    Observable observable = Observable.fromCallable(new Callable() {
      @Override
      public InputStream call() throws Exception {
        return getDataStream();
      }
    });
    if (AppConfiguration.isAsynchronized()) {
      observable = observable.subscribeOn(Schedulers.io());
    }
    AppConfiguration.SchedulerCreator defaultScheduler = AppConfiguration.getDefaultScheduler();
    if (null != defaultScheduler) {
      observable = observable.observeOn(defaultScheduler.create());
    }
    return observable;
  }

  /**
   * Generate File instance with local path.
   * @param name file name
   * @param absoluteLocalFilePath local path.
   * @return file instance.
   * @throws FileNotFoundException file not found.
   */
  public static LCFile withAbsoluteLocalPath(String name, String absoluteLocalFilePath)
          throws FileNotFoundException {
    return withFile(name, new File(absoluteLocalFilePath));
  }

  /**
   * Generate File instance with local file.
   * @param name file name.
   * @param file local file.
   * @return file instance.
   * @throws FileNotFoundException file not found.
   */
  public static LCFile withFile(String name, File file) throws FileNotFoundException {
    if (file == null) {
      throw new IllegalArgumentException("null file object.");
    }
    if (!file.exists() || !file.isFile()) {
      throw new FileNotFoundException();
    }
    LCFile avFile = new LCFile(name, file);
    LCUser currentUser = LCUser.getCurrentUser();
    if (null != currentUser && !StringUtil.isEmpty(currentUser.getObjectId())) {
      avFile.addMetaData("owner", currentUser.getObjectId());
    }
    return avFile;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy