Browse Source

Downloading and parsing fixes.

pull/24/head
Apostolos Fanakis 8 years ago
parent
commit
3e8365f441
  1. 1
      app/src/main/AndroidManifest.xml
  2. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  3. 55
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  4. 32
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  5. 75
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  6. 78
      app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java

1
app/src/main/AndroidManifest.xml

@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:name=".base.BaseApplication"

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

@ -280,9 +280,11 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} else {
pUrl = subBoardCol.select("a").first().attr("href");
pTitle = subBoardCol.select("a").first().text();
if (subBoardCol.select("div.smalltext").first() != null) {
pMods = subBoardCol.select("div.smalltext").first().text();
}
}
}
parsedSubBoards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl));
}
}

55
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java

@ -1,11 +1,15 @@
package gr.thmmy.mthmmy.activities.topic;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
@ -58,6 +62,8 @@ public class TopicActivity extends BaseActivity {
* The key to use when putting topic's title String to {@link TopicActivity}'s Bundle.
*/
public static final String BUNDLE_TOPIC_TITLE = "TOPIC_TITLE";
private static final int PERMISSIONS_REQUEST_CODE = 69;
static boolean readWriteAccepted;
private static TopicTask topicTask;
//About posts
private TopicAdapter topicAdapter;
@ -99,6 +105,7 @@ public class TopicActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_topic);
requestPerms();
Bundle extras = getIntent().getExtras();
topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
@ -176,11 +183,7 @@ public class TopicActivity extends BaseActivity {
initDecrementButton(previousPage, SMALL_STEP);
initIncrementButton(nextPage, SMALL_STEP);
initIncrementButton(lastPage, LARGE_STEP);
firstPage.setEnabled(false);
previousPage.setEnabled(false);
nextPage.setEnabled(false);
lastPage.setEnabled(false);
paginationEnabled(false);
//Gets posts
topicTask = new TopicTask();
@ -210,7 +213,36 @@ public class TopicActivity extends BaseActivity {
topicTask.cancel(true);
}
//--------------------------------------BOTTOM NAV BAR METHODS--------------------------------------
@Override
public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions
, @NonNull int[] grantResults) {
switch (permsRequestCode) {
case PERMISSIONS_REQUEST_CODE:
readWriteAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
break;
}
}
private void requestPerms() { //Runtime permissions for devices with API >= 23
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
checkSelfPermission(PERMISSIONS_STORAGE[0]);
checkSelfPermission(PERMISSIONS_STORAGE[1]);
requestPermissions(PERMISSIONS_STORAGE, PERMISSIONS_REQUEST_CODE);
} else readWriteAccepted = true;
}
//--------------------------------------BOTTOM NAV BAR METHODS----------------------------------
private void paginationEnabled(boolean enabled) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
}
private void initIncrementButton(ImageButton increment, final int step) {
// Increment once for a click
increment.setOnClickListener(new View.OnClickListener() {
@ -335,7 +367,7 @@ public class TopicActivity extends BaseActivity {
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
paginationEnable(false);
paginationEnabled(false);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false);
}
@ -403,7 +435,7 @@ public class TopicActivity extends BaseActivity {
pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages));
pageRequestValue = thisPage;
paginationEnable(true);
paginationEnabled(true);
if (topicTitle == null || Objects.equals(topicTitle, ""))
toolbar.setTitle(parsedTitle);
@ -488,11 +520,4 @@ public class TopicActivity extends BaseActivity {
}
}
}
private void paginationEnable(boolean enabled) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
}
}

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

@ -10,7 +10,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.CardView;
@ -93,7 +92,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
* Index of state indicator in the boolean array. If true quote button for this post is checked.
*/
private static final int isQuoteButtonChecked = 2;
private final MaterialProgressBar progressBar;
//private final MaterialProgressBar progressBar;
private DownloadTask downloadTask;
private TopicActivity.TopicTask topicTask;
@ -167,7 +166,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
//Initializes properties, array's values will be false by default
viewProperties.add(new boolean[3]);
}
this.progressBar = progressBar;
//this.progressBar = progressBar;
downloadTask = new DownloadTask();
this.topicTask = topicTask;
}
@ -244,8 +243,12 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
attached.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TopicActivity.readWriteAccepted) {
downloadTask = new DownloadTask();
downloadTask.execute(attachedFile);
} else
Toast.makeText(context, "Persmissions missing!", Toast.LENGTH_SHORT)
.show();
}
});
@ -616,17 +619,11 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "DownloadTask"; //Separate tag for AsyncTask
private PowerManager.WakeLock mWakeLock;
@Override
protected void onPreExecute() {
super.onPreExecute();
//Locks CPU to prevent going off
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
progressBar.setVisibility(View.VISIBLE);
Toast.makeText(context, "Downloading", Toast.LENGTH_SHORT).show();
}
@Override
@ -647,7 +644,10 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
Report.e(TAG, "Error while trying to download a file", e);
return e.toString();
} catch (OutOfMemoryError e) {
Report.e(TAG, "Error while trying to download a file", e);
Report.e(TAG, e.toString(), e);
return e.toString();
} catch (IllegalStateException e) {
Report.e(TAG, e.toString(), e);
return e.toString();
}
return null;
@ -655,12 +655,10 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
@Override
protected void onPostExecute(String result) {
mWakeLock.release();
if (result != null)
Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
else
Toast.makeText(context, "Download complete", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.INVISIBLE);
if (result != null) {
Toast.makeText(context, "Download failed!", Toast.LENGTH_SHORT).show();
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
}
}

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

