package dan.dit.whatsthat.preferences; import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; import java.io.BufferedInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.Set; import dan.dit.whatsthat.image.ImageObfuscator; import dan.dit.whatsthat.util.webPhotoSharing.CloudinaryController; import dan.dit.whatsthat.util.webPhotoSharing.PhotoAlbumShareController; /** * Created by daniel on 28.10.15. */ public class WebPhotoStorage { private static final String KEY_USER_ALBUM_HASH = "dan.dit.whatsthat.PhotoStorageAlbumHash"; private static final String KEY_USER_UPLOADED_PHOTOS_HASHES = "dan.dit.whatsthat" + ".PhotoStorageUploadedPhotosHashes"; private static final String TEMP_DOWNLOAD_FILE_NAME = "temp_download" + ImageObfuscator.FILE_EXTENSION; private static final int ERROR_CODE_NONE = 1; private static final int ERROR_CODE_DOWNLOAD_RESPONSE_NOT_OK = 2; private static final int ERROR_CODE_STORAGE_NOT_AVAILABLE = 3; private static final int ERROR_CODE_DOWNLOAD_FILE_ILLEGAL = 4; private static final int ERROR_CODE_DOWNLOAD_IOEXCEPTION = 5; private static final int ERROR_CODE_URI_ILLEGAL = 6; private static final int ERROR_CODE_UPLOAD_FAILED = 7; private final PhotoAlbumShareController mController; private String mAlbumHash; private boolean mIsWorking; public WebPhotoStorage(@NonNull SharedPreferences prefs) { mController = new CloudinaryController(); //DumpYourPhoto will probably shut down starting // with 2016, do not use it checkAlbum(prefs); } private boolean checkAlbum(@NonNull SharedPreferences prefs) { mAlbumHash = prefs.getString(KEY_USER_ALBUM_HASH, null); return hasAlbum(); } public boolean hasAlbum() { return !TextUtils.isEmpty(mAlbumHash); } public synchronized boolean isWorking() { return mIsWorking; } public interface UploadListener { void onPhotoUploaded(String photoLink, URL photoShareLink); void onPhotoUploadFailed(int error); } public interface DownloadListener { void onPhotoDownloaded(File photo); void onPhotoDownloadError(int error); } public synchronized void downloadAsync(@NonNull final String downloadLink, final @Nullable DownloadListener listener) { if (TextUtils.isEmpty(downloadLink)) { throw new IllegalArgumentException("No download link given."); } if (isWorking()) { throw new IllegalStateException("Already working on something."); } URL url; try { url = new URL(downloadLink); } catch (MalformedURLException e) { Log.e("HomeStuff", "No valid download link: " + downloadLink + ": " + e); return; } mIsWorking = true; new AsyncTask<URL, Integer, Integer>() { @Override public void onCancelled(Integer result) { mIsWorking = false; } @Override protected Integer doInBackground(URL... params) { return executeDownload(params[0], this); } @Override public void onPostExecute(Integer result) { if (result != ERROR_CODE_NONE) { if (listener != null) { listener.onPhotoDownloadError(result); } } else if (listener != null) { listener.onPhotoDownloaded(getTempStorageFile()); } mIsWorking = false; } }.execute(url); } public File download(URL url, DownloadListener listener) { Integer result = executeDownload(url, null); if (result == null || result != ERROR_CODE_NONE) { if (listener != null) { listener.onPhotoDownloadError(result == null ? ERROR_CODE_DOWNLOAD_IOEXCEPTION : result); } return null; } else if (listener != null) { listener.onPhotoDownloaded(getTempStorageFile()); } return getTempStorageFile(); } private File getTempStorageFile() { File storageFile = User.getTempDirectory(); if (storageFile == null) { Log.e("Image", "No storage directory or file available"); return null; } storageFile = new File(storageFile, TEMP_DOWNLOAD_FILE_NAME); return storageFile; } private Integer executeDownload(URL url, AsyncTask<URL, Integer, Integer> task) { if (url == null) { return ERROR_CODE_URI_ILLEGAL; } InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; File storageFile; try { connection = (HttpURLConnection) url.openConnection(); connection.connect(); // expect HTTP 200 OK, so we don't mistakenly save error report // instead of the file if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { Log.e("HomeStuff", "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage() + " for url " + url); return ERROR_CODE_DOWNLOAD_RESPONSE_NOT_OK; } if (task != null && task.isCancelled()) { return null; } storageFile = getTempStorageFile(); if (storageFile == null) { return ERROR_CODE_STORAGE_NOT_AVAILABLE; } // download the file, 10kb buffer input = new BufferedInputStream(connection.getInputStream(), 1024 * 10); try { output = new FileOutputStream(storageFile, false); } catch (FileNotFoundException fnf) { Log.e("HomeStuff", "Target file on sd card not found: " + fnf + " for name " + storageFile); return ERROR_CODE_DOWNLOAD_FILE_ILLEGAL; } byte data[] = new byte[4096]; long total = 0; int count; while ((count = input.read(data)) > 0) { if (task != null && task.isCancelled()) { input.close(); output.close(); return null; } total += count; // publishing the progress.... this would require overwriting publishprogress in // a custom AsyncTask class as this is a proteted final method //if (fileLength > 0 && task != null) // only if total length is known //task.publishProgress((int) (total * 100 / (double) fileLength)); output.write(data, 0, count); } } catch (Exception e) { Log.e("HomeStuff", "Exception during download: " + e); return ERROR_CODE_DOWNLOAD_IOEXCEPTION; } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) { } if (connection != null) connection.disconnect(); } return ERROR_CODE_NONE; } public synchronized void uploadPhoto(@NonNull final SharedPreferences prefs, @NonNull final File photo, final @Nullable UploadListener listener) { if (prefs == null) { throw new NullPointerException("No shared preferences given."); } if (photo == null) { throw new IllegalArgumentException("No photo to upload."); } if (isWorking()) { throw new IllegalStateException("Already working on something."); } mIsWorking = true; new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { if (!hasAlbum()) { String originName = User.getInstance().getOriginName(); Log.d("HomeStuff", "Does not have an album, creating new one for " + originName); makeOrUpdateAlbumExecute(prefs, originName == null ? String.valueOf(System .currentTimeMillis ()) : originName); } return mController.uploadPhotoToAlbum(mAlbumHash, photo); } @Override public void onPostExecute(String result) { if (result != null) { Set<String> photoLinks = new HashSet<>(prefs.getStringSet (KEY_USER_UPLOADED_PHOTOS_HASHES, new HashSet<String>())); photoLinks.add(result); prefs.edit().putStringSet(KEY_USER_UPLOADED_PHOTOS_HASHES, photoLinks).apply(); if (listener != null) { listener.onPhotoUploaded(result, mController.makeShareLink (result)); } } else if (listener != null) { listener.onPhotoUploadFailed(ERROR_CODE_UPLOAD_FAILED); } mIsWorking = false; } }.execute(); } public synchronized void makeOrUpdateAlbum(@NonNull final SharedPreferences prefs, @NonNull final String name) { if (prefs == null) { throw new NullPointerException("No shared preferences given."); } if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("No name given for new album."); } if (isWorking()) { throw new IllegalStateException("Already working on something."); } mIsWorking = true; new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { makeOrUpdateAlbumExecute(prefs, name); return mAlbumHash; } @Override public void onPostExecute(String result) { mIsWorking = false; } }.execute(); } private void makeOrUpdateAlbumExecute(SharedPreferences prefs, String name) { if (hasAlbum()) { mAlbumHash = mController.updateAlbum(mAlbumHash, name, PhotoAlbumShareController.IS_ALBUM_PUBLIC_DEFAULT); } else { mAlbumHash = mController.makeAlbum(name); } if (hasAlbum()) { prefs.edit().putString(KEY_USER_ALBUM_HASH, mAlbumHash).apply(); } } public @Nullable URL makeDownloadLink(@NonNull Uri shared) { return mController.makeDownloadLink(shared); } }