package com.novoda.downloadmanager.lib; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; import com.novoda.downloadmanager.lib.logger.LLog; import com.novoda.notils.string.QueryUtils; import com.novoda.notils.string.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.novoda.downloadmanager.lib.Constants.UNKNOWN_BYTE_SIZE; import static com.novoda.downloadmanager.lib.DownloadContract.Downloads.*; class DownloadsRepository { private static final int TRUE_THIS_IS_CLEARER_NOW = 1; private final SystemFacade systemFacade; private final ContentResolver contentResolver; private final DownloadInfoCreator downloadInfoCreator; private final DownloadsUriProvider downloadsUriProvider; public DownloadsRepository(SystemFacade systemFacade, ContentResolver contentResolver, DownloadInfoCreator downloadInfoCreator, DownloadsUriProvider downloadsUriProvider) { this.systemFacade = systemFacade; this.contentResolver = contentResolver; this.downloadInfoCreator = downloadInfoCreator; this.downloadsUriProvider = downloadsUriProvider; } public List<FileDownloadInfo> getAllDownloads() { Cursor downloadsCursor = contentResolver.query( downloadsUriProvider.getAllDownloadsUri(), null, null, null, DownloadContract.Batches._ID + " ASC" ); try { List<FileDownloadInfo> downloads = new ArrayList<>(); FileDownloadInfo.Reader reader = new FileDownloadInfo.Reader(contentResolver, downloadsCursor); while (downloadsCursor.moveToNext()) { downloads.add(downloadInfoCreator.create(reader)); } return downloads; } finally { downloadsCursor.close(); } } public FileDownloadInfo getDownloadFor(long id) { Uri uri = ContentUris.withAppendedId(downloadsUriProvider.getAllDownloadsUri(), id); Cursor downloadsCursor = contentResolver.query(uri, null, null, null, null); try { downloadsCursor.moveToFirst(); FileDownloadInfo.Reader reader = new FileDownloadInfo.Reader(contentResolver, downloadsCursor); return downloadInfoCreator.create(reader); } finally { downloadsCursor.close(); } } /** * Query and return status of requested download. */ public int getDownloadStatus(long id) { final Cursor cursor = contentResolver.query( ContentUris.withAppendedId(downloadsUriProvider.getAllDownloadsUri(), id), new String[]{DownloadContract.Downloads.COLUMN_STATUS}, null, null, null ); try { if (cursor.moveToFirst()) { return cursor.getInt(0); } else { // TODO: increase strictness of value returned for unknown // downloads; this is safe default for now. return DownloadStatus.PENDING; } } finally { cursor.close(); } } public void moveDownloadsStatusTo(List<Long> ids, int status) { if (ids.isEmpty()) { return; } ContentValues values = new ContentValues(1); values.put(DownloadContract.Downloads.COLUMN_STATUS, status); String selectionPlaceholders = QueryUtils.createSelectionPlaceholdersOfSize(ids.size()); String where = DownloadContract.Downloads._ID + " IN (" + selectionPlaceholders + ")"; String[] selectionArgs = StringUtils.toStringArray(ids.toArray()); contentResolver.update(downloadsUriProvider.getAllDownloadsUri(), values, where, selectionArgs); } public void pauseDownloadWithBatchId(long batchId) { ContentValues values = new ContentValues(1); values.put(DownloadContract.Downloads.COLUMN_CONTROL, DownloadsControl.CONTROL_PAUSED); String where = DownloadContract.Downloads.COLUMN_BATCH_ID + "= ? AND " + DownloadContract.Downloads.COLUMN_STATUS + " != ?"; String[] selectionArgs = {String.valueOf(batchId), String.valueOf(DownloadStatus.SUCCESS)}; contentResolver.update(downloadsUriProvider.getAllDownloadsUri(), values, where, selectionArgs); } public void resumeDownloadWithBatchId(long batchId) { ContentValues values = new ContentValues(2); values.put(DownloadContract.Downloads.COLUMN_CONTROL, DownloadsControl.CONTROL_RUN); values.put(DownloadContract.Downloads.COLUMN_STATUS, DownloadStatus.PENDING); String where = DownloadContract.Downloads.COLUMN_BATCH_ID + "= ? AND " + DownloadContract.Downloads.COLUMN_STATUS + " != ?"; String[] selectionArgs = {String.valueOf(batchId), String.valueOf(DownloadStatus.SUCCESS)}; contentResolver.update(downloadsUriProvider.getAllDownloadsUri(), values, where, selectionArgs); } public void updateDownload(FileDownloadInfo downloadInfo, String filename, String mimeType, int retryAfter, String requestUri, int finalStatus, String errorMsg, int numFailed) { ContentValues values = new ContentValues(8); values.put(COLUMN_STATUS, finalStatus); values.put(DownloadContract.Downloads.COLUMN_DATA, filename); values.put(DownloadContract.Downloads.COLUMN_MIME_TYPE, mimeType); values.put(DownloadContract.Downloads.COLUMN_LAST_MODIFICATION, systemFacade.currentTimeMillis()); values.put(DownloadContract.Downloads.COLUMN_FAILED_CONNECTIONS, numFailed); values.put(Constants.RETRY_AFTER_X_REDIRECT_COUNT, retryAfter); if (!TextUtils.equals(downloadInfo.getUri(), requestUri)) { values.put(DownloadContract.Downloads.COLUMN_URI, requestUri); } if (DownloadStatus.isCompleted(finalStatus)) { values.put(DownloadContract.Downloads.COLUMN_CONTROL, DownloadsControl.CONTROL_RUN); } // save the error message. could be useful to developers. if (!TextUtils.isEmpty(errorMsg)) { values.put(DownloadContract.Downloads.COLUMN_ERROR_MSG, errorMsg); } contentResolver.update(downloadInfo.getAllDownloadsUri(), values, null, null); } public void setDownloadRunning(FileDownloadInfo downloadInfo) { ContentValues contentValues = new ContentValues(1); contentValues.put(COLUMN_STATUS, DownloadStatus.RUNNING); contentResolver.update(downloadInfo.getAllDownloadsUri(), contentValues, null, null); } public void pauseDownloadWithSize(FileDownloadInfo downloadInfo, long currentBytes, long totalBytes) { ContentValues values = new ContentValues(); values.put(COLUMN_STATUS, DownloadStatus.PAUSED_BY_APP); values.put(COLUMN_CURRENT_BYTES, currentBytes); values.put(COLUMN_TOTAL_BYTES, totalBytes); contentResolver.update(downloadInfo.getAllDownloadsUri(), values, null, null); } public void updateDownloadEndOfStream(FileDownloadInfo downloadInfo, long currentBytes, long contentLength) { ContentValues values = new ContentValues(2); values.put(COLUMN_CURRENT_BYTES, currentBytes); if (contentLength == UNKNOWN_BYTE_SIZE) { values.put(COLUMN_TOTAL_BYTES, currentBytes); } contentResolver.update(downloadInfo.getAllDownloadsUri(), values, null, null); } public void updateDatabaseFromHeaders(FileDownloadInfo downloadInfo, String filename, String headerETag, String mimeType, long totalBytes) { ContentValues values = new ContentValues(4); values.put(DownloadContract.Downloads.COLUMN_DATA, filename); if (headerETag != null) { values.put(Constants.ETAG, headerETag); } if (mimeType != null) { values.put(DownloadContract.Downloads.COLUMN_MIME_TYPE, mimeType); } values.put(COLUMN_TOTAL_BYTES, totalBytes); contentResolver.update(downloadInfo.getAllDownloadsUri(), values, null, null); } public void deleteDownload(Uri downloadUri) { ContentValues values = new ContentValues(1); values.put(DownloadContract.Downloads.COLUMN_DELETED, TRUE_THIS_IS_CLEARER_NOW); contentResolver.update(downloadUri, values, null, null); } public void setDownloadSubmitted(FileDownloadInfo info) { ContentValues contentValues = new ContentValues(1); contentValues.put(DownloadContract.Downloads.COLUMN_STATUS, DownloadStatus.SUBMITTED); contentResolver.update(info.getAllDownloadsUri(), contentValues, null, null); } public List<String> getCurrentDownloadingOrSubmittedBatchIds() { String[] projection = {"DISTINCT " + DownloadContract.Downloads.COLUMN_BATCH_ID}; //Can't pass null as selection argument String where = "(" + DownloadContract.Downloads.COLUMN_CONTROL + " is null or " + DownloadContract.Downloads.COLUMN_CONTROL + " = ? ) " + "AND (" + DownloadContract.Downloads.COLUMN_STATUS + " = ? or " + DownloadContract.Downloads.COLUMN_STATUS + " = ?)) " + "GROUP BY (" + DownloadContract.Downloads.COLUMN_BATCH_ID; // GROUP BY does not need a closing parenthesis because is a hack, Helpers.validateSelection will warn with // java.lang.IllegalArgumentException: unrecognized column or keyword but the group by works as expected // reference http://stackoverflow.com/questions/2315203/android-distinct-and-groupby-in-contentresolver String[] selectionArgs = { String.valueOf(DownloadsControl.CONTROL_RUN), String.valueOf(DownloadStatus.RUNNING), String.valueOf(DownloadStatus.SUBMITTED) }; Cursor cursor = null; try { cursor = contentResolver.query( downloadsUriProvider.getAllDownloadsUri(), projection, where, selectionArgs, null ); if (cursor == null || cursor.getCount() == 0) { return Collections.emptyList(); } List<String> batchIdList = new ArrayList<>(); while (cursor.moveToNext()) { batchIdList.add(cursor.getString(0)); } return batchIdList; } finally { if (cursor != null) { cursor.close(); } } } /** * @return Number of rows updated */ public int updateRunningOrSubmittedDownloadsToPending() { ContentValues values = new ContentValues(2); values.put(DownloadContract.Downloads.COLUMN_CONTROL, DownloadsControl.CONTROL_RUN); values.put(DownloadContract.Downloads.COLUMN_STATUS, DownloadStatus.PENDING); //Can't pass null as selection argument String where = "(" + DownloadContract.Downloads.COLUMN_CONTROL + " is null or " + DownloadContract.Downloads.COLUMN_CONTROL + " = ? ) " + " AND ( " + DownloadContract.Downloads.COLUMN_STATUS + " = ? or " + DownloadContract.Downloads.COLUMN_STATUS + " = ? )"; String[] selectionArgs = { String.valueOf(DownloadsControl.CONTROL_RUN), String.valueOf(DownloadStatus.RUNNING), String.valueOf(DownloadStatus.SUBMITTED) }; return contentResolver.update(downloadsUriProvider.getAllDownloadsUri(), values, where, selectionArgs); } interface DownloadInfoCreator { DownloadInfoCreator NON_FUNCTIONAL = new NonFunctional(); FileDownloadInfo create(FileDownloadInfo.Reader reader); class NonFunctional implements DownloadInfoCreator { @Override public FileDownloadInfo create(FileDownloadInfo.Reader reader) { LLog.w("DownloadInfoCreator.NonFunctional.create()"); return null; } } } }