@ -27,11 +27,10 @@ import okhttp3.RequestBody;
import okhttp3.Response;
/**
This class handles all session related operations (e.g. login, logout)
and stores data to SharedPreferences (session information and cookies).
* This class handles all session related operations (e.g. login, logout)
* and stores data to SharedPreferences (session information and cookies).
*/
public class SessionManager
{
public class SessionManager {
//Class TAG
private static final String TAG = "SessionManager";
@ -66,8 +65,7 @@ public class SessionManager
//Constructor
public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar,
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs)
{
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) {
this.client = client;
this.cookiePersistor = cookiePersistor;
this.cookieJar = cookieJar;
@ -75,18 +73,17 @@ public class SessionManager
}
//------------------------------------AUTH BEGINS----------------------------------------------
/**
* Login function with two options: (username, password) or nothing (using saved cookies).
* Always call it in a separate thread.
*/
public int login(String... strings)
{
public int login(String... strings) {
Report.i(TAG, "Logging in...");
//Build the login request for each case
Request request;
if (strings.length == 2)
{
if (strings.length == 2) {
clearSessionData();
String loginName = strings[0];
@ -101,9 +98,7 @@ public class SessionManager
.url(loginUrl)
.post(formBody)
.build();
}
else
{
} else {
request = new Request.Builder()
.url(loginUrl)
.build();
@ -125,20 +120,16 @@ public class SessionManager
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply();
String avatar = extractAvatarLink(document);
if (avatar!=null)
{
if (avatar != null) {
sharedPrefs.edit().putBoolean(HAS_AVATAR, true).apply();
sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply();
}
else
} else
sharedPrefs.edit().putBoolean(HAS_AVATAR, false).apply();
sharedPrefs.edit().putString(LOGOUT_LINK, HttpUrl.parse(logoutButton.attr("href")).toString()).apply();
return SUCCESS;
}
else
{
} else {
Report.i(TAG, "Login failed.");
//Investigate login failure
@ -159,16 +150,13 @@ public class SessionManager
return FAILURE;
}
//Handle exception
}
catch (InterruptedIOException e){
} catch (InterruptedIOException e) {
Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask
return CANCELLED;
}
catch (IOException e) {
} catch (IOException e) {
Report.w(TAG, "Login IOException", e);
return CONNECTION_ERROR;
}
catch (Exception e) {
} catch (Exception e) {
Report.w(TAG, "Login Exception (other)", e);
return EXCEPTION;
}
@ -183,17 +171,14 @@ public class SessionManager
* Always call it in a separate thread in a way that won't hinder performance (e.g. after
* fragments' data are retrieved).
*/
public void validateSession()
{
public void validateSession() {
Report.i(TAG, "Validating session...");
if(isLoggedIn())
{
if (isLoggedIn()) {
int loginResult = login();
if (loginResult != FAILURE)
return;
}
else if(isLoginScreenDefault())
} else if (isLoginScreenDefault())
return;
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, true).apply();
@ -203,8 +188,7 @@ public class SessionManager
/**
* Call this function when user explicitly chooses to continue as a guest (UI thread).
*/
public void guestLogin()
{
public void guestLogin() {
Report.i("TAG", "Continuing as a guest, as chosen by the user.");
clearSessionData();
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
@ -214,8 +198,7 @@ public class SessionManager
/**
* Logout function. Always call it in a separate thread.
*/
public int logout()
{
public int logout() {
Report.i(TAG, "Logging out...");
Request request = new Request.Builder()
@ -271,15 +254,18 @@ public class SessionManager
return sharedPrefs.getBoolean(LOGIN_SCREEN_AS_DEFAULT, true);
}
//TODO FIX METHOD, THIS MIGHT BE A SECURITY FLAW!! SEE ISSUE #2 MERGED WITH #16
public String getCookieHeader() {
return cookiePersistor.loadAll().get(0).toString();
}
//--------------------------------------GETTERS END---------------------------------------------
//------------------------------------OTHER FUNCTIONS-------------------------------------------
private void setPersistentCookieSession()
{
private void setPersistentCookieSession() {
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
if (cookieList.size() == 2)
{
if (cookieList.size() == 2) {
if ((cookieList.get(0).name().equals("THMMYgrC00ki3"))
&& (cookieList.get(1).name().equals("PHPSESSID"))) {
Cookie.Builder builder = new Cookie.Builder();
@ -295,8 +281,7 @@ public class SessionManager
}
}
private void clearSessionData()
{
private void clearSessionData() {
cookieJar.clear();
sharedPrefs.edit().clear().apply(); //Clear session data
sharedPrefs.edit().putString(USERNAME, guestName).apply();
@ -305,8 +290,7 @@ public class SessionManager
}
@Nullable
private String extractUserName(Document doc)
{
private String extractUserName(Document doc) {
if (doc != null) {
Elements user = doc.select("div[id=myuser] > h3");
@ -324,8 +308,7 @@ public class SessionManager
}
@Nullable
private String extractAvatarLink(Document doc)
{
private String extractAvatarLink(Document doc) {
if (doc != null) {
Elements avatar = doc.select("#ava img");

78
app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java

@ -1,10 +1,18 @@
package gr.thmmy.mthmmy.utils.FileManager;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
@ -12,6 +20,7 @@ import java.io.IOException;
import java.net.URL;
import java.util.Objects;
import gr.thmmy.mthmmy.base.BaseActivity;
import mthmmy.utils.Report;
import okhttp3.Request;
import okhttp3.Response;
@ -22,6 +31,7 @@ import static gr.thmmy.mthmmy.base.BaseActivity.getClient;
* Used for downloading and storing a file from the forum using {@link okhttp3}.
* <p>Class has one constructor, {@link #ThmmyFile(URL, String, String)}.
*/
@SuppressWarnings("unused")
public class ThmmyFile {
/**
* Debug Tag for logging debug output to LogCat
@ -46,8 +56,8 @@ public class ThmmyFile {
}
/**
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. To download
* the file use {@link #download(Context)}!
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. To
* download the file use {@link #download(Context)} after you provide a url!
*
* @param fileUrl {@link URL} object with file's url
* @param filename {@link String} with desired file name
@ -115,23 +125,64 @@ public class ThmmyFile {
/**
* Used to download the file. If download is successful file's extension and path will be assigned
* to object's fields and can be accessed using getter methods.
* <p>File is stored in sdcard1/Android/data/Downloads/packageName</p>
*
* @return the {@link File} if successful, null otherwise
* @throws IOException if the request could not be executed due to cancellation, a connectivity
* problem or timeout. Because networks can fail during an exchange, it is possible that the
* remote server accepted the request before the failure.
* @return null if downloaded with the download service, otherwise the {@link File}
* @throws IOException if the request could not be executed due to cancellation, a
* connectivity problem or timeout. Because networks can fail
* during an exchange, it is possible that the remote server
* accepted the request before the failure.
* @throws SecurityException if the requested file is not hosted by the forum.
* @throws IllegalStateException if file's url or filename is not yet set
*/
@Nullable
public File download(Context context) throws IOException, SecurityException, OutOfMemoryError {
if (fileUrl == null) {
public File download(Context context) throws IOException, IllegalStateException, OutOfMemoryError {
if (fileUrl == null)
throw new IllegalStateException("Internal error!\nNo url was provided.");
else if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr"))
throw new SecurityException("Downloading files from other sources is not supported");
else if (filename == null || Objects.equals(filename, ""))
throw new IllegalStateException("Internal error!\nNo filename was provided.");
try {
downloadWithManager(context, fileUrl);
} catch (IllegalStateException e) {
return downloadWithoutManager(context, fileUrl);
}
return null;
}
if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr"))
throw new SecurityException("Downloading files from other sources is not supported");
Request request = new Request.Builder().url(fileUrl).build();
private void downloadWithManager(Context context, @NonNull URL pFileUrl) throws IllegalStateException, IOException {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(pFileUrl.toString()));
request.addRequestHeader("Cookie", BaseActivity.getSessionManager().getCookieHeader());
request.setDescription("mThmmy");
request.setMimeType(MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(filename)));
request.setTitle(filename);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
try {
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
} catch (IllegalStateException e) {
Report.d(TAG, "External directory not available!", e);
Log.d(TAG, "External directory not available!", e);
throw e;
}
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Download complete", Toast.LENGTH_SHORT).show();
context.unregisterReceiver(this);
}
}, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@Nullable
private File downloadWithoutManager(Context context, @NonNull URL pFileUrl) throws IOException
, SecurityException, OutOfMemoryError {
Request request = new Request.Builder().url(pFileUrl).build();
Response response = getClient().newCall(request).execute();
if (!response.isSuccessful()) {
@ -153,7 +204,8 @@ public class ThmmyFile {
}
@Nullable
private File getOutputMediaFile(Context context, String fileName, String fileInfo) throws OutOfMemoryError, IOException {
private File getOutputMediaFile(Context context, String fileName, String fileInfo) throws
OutOfMemoryError, IOException {
File mediaStorageDir;
String extState = Environment.getExternalStorageState();
if (Environment.isExternalStorageRemovable() &&

Loading…
Cancel
Save