Browse Source

Improved downloading (overhauled)

pull/24/head
Ezerous 7 years ago
parent
commit
265b0705bd
No known key found for this signature in database GPG Key ID: 262B2954BBA319E3
  1. 5
      app/build.gradle
  2. 13
      app/src/main/AndroidManifest.xml
  3. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  4. 45
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  5. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  6. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  7. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  8. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  9. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  10. 73
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  11. 9
      app/src/main/java/gr/thmmy/mthmmy/model/Download.java
  12. 2
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  13. 73
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java
  14. 88
      app/src/main/java/gr/thmmy/mthmmy/services/downloads/DownloadsReceiver.java
  15. 225
      app/src/main/java/gr/thmmy/mthmmy/services/downloads/DownloadsService.java
  16. 10
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  17. 26
      app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java
  18. 7
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java
  19. 48
      app/src/main/res/layout/download_prompt_dialog.xml
  20. 11
      app/src/main/res/values/strings.xml
  21. 8
      app/src/main/res/xml/provider_paths.xml
  22. 1
      build.gradle

5
app/build.gradle

@ -4,7 +4,6 @@ apply plugin: 'io.fabric'
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
defaultConfig {
vectorDrawables.useSupportLibrary = true
@ -35,9 +34,9 @@ dependencies {
implementation 'com.android.support:support-v4:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.google.firebase:firebase-core:16.0.0'
implementation 'com.google.firebase:firebase-core:16.0.1'
implementation 'com.google.firebase:firebase-messaging:17.0.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

13
app/src/main/AndroidManifest.xml

@ -116,9 +116,6 @@
android:resource="@xml/provider_paths" />
</provider>
<service
android:name=".services.downloads.DownloadsService"
android:exported="false" />
<service
android:name=".services.NotificationService"
android:exported="false">
@ -126,16 +123,6 @@
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<receiver
android:name=".services.downloads.DownloadsReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.media.action.ACTION_DOWNLOAD" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>

4
app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java

@ -25,8 +25,8 @@ import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.Topic;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
@ -298,7 +298,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
@Override
protected void postParsing(ResultCode result) {
protected void postExecution(ResultCode result) {
//TODO if (result == ResultCode.SUCCESS)...
if (boardTitle == null || Objects.equals(boardTitle, "")
|| !Objects.equals(boardTitle, parsedTitle)) {

45
app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java

@ -20,11 +20,15 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Download;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
@ -158,13 +162,16 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
/**
* An {@link ParseTask} that handles asynchronous fetching of a downloads page and parsing it's
* data. {@link ParseTask#postParsing(ResultCode) postParsing} method calls {@link RecyclerView#swapAdapter}
* data. {@link ParseTask#postExecution(ResultCode) postExecution} method calls {@link RecyclerView#swapAdapter}
* to build graphics.
* <p>
* <p>Calling TopicTask's {@link ParseTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
private class ParseDownloadPageTask extends ParseTask {
private Download.DownloadItemType type;
private Download download;
@Override
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -173,6 +180,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
@Override
protected void parse(Document downloadPage) throws ParseException {
try{
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text();
@ -181,11 +189,10 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1);
}
Download.DownloadItemType type;
if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.
PageCategory.DOWNLOADS_CATEGORY))
if (ThmmyPage.resolvePageCategory(Uri.parse(url)).is(ThmmyPage.PageCategory.DOWNLOADS_CATEGORY))
type = Download.DownloadItemType.DOWNLOADS_CATEGORY;
else type = Download.DownloadItemType.DOWNLOADS_FILE;
else
type = Download.DownloadItemType.DOWNLOADS_FILE;
Elements pages = downloadPage.select("a.navPages");
if (pages != null) {
@ -223,19 +230,39 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
}
}
} else {
parsedDownloads.add(new Download(type,
download = new Download(type,
rows.select("b>a").first().attr("href"),
rows.select("b>a").first().text(),
rows.select("div.smalltext:not(:has(a))").text(),
rows.select("span:not(:has(a))").first().text(),
false,
rows.select("span:has(a)").first().text()));
rows.select("span:has(a)").first().text());
parsedDownloads.add(download);
}
}catch(Exception e){
throw new ParseException("Parsing failed (DownloadsActivity)");
}
}
@Override
protected void postParsing() {
if (type == Download.DownloadItemType.DOWNLOADS_FILE) {
OkHttpClient client = BaseApplication.getInstance().getClient();
String fileName = null;
try {
Response response = client.newCall(new Request.Builder().url(download.getUrl()).build()).execute();
String contentDisposition = response.headers("Content-Disposition").toString(); //check if link provides an attachment
if (contentDisposition.contains("attachment"))
fileName = contentDisposition.split("\"")[1];
download.setFileName(fileName);
} catch (Exception e) {
Timber.e(e, "Couldn't extract fileName.");
}
}
}
@Override
protected void postParsing(ResultCode result) {
protected void postExecution(ResultCode result) {
if (downloadsTitle != null && !downloadsTitle.equals("")
&& !downloadsTitle.equals("Αρχεία για λήψη")
&& toolbar.getTitle() != downloadsTitle)

4
app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java

@ -127,8 +127,8 @@ class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public void onClick(View view) {
try {
((BaseActivity) context).launchDownloadService(new ThmmyFile(
new URL(download.getUrl()), null, null));
((BaseActivity) context).downloadFile(new ThmmyFile(
new URL(download.getUrl()), download.getFileName(), null));
} catch (MalformedURLException e) {
e.printStackTrace();
}

4
app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java

@ -27,8 +27,8 @@ import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.HttpUrl;
import okhttp3.Request;
@ -215,7 +215,7 @@ public class ForumFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
forumAdapter.notifyParentDataSetChanged(false);

4
app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java

@ -24,8 +24,8 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import timber.log.Timber;
@ -190,7 +190,7 @@ public class RecentFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
{
topicSummaries.clear();

4
app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java

@ -25,8 +25,8 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import gr.thmmy.mthmmy.utils.parsing.ParseException;
import gr.thmmy.mthmmy.utils.parsing.ParseTask;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import timber.log.Timber;
@ -204,7 +204,7 @@ public class UnreadFragment extends BaseFragment {
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
protected void postExecution(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
unreadAdapter.notifyDataSetChanged();

2
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java

@ -238,7 +238,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
attached.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
((BaseActivity) context).launchDownloadService(attachedFile);
((BaseActivity) context).downloadFile(attachedFile);
}
});

73
app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java

@ -7,16 +7,21 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetDialog;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.messaging.FirebaseMessaging;
@ -32,6 +37,7 @@ import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import java.io.File;
import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
@ -43,9 +49,11 @@ import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.downloads.DownloadsService;
import gr.thmmy.mthmmy.services.DownloadHelper;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.FileUtils;
import okhttp3.OkHttpClient;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE;
@ -53,6 +61,8 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType;
public abstract class BaseActivity extends AppCompatActivity {
// Client & Cookies
@ -600,7 +610,7 @@ public abstract class BaseActivity extends AppCompatActivity {
, @NonNull int[] grantResults) {
switch (permsRequestCode) {
case PERMISSIONS_REQUEST_CODE:
launchDownloadService();
downloadFile();
break;
}
}
@ -609,9 +619,9 @@ public abstract class BaseActivity extends AppCompatActivity {
//----------------------------------DOWNLOAD----------------------
private ThmmyFile tempThmmyFile;
public void launchDownloadService(ThmmyFile thmmyFile) {
public void downloadFile(ThmmyFile thmmyFile) {
if (checkPerms())
DownloadsService.startActionDownload(this, thmmyFile.getFileUrl().toString());
prepareDownload(thmmyFile);
else {
tempThmmyFile = thmmyFile;
requestPerms();
@ -619,15 +629,64 @@ public abstract class BaseActivity extends AppCompatActivity {
}
//Uses temp file - called after permission grant
private void launchDownloadService() {
private void downloadFile() {
if (checkPerms())
DownloadsService.startActionDownload(this, tempThmmyFile.getFileUrl().toString());
prepareDownload(tempThmmyFile);
}
private void prepareDownload(ThmmyFile thmmyFile){
String fileName = thmmyFile.getFilename();
if(FileUtils.fileNameExists(fileName))
openDownloadPrompt(thmmyFile);
else
DownloadHelper.enqueueDownload(thmmyFile);
}
private void openDownloadPrompt(final ThmmyFile thmmyFile) {
View view = getLayoutInflater().inflate(R.layout.download_prompt_dialog, null);
final BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(view);
TextView downloadPromptTextView = view.findViewById(R.id.downloadPromptTextView);
downloadPromptTextView.setText(getString(R.string.downloadPromptText,thmmyFile.getFilename()));
Button cancelButton = view.findViewById(R.id.cancel);
Button openButton = view.findViewById(R.id.open);
Button downloadButton = view.findViewById(R.id.download);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
openButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
try{
String fileName = thmmyFile.getFilename();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri fileUri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".provider", new File(SAVE_DIR, fileName));
intent.setDataAndType(fileUri, getMimeType(fileName));
BaseActivity.this.startActivity(intent);
}catch (Exception e){
Timber.e(e,"Couldn't open downloaded file...");
Toast.makeText(BaseActivity.this, "Couldn't open file...", Toast.LENGTH_SHORT).show();
}
}
});
downloadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
DownloadHelper.enqueueDownload(thmmyFile);
}
});
dialog.show();
}
//----------------------------------MISC----------------------
protected void setMainActivity(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
}

9
app/src/main/java/gr/thmmy/mthmmy/model/Download.java

@ -6,6 +6,7 @@ public class Download {
private final String url, title, subTitle, statNumbers, extraInfo;
private final boolean hasSubCategory;
private final DownloadItemType type;
private String fileName;
public Download() {
type = null;
@ -55,4 +56,12 @@ public class Download {
public boolean hasSubCategory() {
return hasSubCategory;
}
public String getFileName(){
return fileName;
}
public void setFileName(String fileName){
this.fileName = fileName;
}
}

2
app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java

@ -3,8 +3,6 @@ package gr.thmmy.mthmmy.model;
import android.net.Uri;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import timber.log.Timber;

73
app/src/main/java/gr/thmmy/mthmmy/services/DownloadHelper.java

@ -0,0 +1,73 @@
package gr.thmmy.mthmmy.services;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.ThmmyFile;
import okhttp3.Cookie;
import timber.log.Timber;
import static gr.thmmy.mthmmy.utils.FileUtils.getMimeType;
/**
* Not an actual service, but simply a helper class that adds a download to the queue of Android's
* DownloadManager system service.
*/
public class DownloadHelper {
public static final File SAVE_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
public static void enqueueDownload(ThmmyFile thmmyFile){
Context applicationContext = BaseApplication.getInstance().getApplicationContext();
Toast.makeText(applicationContext, "Download started!", Toast.LENGTH_SHORT).show();
try {
String fileName = renameFileIfExists(thmmyFile.getFilename());
Uri downloadURI = Uri.parse(thmmyFile.getFileUrl().toString());
DownloadManager downloadManager = (DownloadManager)applicationContext.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(downloadURI);
Cookie thmmyCookie = BaseApplication.getInstance().getSessionManager().getThmmyCookie();
request.addRequestHeader("Cookie", thmmyCookie.name() + "=" + thmmyCookie.value());
request.setTitle(fileName);
request.setMimeType(getMimeType(fileName));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(SAVE_DIR.getName(), fileName);
request.allowScanningByMediaScanner();
downloadManager.enqueue(request);
} catch (Exception e) {
Toast.makeText(applicationContext, "Download failed...", Toast.LENGTH_SHORT).show();
Timber.e(e, "Exception while enqueuing download.");
}
}
private static String renameFileIfExists(String originalFileName) {
final String dirPath = SAVE_DIR.getAbsolutePath();
File file = new File(dirPath, originalFileName);
String nameFormat;
String[] tokens = originalFileName.split("\\.(?=[^.]+$)");
if (tokens.length != 2) {
Timber.w("Couldn't get file extension...");
nameFormat = originalFileName + "(%d)";
} else
nameFormat = tokens[0] + "-%d." + tokens[1];
for (int i = 1; ; i++) {
if (!file.isFile())
break;
file = new File(dirPath, String.format(nameFormat, i));
}
return file.getName();
}
}

88
app/src/main/java/gr/thmmy/mthmmy/services/downloads/DownloadsReceiver.java

@ -1,88 +0,0 @@
package gr.thmmy.mthmmy.services.downloads;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.webkit.MimeTypeMap;
import java.io.File;
import timber.log.Timber;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.ACTION_DOWNLOAD;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.COMPLETED;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_DOWNLOAD_ID;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_DOWNLOAD_STATE;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_FILE_NAME;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_NOTIFICATION_TEXT;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_NOTIFICATION_TICKER;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.EXTRA_NOTIFICATION_TITLE;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.SAVE_DIR;
import static gr.thmmy.mthmmy.services.downloads.DownloadsService.STARTED;
public class DownloadsReceiver extends BroadcastReceiver {
private static final String NOTIFICATION_TAG = "DOWNLOADS";
private static final String DOWNLOADS_CHANNEL_ID = "Downloads";
private static final String DOWNLOADS_CHANNEL_NAME = "Downloads";
public DownloadsReceiver() {}
@Override
public void onReceive(Context context, Intent intent) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, DOWNLOADS_CHANNEL_ID);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (intent.getAction().equals(ACTION_DOWNLOAD)) {
Bundle extras = intent.getExtras();
int id = extras.getInt(EXTRA_DOWNLOAD_ID);
String state = extras.getString(EXTRA_DOWNLOAD_STATE, "NONE");
String title = extras.getString(EXTRA_NOTIFICATION_TITLE);
String text = extras.getString(EXTRA_NOTIFICATION_TEXT);
String ticker = extras.getString(EXTRA_NOTIFICATION_TICKER);
notificationBuilder.setContentTitle(title)
.setContentText(text)
.setTicker(ticker)
.setAutoCancel(true);
if (state.equals(STARTED))
notificationBuilder.setOngoing(true)
.setSmallIcon(android.R.drawable.stat_sys_download);
else if (state.equals(COMPLETED)) {
String fileName = extras.getString(EXTRA_FILE_NAME, "NONE");
File file = new File(SAVE_DIR, fileName);
if (file.exists()) {
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()));
Intent chooserIntent = new Intent(Intent.ACTION_VIEW);
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
chooserIntent.setDataAndType(Uri.fromFile(file), type);
Intent chooser = Intent.createChooser(chooserIntent, "Open With...");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT);
notificationBuilder.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download_done);
} else
Timber.w("File doesn't exist.");
}
// Since Android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
notificationManager.createNotificationChannel(new NotificationChannel(DOWNLOADS_CHANNEL_ID, DOWNLOADS_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
notificationManager.notify(NOTIFICATION_TAG, id, notificationBuilder.build());
}
}
}

225
app/src/main/java/gr/thmmy/mthmmy/services/downloads/DownloadsService.java

@ -1,225 +0,0 @@
package gr.thmmy.mthmmy.services.downloads;
import android.app.DownloadManager;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import timber.log.Timber;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
*/
public class DownloadsService extends IntentService {
private static final String TAG = "DownloadsService";
private static int sDownloadId = 0;
private DownloadsReceiver receiver;
public static final String SAVE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mthmmy";
public static final String ACTION_DOWNLOAD = "gr.thmmy.mthmmy.services.action.DOWNLOAD";
public static final String EXTRA_DOWNLOAD_URL = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_URL";
public static final String EXTRA_DOWNLOAD_ID = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_ID";
public static final String EXTRA_DOWNLOAD_STATE = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_STATE";
public static final String EXTRA_FILE_NAME = "gr.thmmy.mthmmy.services.extra.FILE_NAME";
public static final String EXTRA_NOTIFICATION_TITLE = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TITLE";
public static final String EXTRA_NOTIFICATION_TEXT = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TEXT";
public static final String EXTRA_NOTIFICATION_TICKER = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TICKER";
public static final String STARTED = "Started";
public static final String COMPLETED = "Completed";
public static final String FAILED = "Failed";
public DownloadsService() {
super("DownloadsService");
}
@Override
public void onCreate() {
super.onCreate();
final IntentFilter filter = new IntentFilter(DownloadsService.ACTION_DOWNLOAD);
receiver = new DownloadsReceiver();
registerReceiver(receiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
this.unregisterReceiver(receiver);
}
/**
* Starts this service to perform action Download with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
public static void startActionDownload(Context context, String downloadUrl) {
Intent intent = new Intent(context, DownloadsService.class);
intent.setAction(ACTION_DOWNLOAD);
intent.putExtra(EXTRA_DOWNLOAD_URL, downloadUrl);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_DOWNLOAD.equals(action)) {
final String downloadLink = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
handleActionDownload(downloadLink);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionDownload(String downloadLink) {
OkHttpClient client = BaseApplication.getInstance().getClient();
BufferedSink sink = null;
String fileName = "file";
int downloadId = sDownloadId;
sDownloadId++;
try {
Request request = new Request.Builder().url(downloadLink).build();
Response response = client.newCall(request).execute();
String contentDisposition = response.headers("Content-Disposition").toString(); //check if link provides an attachment
if (contentDisposition.contains("attachment")) {
fileName = contentDisposition.split("\"")[1];
File dirPath = new File(SAVE_DIR);
if (!dirPath.isDirectory()) {
if (dirPath.mkdirs())
Timber.i("mTHMMY's directory created successfully!");
else
Timber.e("Couldn't create mTHMMY's directory...");
}
String nameFormat;
String[] tokens = fileName.split("\\.(?=[^\\.]+$)");
if (tokens.length != 2) {
Timber.w("Couldn't get file extension...");
nameFormat = fileName + "(%d)";
} else
nameFormat = tokens[0] + "(%d)." + tokens[1];
File file = new File(dirPath, fileName);
for (int i = 1; ; i++) {
if (!file.exists())
break;
file = new File(dirPath, String.format(nameFormat, i));
}
fileName = file.getName();
Timber.v("Started saving file %s", fileName);
sendNotification(downloadId, STARTED, fileName);
sink = Okio.buffer(Okio.sink(file));
sink.writeAll(response.body().source());
sink.flush();
Timber.i("Download OK!");
sendNotification(downloadId, COMPLETED, fileName);
// Register download
DownloadManager mManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long length = file.length();
mManager.addCompletedDownload(fileName, fileName, false, getMimeType(file), SAVE_DIR +File.separator+ fileName, length, false);
} else
Timber.e("No attachment in response!");
} catch (FileNotFoundException e) {
Timber.i("Download failed...");
Timber.e(e, "FileNotFound");
sendNotification(downloadId, FAILED, fileName);
} catch (IOException e) {
Timber.i("Download failed...");
Timber.e(e, "IOException");
sendNotification(downloadId, FAILED, fileName);
} finally {
if (sink != null) {
try {
sink.close();
} catch (IOException e) {
// Ignore - Significant errors should already have been reported
}
}
}
}
private void sendNotification(int downloadId, String type, @NonNull String fileName) {
Intent intent = new Intent(ACTION_DOWNLOAD);
switch (type) {
case STARTED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "\"" + fileName + "\"");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "Download Started");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Downloading...");
break;
}
case COMPLETED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "\"" + fileName + "\"");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "Download Completed");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Completed");
break;
}
case FAILED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "\"" + fileName + "\"");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "Download Failed");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Failed");
break;
}
default: {
Timber.e("Invalid notification case!");
return;
}
}
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
intent.putExtra(EXTRA_DOWNLOAD_STATE, type);
intent.putExtra(EXTRA_FILE_NAME, fileName);
sendBroadcast(intent);
}
@NonNull
static String getMimeType(@NonNull File file) {
String type = null;
final String url = file.toString();
final String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null)
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
if (type == null)
type = "*/*";
return type;
}
}

10
app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java

@ -254,6 +254,16 @@ public class SessionManager {
return sharedPrefs.getString(AVATAR_LINK, AVATAR_LINK);
}
public Cookie getThmmyCookie() {
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
for(Cookie cookie: cookieList)
{
if(cookie.name().equals("THMMYgrC00ki3"))
return cookie;
}
return null;
}
public boolean hasAvatar() {
return sharedPrefs.getBoolean(HAS_AVATAR, false);
}

26
app/src/main/java/gr/thmmy/mthmmy/utils/FileUtils.java

@ -0,0 +1,26 @@
package gr.thmmy.mthmmy.utils;
import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
import java.io.File;
import static gr.thmmy.mthmmy.services.DownloadHelper.SAVE_DIR;
public class FileUtils {
@NonNull
public static String getMimeType(@NonNull String fileName) {
String type = null;
final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
if (extension != null)
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
if (type == null)
type = "*/*";
return type;
}
public static boolean fileNameExists (String fileName) {
return fileName != null && (new File(SAVE_DIR.getAbsolutePath(), fileName)).isFile();
}
}

7
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseTask.java

@ -26,7 +26,9 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
}
protected abstract void parse (Document document) throws ParseException;
protected abstract void postParsing (ParseTask.ResultCode result); //ResultCode.NETWORK_ERROR is handled automatically
protected abstract void postExecution(ParseTask.ResultCode result); //ResultCode.NETWORK_ERROR is handled automatically
protected void postParsing (){}
protected Request prepareRequest(String... params) {
url = params[0];
@ -42,6 +44,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
parse(document);
postParsing();
return ResultCode.SUCCESS;
} catch (ParseException e) {
Timber.tag(this.getClass().getSimpleName());
@ -62,7 +65,7 @@ public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.Result
protected void onPostExecute(ParseTask.ResultCode result) {
if (result == ResultCode.NETWORK_ERROR)
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
postParsing(result);
postExecution(result);
}
}

