Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

12 allow import of multiple files at once #79

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

import io.reactivex.Completable;
import io.reactivex.CompletableTransformer;
import io.reactivex.Flowable;
import io.reactivex.FlowableTransformer;
import io.reactivex.Maybe;
import io.reactivex.MaybeTransformer;
import io.reactivex.Single;
Expand Down Expand Up @@ -86,6 +88,10 @@ public class DataSource implements IServersRepository, ITellaUploadServersReposi
observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());

final private FlowableTransformer schedulersFlowableTransformer =
observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());


public static synchronized DataSource getInstance(Context context, byte[] key) {
if (dataSource == null) {
Expand Down Expand Up @@ -115,6 +121,11 @@ private <T> MaybeTransformer<T, T> applyMaybeSchedulers() {
return (MaybeTransformer<T, T>) schedulersMaybeTransformer;
}

private <T>FlowableTransformer<T, T> applyFlowableSchedulers() {
//noinspection unchecked
return (FlowableTransformer<T, T>) schedulersFlowableTransformer;
}

@Override
public Single<List<CollectServer>> listCollectServers() {
return Single.fromCallable(() -> dataSource.getServers())
Expand Down Expand Up @@ -367,6 +378,14 @@ public Single<MediaFileBundle> registerMediaFileBundle(final MediaFileBundle med
}).compose(applySchedulers());
}

@Override
public Flowable<List<MediaFileBundle>> registerMediaFileBundles(List<MediaFileBundle> mediaFileBundles) {
return Flowable.fromCallable(() -> {
registerMediaFileRecords(mediaFileBundles);
return mediaFileBundles;
}).compose(applyFlowableSchedulers());
}

public Single<List<MediaFile>> listMediaFiles(final Filter filter, final Sort sort) {
return Single.fromCallable(() -> getMediaFiles(filter, sort))
.compose(applySchedulers());
Expand Down Expand Up @@ -478,6 +497,53 @@ private MediaFile registerMediaFileRecord(MediaFile mediaFile, MediaFileThumbnai
return mediaFile;
}

private List<MediaFileBundle> registerMediaFileRecords(List<MediaFileBundle> mediaFileBundles) {
try {
database.beginTransaction();

for (MediaFileBundle mediaFileBundle: mediaFileBundles) {

MediaFile mediaFile = mediaFileBundle.getMediaFile();
MediaFileThumbnailData thumbnailData = mediaFileBundle.getMediaFileThumbnailData();

if (mediaFile.getCreated() == 0) {
mediaFile.setCreated(Util.currentTimestamp());
}

ContentValues values = new ContentValues();
values.put(D.C_PATH, mediaFile.getPath());
values.put(D.C_UID, mediaFile.getUid());
values.put(D.C_FILE_NAME, mediaFile.getFileName());
values.put(D.C_METADATA, new GsonBuilder().create().toJson(new EntityMapper().transform(mediaFile.getMetadata())));
values.put(D.C_CREATED, mediaFile.getCreated());
if (mediaFile.getDuration() > 0) {
values.put(D.C_DURATION, mediaFile.getDuration());
}
if (mediaFile.getSize() > 0) {
values.put(D.C_SIZE, mediaFile.getSize());
}
values.put(D.C_ANONYMOUS, mediaFile.isAnonymous() ? 1 : 0);
values.put(D.C_HASH, mediaFile.getHash());

//TODO should use a function that concatenation insert statements and runs them as one
mediaFile.setId(database.insert(D.T_MEDIA_FILE, null, values));

if (!MediaFileThumbnailData.NONE.equals(thumbnailData)) {
updateThumbnail(mediaFile.getId(), thumbnailData);
}

mediaFileBundle.setMediaFile(mediaFile);
mediaFileBundles.set(mediaFileBundles.indexOf(mediaFileBundle), mediaFileBundle);
}

database.setTransactionSuccessful();
} finally {
database.endTransaction();
}

return mediaFileBundles;
}

private long countDBCollectServers() {
return net.sqlcipher.DatabaseUtils.queryNumEntries(database, D.T_COLLECT_SERVER);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import rs.readahead.washington.mobile.domain.entity.MediaFile;
Expand All @@ -16,6 +17,7 @@ interface IMediaFileDeleter {

Single<MediaFile> registerMediaFile(MediaFile mediaFile, MediaFileThumbnailData thumbnailData);
Single<MediaFileBundle> registerMediaFileBundle(MediaFileBundle mediaFileBundle);
Flowable<List<MediaFileBundle>> registerMediaFileBundles(List<MediaFileBundle> mediaFileBundles);
Single<List<MediaFile>> listMediaFiles(Filter filter, Sort sort);
Maybe<MediaFileThumbnailData> getMediaFileThumbnail(long id);
Maybe<MediaFileThumbnailData> getMediaFileThumbnail(String uid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public static void startSelectMediaActivity(Activity activity, @NonNull String t
}

if (Build.VERSION.SDK_INT >= 19) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
try {
activity.startActivityForResult(intent, requestCode);
Expand All @@ -123,12 +124,12 @@ public static void startSelectMediaActivity(Activity activity, @NonNull String t
}
}

//if (Build.VERSION.SDK_INT >= 18) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
Timber.d("+++++ get multiple");
// }

intent.setAction(Intent.ACTION_GET_CONTENT);
//TODO make multiple selection work in API 17 & 18
//if (Build.VERSION.SDK_INT <= 18) {
intent.setAction(Intent.ACTION_SEND_MULTIPLE)
.setAction(Intent.ACTION_GET_CONTENT);
Timber.d("+++++ get multiple");
//}

try {
activity.startActivityForResult(intent, requestCode);
Expand Down Expand Up @@ -236,6 +237,47 @@ public static MediaFileBundle importPhotoUri(Context context, Uri uri) throws Ex
return mediaFileBundle;
}

public static List<MediaFileBundle> importPhotoUris(Context context, List<Uri> uris) throws Exception {
List<MediaFileBundle> mediaFileBundles = new ArrayList<>();

for (Uri uri : uris) {

MediaFileBundle mediaFileBundle = new MediaFileBundle();

MediaFile mediaFile = MediaFile.newJpeg();
mediaFileBundle.setMediaFile(mediaFile);

InputStream input = context.getContentResolver().openInputStream(uri);

Bitmap bm = BitmapFactory.decodeStream(input);
input = context.getContentResolver().openInputStream(uri);
bm = modifyOrientation(bm, input);
Bitmap thumb = ThumbnailUtils.extractThumbnail(bm, bm.getWidth() / 10, bm.getHeight() / 10); // todo: make this smarter and global

ByteArrayOutputStream stream = new ByteArrayOutputStream();
if (thumb.compress(Bitmap.CompressFormat.JPEG, 100, stream)) {
mediaFileBundle.setMediaFileThumbnailData(new MediaFileThumbnailData(stream.toByteArray()));
}

// todo: killing too much quality like this?

DigestOutputStream os = MediaFileHandler.getOutputStream(context, mediaFile);

if (os == null) throw new NullPointerException();

if (!bm.compress(Bitmap.CompressFormat.JPEG, 100, os)) {
throw new Exception("JPEG compression failed");
}

mediaFile.setHash(StringUtils.hexString(os.getMessageDigest().digest()));
mediaFile.setSize(getSize(context, mediaFile));

mediaFileBundles.add(mediaFileBundle);
}

return mediaFileBundles;
}

public static MediaFileBundle saveJpegPhoto(@NonNull Context context, @NonNull byte[] jpegPhoto) throws Exception {
MediaFileBundle mediaFileBundle = new MediaFileBundle();

Expand Down Expand Up @@ -328,6 +370,49 @@ public static MediaFileBundle importVideoUri(Context context, Uri uri) throws Ex
return mediaFileBundle;
}

public static List<MediaFileBundle> importVideoUris(Context context, List<Uri> uris) throws Exception {
List<MediaFileBundle> mediaFileBundles = new ArrayList<>();

for (Uri uri : uris) {
MediaFileBundle mediaFileBundle = new MediaFileBundle();

MediaFile mediaFile = MediaFile.newMp4();
mediaFileBundle.setMediaFile(mediaFile);

MediaMetadataRetriever retriever = new MediaMetadataRetriever();

try {
retriever.setDataSource(context, uri);

// duration
String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
mediaFile.setDuration(Long.parseLong(time));

// thumbnail
byte[] thumb = getThumbByteArray(retriever.getFrameAtTime());
if (thumb != null) {
mediaFileBundle.setMediaFileThumbnailData(new MediaFileThumbnailData(thumb));
}
} catch (Exception e) {
FirebaseCrashlytics.getInstance().recordException(e);
Timber.e(e, MediaFileHandler.class.getName());
} finally {
try {
retriever.release();
} catch (Exception ignore) {
}
}

InputStream is = context.getContentResolver().openInputStream(uri);

copyToMediaFileStream(context, mediaFile, is);

mediaFileBundles.add(mediaFileBundle);
}

return mediaFileBundles;
}

public static MediaFileBundle saveMp4Video(Context context, File video) {
FileInputStream vis = null;
InputStream is = null;
Expand Down Expand Up @@ -435,6 +520,12 @@ private static byte[] getThumbByteArray(@Nullable Bitmap frame) {
return null;
}

public Observable<List<MediaFileBundle>> registerMediaFiles(final List<MediaFileBundle> mediaFileBundles) {
return cacheWordDataSource.getDataSource()
.flatMap((Function<DataSource, ObservableSource<List<MediaFileBundle>>>) dataSource ->
dataSource.registerMediaFileBundles(mediaFileBundles).toObservable());
}

@SuppressWarnings("UnusedReturnValue")
static boolean deleteFile(Context context, @NonNull MediaFile mediaFile) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import rs.readahead.washington.mobile.domain.entity.MediaFile;
import rs.readahead.washington.mobile.domain.entity.TellaUploadServer;
import rs.readahead.washington.mobile.domain.repository.IMediaFileRecordRepository;
import rs.readahead.washington.mobile.media.MediaFileBundle;
import rs.readahead.washington.mobile.presentation.entity.MediaFileThumbnailData;


Expand All @@ -19,10 +20,12 @@ public interface IView {
void onGetFilesSuccess(List<MediaFile> files);
void onGetFilesError(Throwable error);
void onMediaImported(MediaFile mediaFile, MediaFileThumbnailData thumbnailData);
void onMediaImported(List<MediaFileBundle> mediaFileBundles);
void onImportError(Throwable error);
void onImportStarted();
void onImportEnded();
void onMediaFilesAdded(MediaFile mediaFile);
void onMediaFilesAdded(List<MediaFileBundle> mediaFileBundles);
void onMediaFilesAddingError(Throwable error);
void onMediaFilesDeleted(int num);
void onMediaFilesDeletionError(Throwable throwable);
Expand All @@ -40,8 +43,11 @@ public interface IView {
public interface IPresenter extends IBasePresenter {
void getFiles(IMediaFileRecordRepository.Filter filter, IMediaFileRecordRepository.Sort sort);
void importImage(Uri uri);
void importImages(List<Uri> uris);
void importVideo(Uri uri);
void importVideos(List<Uri> uris);
void addNewMediaFile(MediaFile mediaFile, MediaFileThumbnailData thumbnailData);
void addNewMediaFiles(List<MediaFileBundle> mediaFileBundles);
void deleteMediaFiles(List<MediaFile> mediaFiles);
void exportMediaFiles(List<MediaFile> mediaFiles);
void countTUServers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import rs.readahead.washington.mobile.data.database.KeyDataSource;
import rs.readahead.washington.mobile.domain.entity.MediaFile;
import rs.readahead.washington.mobile.domain.repository.IMediaFileRecordRepository;
import rs.readahead.washington.mobile.media.MediaFileBundle;
import rs.readahead.washington.mobile.media.MediaFileHandler;
import rs.readahead.washington.mobile.mvp.contract.IGalleryPresenterContract;
import rs.readahead.washington.mobile.presentation.entity.MediaFileThumbnailData;
Expand Down Expand Up @@ -80,6 +81,23 @@ public void importImage(final Uri uri) {
);
}

@Override
public void importImages(final List<Uri> uris) {
disposables.add(Observable
.fromCallable(() -> MediaFileHandler.importPhotoUris(view.getContext(), uris))
.subscribeOn(Schedulers.computation())
.doOnSubscribe(disposable -> view.onImportStarted())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> view.onImportEnded())
.subscribe(mediaHolder ->
view.onMediaImported(mediaHolder),
throwable -> {
FirebaseCrashlytics.getInstance().recordException(throwable);
view.onImportError(throwable);
})
);
}

@Override
public void importVideo(final Uri uri) {
disposables.add(Observable
Expand All @@ -95,6 +113,22 @@ public void importVideo(final Uri uri) {
);
}

@Override
public void importVideos(final List<Uri> uris) {
disposables.add(Observable
.fromCallable(() -> MediaFileHandler.importVideoUris(view.getContext(), uris))
.subscribeOn(Schedulers.computation())
.doOnSubscribe(disposable -> view.onImportStarted())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> view.onImportEnded())
.subscribe(mediaHolder -> view.onMediaImported(mediaHolder),
throwable -> {
FirebaseCrashlytics.getInstance().recordException(throwable);
view.onImportError(throwable);
})
);
}

@Override
public void addNewMediaFile(MediaFile mediaFile, MediaFileThumbnailData thumbnailData) {
disposables.add(mediaFileHandler.registerMediaFile(mediaFile, thumbnailData)
Expand All @@ -107,6 +141,18 @@ public void addNewMediaFile(MediaFile mediaFile, MediaFileThumbnailData thumbnai
);
}

@Override
public void addNewMediaFiles(List<MediaFileBundle> mediaFileBundles) {
disposables.add(mediaFileHandler.registerMediaFiles(mediaFileBundles)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mediaFileBundles1 -> view.onMediaFilesAdded(mediaFileBundles1), throwable -> {
FirebaseCrashlytics.getInstance().recordException(throwable);
view.onMediaFilesAddingError(throwable);
})
);
}

@Override
public void deleteMediaFiles(final List<MediaFile> mediaFiles) {
disposables.add(keyDataSource.getDataSource()
Expand Down
Loading