48
app/src/main/res/layout/download_prompt_dialog.xml

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/primary_light"
android:padding="4dp">
<TextView
android:id="@+id/downloadPromptTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
android:textSize="16sp"
android:textColor="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal">
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?android:attr/borderlessButtonStyle"
android:text="@string/cancel" />
<Button
android:id="@+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/open" />
<Button
android:id="@+id/download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download" />
</LinearLayout>
</LinearLayout>

11
app/src/main/res/values/strings.xml

@ -75,7 +75,7 @@
<string name="board_bookmarks_title">Boards</string>
<string name="topic_bookmarks_title">Topics</string>
<!--FontAwesome strings-->
<!--FontAwesome-->
<string name="fa_icon_star">&#xf005;</string>
<string name="fa_file">&#xf15b;</string>
<string name="fa_file_image_o">&#xf1c5;</string>
@ -90,5 +90,14 @@
<string name="fa_sticky">&#xf249;</string>
<string name="fa_folder">&#xf07b;</string>
<string name="fa_circle">&#xf111;</string>
<!--Notifications-->
<string name="toggle_notification">Toggle Notification</string>
<!--Download Prompt-->
<string name="downloadPromptText">File \"%1$s\" already exists. Download again?"</string>
<string name="download_symbol">Download Symbol</string>
<string name="cancel">Cancel</string>
<string name="open">Open</string>
<string name="download">Download</string>
</resources>

8
app/src/main/res/xml/provider_paths.xml

@ -1,10 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_downloads"
path="Downloads/"/>
<files-path
name="my_downloads"
path="Downloads/"/>
<external-path name="my_downloads" path="."/>
</paths>

1
build.gradle

@ -4,7 +4,6 @@ buildscript {
repositories {
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://maven.google.com" }
maven { url "https://maven.fabric.io/public" }
google()
}

Loading…
Cancel
Save