mirror of https://github.com/kurisufriend/Clover
# Conflicts: # Clover/app/build.gradle # Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java # Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java # Clover/app/src/main/java/org/floens/chan/core/model/Post.java # Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java # Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java # Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java # Clover/app/src/main/java/org/floens/chan/test/TestActivity.java # Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java # Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java # Clover/build.gradlemultisite
commit
f337483a02
@ -1,46 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.model; |
||||
|
||||
import org.floens.chan.core.saver.StorageHelper; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class FileItem { |
||||
public File file; |
||||
|
||||
public FileItem(File file) { |
||||
this.file = file; |
||||
} |
||||
|
||||
public boolean isFile() { |
||||
return file.isFile(); |
||||
} |
||||
|
||||
public boolean isFolder() { |
||||
return file.isDirectory(); |
||||
} |
||||
|
||||
public boolean canNavigate() { |
||||
return StorageHelper.canNavigate(file); |
||||
} |
||||
|
||||
public boolean canOpen() { |
||||
return StorageHelper.canOpen(file); |
||||
} |
||||
} |
@ -1,34 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.model; |
||||
|
||||
import java.io.File; |
||||
import java.util.List; |
||||
|
||||
public class FileItems { |
||||
public final File path; |
||||
public final List<FileItem> fileItems; |
||||
|
||||
public final boolean canNavigateUp; |
||||
|
||||
public FileItems(File path, List<FileItem> fileItems, boolean canNavigateUp) { |
||||
this.path = path; |
||||
this.fileItems = fileItems; |
||||
this.canNavigateUp = canNavigateUp; |
||||
} |
||||
} |
@ -0,0 +1,162 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.net; |
||||
|
||||
|
||||
import android.util.JsonReader; |
||||
|
||||
import com.android.volley.Response; |
||||
|
||||
import org.floens.chan.BuildConfig; |
||||
|
||||
import java.io.IOException; |
||||
import java.text.DateFormat; |
||||
import java.text.ParseException; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
public class UpdateApiRequest extends JsonReaderRequest<UpdateApiRequest.UpdateApiResponse> { |
||||
public static final String TYPE_UPDATE = "update"; |
||||
|
||||
private static final int API_VERSION = 1; |
||||
|
||||
private String forFlavor; |
||||
|
||||
public UpdateApiRequest(Response.Listener<UpdateApiResponse> listener, Response.ErrorListener errorListener) { |
||||
super(BuildConfig.UPDATE_API_ENDPOINT, listener, errorListener); |
||||
forFlavor = BuildConfig.FLAVOR; |
||||
} |
||||
|
||||
@Override |
||||
public UpdateApiResponse readJson(JsonReader reader) throws Exception { |
||||
reader.beginObject(); |
||||
|
||||
UpdateApiResponse response = new UpdateApiResponse(); |
||||
|
||||
int apiVersion; |
||||
out: |
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "api_version": |
||||
apiVersion = reader.nextInt(); |
||||
|
||||
if (apiVersion > API_VERSION) { |
||||
response.newerApiVersion = true; |
||||
|
||||
while (reader.hasNext()) reader.skipValue(); |
||||
|
||||
break out; |
||||
} |
||||
|
||||
break; |
||||
case "messages": |
||||
reader.beginArray(); |
||||
while (reader.hasNext()) { |
||||
response.messages.add(readMessage(reader)); |
||||
} |
||||
reader.endArray(); |
||||
break; |
||||
case "check_interval": |
||||
response.checkIntervalMs = reader.nextLong(); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
reader.endObject(); |
||||
|
||||
return response; |
||||
} |
||||
|
||||
private UpdateApiMessage readMessage(JsonReader reader) throws IOException { |
||||
reader.beginObject(); |
||||
|
||||
UpdateApiMessage message = new UpdateApiMessage(); |
||||
|
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "type": |
||||
message.type = reader.nextString(); |
||||
break; |
||||
case "code": |
||||
message.code = reader.nextInt(); |
||||
break; |
||||
case "date": |
||||
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); |
||||
try { |
||||
message.date = format.parse(reader.nextString()); |
||||
} catch (ParseException ignore) { |
||||
} |
||||
break; |
||||
case "message_html": |
||||
message.messageHtml = reader.nextString(); |
||||
break; |
||||
case "apk": |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
if (reader.nextName().equals(forFlavor)) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
switch (reader.nextName()) { |
||||
case "url": |
||||
message.apkUrl = HttpUrl.parse(reader.nextString()); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
break; |
||||
default: |
||||
reader.skipValue(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
reader.endObject(); |
||||
|
||||
return message; |
||||
} |
||||
|
||||
public static class UpdateApiResponse { |
||||
public boolean newerApiVersion; |
||||
public List<UpdateApiMessage> messages = new ArrayList<>(); |
||||
public long checkIntervalMs; |
||||
} |
||||
|
||||
public static class UpdateApiMessage { |
||||
public String type; |
||||
public int code; |
||||
public Date date; |
||||
public String messageHtml; |
||||
public HttpUrl apkUrl; |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.settings; |
||||
|
||||
import android.content.SharedPreferences; |
||||
|
||||
public class LongSetting extends Setting<Long> { |
||||
private boolean hasCached = false; |
||||
private Long cached; |
||||
|
||||
public LongSetting(SharedPreferences sharedPreferences, String key, Long def) { |
||||
super(sharedPreferences, key, def); |
||||
} |
||||
|
||||
@Override |
||||
public Long get() { |
||||
if (hasCached) { |
||||
return cached; |
||||
} else { |
||||
cached = sharedPreferences.getLong(key, def); |
||||
hasCached = true; |
||||
return cached; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void set(Long value) { |
||||
if (!value.equals(get())) { |
||||
sharedPreferences.edit().putLong(key, value).apply(); |
||||
cached = value; |
||||
onValueChanged(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,249 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.update; |
||||
|
||||
|
||||
import android.content.Intent; |
||||
import android.net.Uri; |
||||
import android.os.Environment; |
||||
import android.os.StrictMode; |
||||
import android.text.TextUtils; |
||||
|
||||
import com.android.volley.RequestQueue; |
||||
import com.android.volley.Response; |
||||
import com.android.volley.VolleyError; |
||||
|
||||
import org.floens.chan.BuildConfig; |
||||
import org.floens.chan.core.cache.FileCache; |
||||
import org.floens.chan.core.net.UpdateApiRequest; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.utils.IOUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
import org.floens.chan.utils.Time; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
import static org.floens.chan.Chan.getGraph; |
||||
|
||||
/** |
||||
* Calls the update API and downloads and requests installs of APK files. |
||||
* <p>The APK files are downloaded to the public Download directory, and the default APK install |
||||
* screen is launched after downloading. |
||||
*/ |
||||
public class UpdateManager { |
||||
public static final long DEFAULT_UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 24 * 5; // 5 days
|
||||
|
||||
private static final String TAG = "UpdateManager"; |
||||
|
||||
private static final String DOWNLOAD_FILE = "Clover_update.apk"; |
||||
|
||||
@Inject |
||||
RequestQueue volleyRequestQueue; |
||||
|
||||
@Inject |
||||
FileCache fileCache; |
||||
|
||||
private UpdateCallback callback; |
||||
|
||||
public UpdateManager(UpdateCallback callback) { |
||||
getGraph().inject(this); |
||||
this.callback = callback; |
||||
} |
||||
|
||||
public boolean isUpdatingAvailable() { |
||||
return !TextUtils.isEmpty(BuildConfig.UPDATE_API_ENDPOINT); |
||||
} |
||||
|
||||
public void runUpdateApi(final boolean manual) { |
||||
if (!manual) { |
||||
long lastUpdateTime = ChanSettings.updateCheckTime.get(); |
||||
long interval = ChanSettings.updateCheckInterval.get(); |
||||
long now = Time.get(); |
||||
long delta = (lastUpdateTime + interval) - now; |
||||
if (delta > 0) { |
||||
return; |
||||
} else { |
||||
ChanSettings.updateCheckTime.set(now); |
||||
} |
||||
} |
||||
|
||||
Logger.d(TAG, "Calling update API"); |
||||
volleyRequestQueue.add(new UpdateApiRequest(new Response.Listener<UpdateApiRequest.UpdateApiResponse>() { |
||||
@Override |
||||
public void onResponse(UpdateApiRequest.UpdateApiResponse response) { |
||||
if (!processUpdateApiResponse(response) && manual) { |
||||
callback.onManualCheckNone(); |
||||
} |
||||
} |
||||
}, new Response.ErrorListener() { |
||||
@Override |
||||
public void onErrorResponse(VolleyError error) { |
||||
Logger.e(TAG, "Failed to process API call for updating", error); |
||||
|
||||
if (manual) { |
||||
callback.onManualCheckFailed(); |
||||
} |
||||
} |
||||
})); |
||||
} |
||||
|
||||
private boolean processUpdateApiResponse(UpdateApiRequest.UpdateApiResponse response) { |
||||
if (response.newerApiVersion) { |
||||
Logger.e(TAG, "API endpoint reports a higher API version than we support, aborting update check."); |
||||
|
||||
// ignore
|
||||
return false; |
||||
} |
||||
|
||||
if (response.checkIntervalMs != 0) { |
||||
ChanSettings.updateCheckInterval.set(response.checkIntervalMs); |
||||
} |
||||
|
||||
for (UpdateApiRequest.UpdateApiMessage message : response.messages) { |
||||
if (processUpdateMessage(message)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private boolean processUpdateMessage(UpdateApiRequest.UpdateApiMessage message) { |
||||
if (message.code <= BuildConfig.VERSION_CODE) { |
||||
Logger.d(TAG, "No newer version available (" + BuildConfig.VERSION_CODE + " >= " + message.code + ")."); |
||||
// Our code is newer than the message
|
||||
return false; |
||||
} |
||||
|
||||
if (message.type.equals(UpdateApiRequest.TYPE_UPDATE)) { |
||||
if (message.apkUrl == null) { |
||||
Logger.i(TAG, "Update available but none for this build flavor."); |
||||
// Not for this flavor, discard.
|
||||
return false; |
||||
} |
||||
|
||||
Logger.i(TAG, "Update available (" + message.code + ") with url \"" + message.apkUrl + "\"."); |
||||
callback.showUpdateAvailableDialog(message); |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Install the APK file specified in {@code update}. This methods needs the storage permission. |
||||
* |
||||
* @param update update with apk details. |
||||
*/ |
||||
public void doUpdate(Update update) { |
||||
fileCache.downloadFile(update.apkUrl.toString(), new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
if (!done) callback.onUpdateDownloadProgress(downloaded, total); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
callback.onUpdateDownloadSuccess(); |
||||
copyToPublicDirectory(file); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
callback.onUpdateDownloadFailed(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void retry(Install install) { |
||||
installApk(install); |
||||
} |
||||
|
||||
private void copyToPublicDirectory(File cacheFile) { |
||||
File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), DOWNLOAD_FILE); |
||||
try { |
||||
IOUtils.copyFile(cacheFile, out); |
||||
} catch (IOException e) { |
||||
Logger.e(TAG, "requestApkInstall", e); |
||||
callback.onUpdateDownloadMoveFailed(); |
||||
return; |
||||
} |
||||
installApk(new Install(out)); |
||||
} |
||||
|
||||
private void installApk(Install install) { |
||||
// First open the dialog that asks to retry and calls this method again.
|
||||
callback.openUpdateRetryDialog(install); |
||||
|
||||
// Then launch the APK install intent.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW); |
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
||||
intent.setDataAndType(Uri.fromFile(install.installFile), "application/vnd.android.package-archive"); |
||||
|
||||
// The installer wants a content scheme from android N and up,
|
||||
// but I don't feel like implementing a content provider just for this feature.
|
||||
// Temporary change the strictmode policy while starting the intent.
|
||||
StrictMode.VmPolicy vmPolicy = StrictMode.getVmPolicy(); |
||||
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX); |
||||
|
||||
callback.onUpdateOpenInstallScreen(intent); |
||||
|
||||
StrictMode.setVmPolicy(vmPolicy); |
||||
} |
||||
|
||||
public static class Update { |
||||
private HttpUrl apkUrl; |
||||
|
||||
public Update(HttpUrl apkUrl) { |
||||
this.apkUrl = apkUrl; |
||||
} |
||||
} |
||||
|
||||
public static class Install { |
||||
private File installFile; |
||||
|
||||
public Install(File installFile) { |
||||
this.installFile = installFile; |
||||
} |
||||
} |
||||
|
||||
public interface UpdateCallback { |
||||
void onManualCheckNone(); |
||||
|
||||
void onManualCheckFailed(); |
||||
|
||||
void showUpdateAvailableDialog(UpdateApiRequest.UpdateApiMessage message); |
||||
|
||||
void onUpdateDownloadProgress(long downloaded, long total); |
||||
|
||||
void onUpdateDownloadSuccess(); |
||||
|
||||
void onUpdateDownloadFailed(); |
||||
|
||||
void onUpdateDownloadMoveFailed(); |
||||
|
||||
void onUpdateOpenInstallScreen(Intent intent); |
||||
|
||||
void openUpdateRetryDialog(Install install); |
||||
} |
||||
} |
@ -1,303 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.test; |
||||
|
||||
import android.app.Activity; |
||||
import android.os.Bundle; |
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.view.View; |
||||
import android.widget.Button; |
||||
import android.widget.LinearLayout; |
||||
|
||||
import org.floens.chan.Chan; |
||||
import org.floens.chan.chan.ChanLoader; |
||||
import org.floens.chan.core.cache.FileCache; |
||||
import org.floens.chan.core.exception.ChanLoaderException; |
||||
import org.floens.chan.core.model.ChanThread; |
||||
import org.floens.chan.core.model.Loadable; |
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.ui.theme.ThemeHelper; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.File; |
||||
|
||||
// Poor mans unit testing.
|
||||
// Move to proper unit testing when the gradle plugin fully supports it.
|
||||
public class TestActivity extends Activity implements View.OnClickListener { |
||||
private static final String TAG = "FileCacheTest"; |
||||
private final Handler handler = new Handler(Looper.getMainLooper()); |
||||
|
||||
private Button clearCache; |
||||
private Button stats; |
||||
private Button simpleTest; |
||||
private Button cacheTest; |
||||
private Button timeoutTest; |
||||
|
||||
private FileCache fileCache; |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
|
||||
ThemeHelper.getInstance().setupContext(this); |
||||
|
||||
LinearLayout linearLayout = new LinearLayout(this); |
||||
linearLayout.setOrientation(LinearLayout.VERTICAL); |
||||
|
||||
clearCache = new Button(this); |
||||
clearCache.setText("Clear cache"); |
||||
clearCache.setOnClickListener(this); |
||||
linearLayout.addView(clearCache); |
||||
|
||||
stats = new Button(this); |
||||
stats.setText("Stats"); |
||||
stats.setOnClickListener(this); |
||||
linearLayout.addView(stats); |
||||
|
||||
simpleTest = new Button(this); |
||||
simpleTest.setText("Test download and cancel"); |
||||
simpleTest.setOnClickListener(this); |
||||
linearLayout.addView(simpleTest); |
||||
|
||||
cacheTest = new Button(this); |
||||
cacheTest.setText("Test cache size"); |
||||
cacheTest.setOnClickListener(this); |
||||
linearLayout.addView(cacheTest); |
||||
|
||||
timeoutTest = new Button(this); |
||||
timeoutTest.setText("Test multiple parallel"); |
||||
timeoutTest.setOnClickListener(this); |
||||
linearLayout.addView(timeoutTest); |
||||
|
||||
setContentView(linearLayout); |
||||
|
||||
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir(); |
||||
File fileCacheDir = new File(cacheDir, "filecache"); |
||||
fileCache = new FileCache(fileCacheDir, 50 * 1024 * 1024, Chan.getInstance().getUserAgent()); |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
if (v == clearCache) { |
||||
clearCache(); |
||||
} else if (v == stats) { |
||||
stats(); |
||||
} else if (v == simpleTest) { |
||||
testDownloadAndCancel(); |
||||
} else if (v == cacheTest) { |
||||
testCache(); |
||||
} else if (v == timeoutTest) { |
||||
testTimeout(); |
||||
} |
||||
} |
||||
|
||||
public void clearCache() { |
||||
fileCache.clearCache(); |
||||
} |
||||
|
||||
public void stats() { |
||||
fileCache.logStats(); |
||||
} |
||||
|
||||
public void testDownloadAndCancel() { |
||||
// 1.9MB file of the clover Logger.i(TAG,
|
||||
final String testImage = "http://a.pomf.se/ndbolc.png"; |
||||
final File cacheFile = fileCache.get(testImage); |
||||
|
||||
Logger.i(TAG, "Downloading " + testImage); |
||||
final FileCache.FileCacheDownloader downloader = fileCache.downloadFile(testImage, new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
Logger.i(TAG, "onProgress " + downloaded + "/" + total + " " + done); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
Logger.i(TAG, "onSuccess " + file.exists()); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
Logger.i(TAG, "onFail Cachefile exists() = " + cacheFile.exists()); |
||||
} |
||||
}); |
||||
|
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
fileCache.downloadFile(testImage, new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
Logger.i(TAG, "2nd progress " + downloaded + "/" + total); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
Logger.i(TAG, "2nd onSuccess " + file.exists()); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
Logger.i(TAG, "2nd onFail Cachefile exists() = " + cacheFile.exists()); |
||||
} |
||||
}); |
||||
} |
||||
}, 200); |
||||
|
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
Logger.i(TAG, "Cancelling download!"); |
||||
downloader.cancel(); |
||||
} |
||||
}, 500); |
||||
|
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
Logger.i(TAG, "File exists() = " + cacheFile.exists()); |
||||
} |
||||
}, 600); |
||||
|
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
final File cache404File = fileCache.get(testImage + "404"); |
||||
fileCache.downloadFile(testImage + "404", new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
Logger.i(TAG, "404 progress " + downloaded + "/" + total + " " + done); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
Logger.i(TAG, "404 onSuccess " + file.exists()); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
Logger.i(TAG, "404 onFail " + cache404File.exists()); |
||||
} |
||||
}); |
||||
} |
||||
}, 1000); |
||||
} |
||||
|
||||
private void testCache() { |
||||
// Loadable loadable = Loadable.forCatalog(Sites.defaultSite().boards().boards.get(0));
|
||||
Loadable loadable = null; |
||||
ChanLoader loader = new ChanLoader(loadable); |
||||
loader.addListener(new ChanLoader.ChanLoaderCallback() { |
||||
@Override |
||||
public void onChanLoaderData(ChanThread result) { |
||||
for (Post post : result.posts) { |
||||
if (post.image != null) { |
||||
final String imageUrl = post.image.imageUrl.toString(); |
||||
fileCache.downloadFile(imageUrl, new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
Logger.i(TAG, "Progress for " + imageUrl + " " + downloaded + "/" + total + " " + done); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
Logger.i(TAG, "onSuccess for " + imageUrl + " exists() = " + file.exists()); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
Logger.i(TAG, "onFail for " + imageUrl); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onChanLoaderError(ChanLoaderException error) { |
||||
|
||||
} |
||||
}); |
||||
loader.requestData(); |
||||
} |
||||
|
||||
private void testTimeout() { |
||||
testTimeoutInner("https://i.4cdn.org/hr/1429923649068.jpg", fileCache, 0); |
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
testTimeoutInner("https://i.4cdn.org/hr/1430058524427.jpg", fileCache, 0); |
||||
} |
||||
}, 200); |
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
testTimeoutInner("https://i.4cdn.org/hr/1430058627352.jpg", fileCache, 0); |
||||
} |
||||
}, 400); |
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
testTimeoutInner("https://i.4cdn.org/hr/1430058580015.jpg", fileCache, 0); |
||||
} |
||||
}, 600); |
||||
} |
||||
|
||||
private void testTimeoutInner(final String url, final FileCache fileCache, final int tries) { |
||||
final File cacheFile = fileCache.get(url); |
||||
Logger.i(TAG, "Downloading " + url + " try " + tries); |
||||
final FileCache.FileCacheDownloader downloader = fileCache.downloadFile(url, new FileCache.DownloadedCallback() { |
||||
@Override |
||||
public void onProgress(long downloaded, long total, boolean done) { |
||||
Logger.i(TAG, "onProgress " + url + " " + downloaded + "/" + total); |
||||
} |
||||
|
||||
@Override |
||||
public void onSuccess(File file) { |
||||
Logger.i(TAG, "onSuccess " + file.exists()); |
||||
} |
||||
|
||||
@Override |
||||
public void onFail(boolean notFound) { |
||||
Logger.i(TAG, "onFail Cachefile exists() = " + cacheFile.exists()); |
||||
} |
||||
}); |
||||
|
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (downloader == null) { |
||||
Logger.i(TAG, "Downloader null, cannot cancel"); |
||||
} else { |
||||
downloader.cancel(); |
||||
handler.postDelayed(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (tries < 10) { |
||||
testTimeoutInner(url, fileCache, tries + 1); |
||||
} else { |
||||
fileCache.logStats(); |
||||
} |
||||
} |
||||
}, 500); |
||||
} |
||||
} |
||||
}, 1000); |
||||
} |
||||
} |
@ -0,0 +1,107 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.controller; |
||||
|
||||
import android.content.ClipData; |
||||
import android.content.ClipboardManager; |
||||
import android.content.Context; |
||||
import android.view.ViewGroup; |
||||
import android.widget.ScrollView; |
||||
import android.widget.TextView; |
||||
import android.widget.Toast; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.controller.Controller; |
||||
import org.floens.chan.ui.toolbar.ToolbarMenu; |
||||
import org.floens.chan.ui.toolbar.ToolbarMenuItem; |
||||
import org.floens.chan.ui.view.FloatingMenuItem; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
import org.floens.chan.utils.IOUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.getAttrColor; |
||||
|
||||
public class LogsController extends Controller implements ToolbarMenuItem.ToolbarMenuItemCallback { |
||||
private static final String TAG = "LogsController"; |
||||
|
||||
private static final int COPY_ID = 101; |
||||
|
||||
private TextView logTextView; |
||||
|
||||
private String logText; |
||||
|
||||
public LogsController(Context context) { |
||||
super(context); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate() { |
||||
super.onCreate(); |
||||
|
||||
navigationItem.setTitle(org.floens.chan.R.string.settings_logs_screen); |
||||
|
||||
navigationItem.menu = new ToolbarMenu(context); |
||||
List<FloatingMenuItem> items = new ArrayList<>(); |
||||
items.add(new FloatingMenuItem(COPY_ID, R.string.settings_logs_copy)); |
||||
navigationItem.createOverflow(context, this, items); |
||||
|
||||
ScrollView container = new ScrollView(context); |
||||
container.setBackgroundColor(getAttrColor(context, org.floens.chan.R.attr.backcolor)); |
||||
logTextView = new TextView(context); |
||||
container.addView(logTextView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); |
||||
|
||||
view = container; |
||||
|
||||
loadLogs(); |
||||
} |
||||
|
||||
@Override |
||||
public void onMenuItemClicked(ToolbarMenuItem item) { |
||||
} |
||||
|
||||
@Override |
||||
public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) { |
||||
if ((int) item.getId() == COPY_ID) { |
||||
ClipboardManager clipboard = (ClipboardManager) AndroidUtils.getAppContext().getSystemService(Context.CLIPBOARD_SERVICE); |
||||
ClipData clip = ClipData.newPlainText("Logs", logText); |
||||
clipboard.setPrimaryClip(clip); |
||||
Toast.makeText(context, R.string.settings_logs_copied_to_clipboard, Toast.LENGTH_SHORT).show(); |
||||
} |
||||
} |
||||
|
||||
private void loadLogs() { |
||||
Process process; |
||||
try { |
||||
process = new ProcessBuilder() |
||||
.command("logcat", "-d", "-v", "tag") |
||||
.start(); |
||||
} catch (IOException e) { |
||||
Logger.e(TAG, "Error starting logcat", e); |
||||
return; |
||||
} |
||||
|
||||
InputStream outputStream = process.getInputStream(); |
||||
logText = IOUtils.readString(outputStream); |
||||
logTextView.setText(logText); |
||||
} |
||||
} |
@ -1,118 +0,0 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.helper; |
||||
|
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.Html; |
||||
import android.widget.Button; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class PreviousVersionHandler { |
||||
private static final String TAG = "PreviousVersionHandler"; |
||||
|
||||
/* |
||||
* Manifest version code, manifest version name, this version mapping: |
||||
* |
||||
* 28 = v1.1.2 |
||||
* 32 = v1.1.3 |
||||
* 36 = v1.2.0 |
||||
* 39 = v1.2.1 |
||||
* 40 = v1.2.2 |
||||
* 41 = v1.2.3 |
||||
* 42 = v1.2.4 |
||||
* 43 = v1.2.5 |
||||
* 44 = v1.2.6 |
||||
* 46 = v1.2.7 |
||||
* 47 = v1.2.8 |
||||
* 48 = v1.2.9 |
||||
* 49 = v1.2.10 |
||||
* 50 = v1.2.11 |
||||
* 51 = v2.0.0 = 1 |
||||
* 52 = v2.1.0 = 2 |
||||
* 53 = v2.1.1 = 2 |
||||
* 54 = v2.1.2 = 2 |
||||
* 55 = v2.1.3 = 2 |
||||
* 56 = v2.2.0 = 3 |
||||
*/ |
||||
private static final int CURRENT_VERSION = 3; |
||||
|
||||
public void run(Context context) { |
||||
int previous = ChanSettings.previousVersion.get(); |
||||
if (previous < CURRENT_VERSION) { |
||||
if (previous < 1) { |
||||
cleanupOutdatedIonFolder(context); |
||||
} |
||||
|
||||
// Add more previous version checks here
|
||||
|
||||
showMessage(context, CURRENT_VERSION); |
||||
|
||||
ChanSettings.previousVersion.set(CURRENT_VERSION); |
||||
} |
||||
} |
||||
|
||||
private void showMessage(Context context, int version) { |
||||
int resource = context.getResources().getIdentifier("previous_version_" + version, "string", context.getPackageName()); |
||||
if (resource != 0) { |
||||
CharSequence message = Html.fromHtml(context.getString(resource)); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(message) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
|
||||
final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); |
||||
button.setEnabled(false); |
||||
AndroidUtils.runOnUiThread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
dialog.setCanceledOnTouchOutside(true); |
||||
button.setEnabled(true); |
||||
} |
||||
}, 1500); |
||||
} |
||||
} |
||||
|
||||
private void cleanupOutdatedIonFolder(Context context) { |
||||
Logger.i(TAG, "Cleaning up old ion folder"); |
||||
File ionCacheFolder = new File(context.getCacheDir() + "/ion"); |
||||
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { |
||||
Logger.i(TAG, "Clearing old ion folder"); |
||||
for (File file : ionCacheFolder.listFiles()) { |
||||
if (!file.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion file " + file.getName()); |
||||
} |
||||
} |
||||
if (!ionCacheFolder.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion folder"); |
||||
} else { |
||||
Logger.i(TAG, "Deleted old ion folder"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,286 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.helper; |
||||
|
||||
import android.Manifest; |
||||
import android.app.ProgressDialog; |
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.content.Intent; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.Html; |
||||
import android.text.Spanned; |
||||
import android.widget.Button; |
||||
|
||||
import org.floens.chan.BuildConfig; |
||||
import org.floens.chan.R; |
||||
import org.floens.chan.core.net.UpdateApiRequest; |
||||
import org.floens.chan.core.settings.ChanSettings; |
||||
import org.floens.chan.core.update.UpdateManager; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
import org.floens.chan.utils.Logger; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class VersionHandler implements UpdateManager.UpdateCallback { |
||||
private static final String TAG = "VersionHandler"; |
||||
|
||||
/* |
||||
* Manifest version code, manifest version name, this version mapping: |
||||
* |
||||
* 28 = v1.1.2 |
||||
* 32 = v1.1.3 |
||||
* 36 = v1.2.0 |
||||
* 39 = v1.2.1 |
||||
* 40 = v1.2.2 |
||||
* 41 = v1.2.3 |
||||
* 42 = v1.2.4 |
||||
* 43 = v1.2.5 |
||||
* 44 = v1.2.6 |
||||
* 46 = v1.2.7 |
||||
* 47 = v1.2.8 |
||||
* 48 = v1.2.9 |
||||
* 49 = v1.2.10 |
||||
* 50 = v1.2.11 |
||||
* 51 = v2.0.0 = 1 |
||||
* 52 = v2.1.0 = 2 |
||||
* 53 = v2.1.1 = 2 |
||||
* 54 = v2.1.2 = 2 |
||||
* 55 = v2.1.3 = 2 |
||||
* 56 = v2.2.0 = 3 |
||||
* Since v2.3.0, this has been aligned with the versionCode as defined in build.gradle |
||||
* It is of the format XXYYZZ, where XX is major, YY is minor, ZZ is patch. |
||||
* 20300 = v2.3.0 = 20300 |
||||
*/ |
||||
private static final int CURRENT_VERSION = BuildConfig.VERSION_CODE; |
||||
|
||||
/** |
||||
* Context to show dialogs to. |
||||
*/ |
||||
private Context context; |
||||
private RuntimePermissionsHelper runtimePermissionsHelper; |
||||
|
||||
private UpdateManager updateManager; |
||||
|
||||
private ProgressDialog updateDownloadDialog; |
||||
|
||||
public VersionHandler(Context context, RuntimePermissionsHelper runtimePermissionsHelper) { |
||||
this.context = context; |
||||
this.runtimePermissionsHelper = runtimePermissionsHelper; |
||||
|
||||
updateManager = new UpdateManager(this); |
||||
} |
||||
|
||||
/** |
||||
* Runs every time onCreate is called on the StartActivity. |
||||
*/ |
||||
public void run() { |
||||
int previous = ChanSettings.previousVersion.get(); |
||||
if (previous < CURRENT_VERSION) { |
||||
if (previous < 1) { |
||||
cleanupOutdatedIonFolder(context); |
||||
} |
||||
|
||||
// Add more previous version checks here
|
||||
|
||||
showMessage(CURRENT_VERSION); |
||||
|
||||
ChanSettings.previousVersion.set(CURRENT_VERSION); |
||||
|
||||
// Don't process the updater because a dialog is now already showing.
|
||||
return; |
||||
} |
||||
|
||||
if (updateManager.isUpdatingAvailable()) { |
||||
updateManager.runUpdateApi(false); |
||||
} |
||||
} |
||||
|
||||
public boolean isUpdatingAvailable() { |
||||
return updateManager.isUpdatingAvailable(); |
||||
} |
||||
|
||||
public void manualUpdateCheck() { |
||||
updateManager.runUpdateApi(true); |
||||
} |
||||
|
||||
@Override |
||||
public void onManualCheckNone() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_none) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onManualCheckFailed() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_check_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void showUpdateAvailableDialog(final UpdateApiRequest.UpdateApiMessage message) { |
||||
Spanned text = Html.fromHtml(message.messageHtml); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(text) |
||||
.setNegativeButton(R.string.update_later, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updatePostponed(message); |
||||
} |
||||
}) |
||||
.setPositiveButton(R.string.update_install, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updateInstallRequested(message); |
||||
} |
||||
}) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
} |
||||
|
||||
private void updatePostponed(UpdateApiRequest.UpdateApiMessage message) { |
||||
} |
||||
|
||||
private void updateInstallRequested(final UpdateApiRequest.UpdateApiMessage message) { |
||||
runtimePermissionsHelper.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new RuntimePermissionsHelper.Callback() { |
||||
@Override |
||||
public void onRuntimePermissionResult(boolean granted) { |
||||
if (granted) { |
||||
createDownloadProgressDialog(); |
||||
updateManager.doUpdate(new UpdateManager.Update(message.apkUrl)); |
||||
} else { |
||||
runtimePermissionsHelper.showPermissionRequiredDialog(context, |
||||
context.getString(R.string.update_storage_permission_required_title), |
||||
context.getString(R.string.update_storage_permission_required), |
||||
new RuntimePermissionsHelper.PermissionRequiredDialogCallback() { |
||||
@Override |
||||
public void retryPermissionRequest() { |
||||
updateInstallRequested(message); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void createDownloadProgressDialog() { |
||||
updateDownloadDialog = new ProgressDialog(context); |
||||
updateDownloadDialog.setCancelable(false); |
||||
updateDownloadDialog.setTitle(R.string.update_install_downloading); |
||||
updateDownloadDialog.setMax(10000); |
||||
updateDownloadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); |
||||
updateDownloadDialog.setProgressNumberFormat(""); |
||||
updateDownloadDialog.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadProgress(long downloaded, long total) { |
||||
updateDownloadDialog.setProgress((int) (updateDownloadDialog.getMax() * (downloaded / (double) total))); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadSuccess() { |
||||
updateDownloadDialog.dismiss(); |
||||
updateDownloadDialog = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadFailed() { |
||||
updateDownloadDialog.dismiss(); |
||||
updateDownloadDialog = null; |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_install_download_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateDownloadMoveFailed() { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_install_download_move_failed) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onUpdateOpenInstallScreen(Intent intent) { |
||||
AndroidUtils.openIntent(intent); |
||||
} |
||||
|
||||
@Override |
||||
public void openUpdateRetryDialog(final UpdateManager.Install install) { |
||||
new AlertDialog.Builder(context) |
||||
.setTitle(R.string.update_retry_title) |
||||
.setMessage(R.string.update_retry) |
||||
.setNegativeButton(R.string.cancel, null) |
||||
.setPositiveButton(R.string.update_retry_button, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
updateManager.retry(install); |
||||
} |
||||
}) |
||||
.show(); |
||||
} |
||||
|
||||
private void showMessage(int version) { |
||||
int resource = context.getResources().getIdentifier("changelog_" + version, "string", context.getPackageName()); |
||||
if (resource != 0) { |
||||
CharSequence message = Html.fromHtml(context.getString(resource)); |
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context) |
||||
.setMessage(message) |
||||
.setPositiveButton(R.string.ok, null) |
||||
.create(); |
||||
dialog.show(); |
||||
dialog.setCanceledOnTouchOutside(false); |
||||
|
||||
final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); |
||||
button.setEnabled(false); |
||||
AndroidUtils.runOnUiThread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
dialog.setCanceledOnTouchOutside(true); |
||||
button.setEnabled(true); |
||||
} |
||||
}, 1500); |
||||
} |
||||
} |
||||
|
||||
private void cleanupOutdatedIonFolder(Context context) { |
||||
Logger.i(TAG, "Cleaning up old ion folder"); |
||||
File ionCacheFolder = new File(context.getCacheDir() + "/ion"); |
||||
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) { |
||||
Logger.i(TAG, "Clearing old ion folder"); |
||||
for (File file : ionCacheFolder.listFiles()) { |
||||
if (!file.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion file " + file.getName()); |
||||
} |
||||
} |
||||
if (!ionCacheFolder.delete()) { |
||||
Logger.i(TAG, "Could not delete old ion folder"); |
||||
} else { |
||||
Logger.i(TAG, "Deleted old ion folder"); |
||||
} |
||||
} |
||||
} |
||||
} |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -1,6 +1,6 @@ |
||||
#Tue Aug 16 12:22:14 CEST 2016 |
||||
#Tue Mar 07 00:32:11 EST 2017 |
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip |
||||
|
@ -1,46 +0,0 @@ |
||||
mode=checkcaptcha only works on some boards! |
||||
Until a proper api exists for checking what boards have this option, it will not be implemented. |
||||
(that is, a proper way to check if "var preupload_captcha = true" was set) |
||||
|
||||
|
||||
Send to the normal url "https://sys.4chan.org/" + board + "/post"; |
||||
Used in the extension for checking if the captcha is correct when submitting a large file |
||||
|
||||
POST: |
||||
mode=checkcaptcha |
||||
challenge=<normal captcha challenge> |
||||
response=<user response> |
||||
|
||||
|
||||
returns json: |
||||
{ |
||||
"token": "<captcha token>", |
||||
"error": "<error shown to the user>", |
||||
"fail": "<fail logged to console>" |
||||
} |
||||
|
||||
In the extension: |
||||
|
||||
if (response.token) { |
||||
a = $.id("qrCapToken"), |
||||
a.value = response.token, |
||||
a.removeAttribute("disabled"), |
||||
QR.submitDirect() |
||||
} else { |
||||
if (response.error) { |
||||
QR.reloadCaptcha(), |
||||
QR.btn.value = "Post", |
||||
QR.showPostError(response.error)) |
||||
} else { |
||||
if (response.fail) { |
||||
console.log(b.fail), |
||||
QR.submitDirect() |
||||
} |
||||
} |
||||
} |
||||
|
||||
qrCapToken is a hidden form field with the name captcha_token |
||||
So when token exists in the json, send a normal reply with captcha_token |
||||
|
||||
|
||||
|
@ -0,0 +1,22 @@ |
||||
{ |
||||
"api_version": 1, |
||||
"messages": [ |
||||
{ |
||||
"type": "update", |
||||
|
||||
"code": 56, |
||||
"date": "2017-03-18T13:23:06.614104", |
||||
"message_html": "<h2>Clover v2.2.0 is available</h2>A new version of Clover is available.<br><br>This release fixes stuff.<br>- aaa<br>- bbb", |
||||
|
||||
"apk": { |
||||
"default": { |
||||
"url": "https://github.com/Floens/Clover/releases/download/v2.2.0/Clover_v2.2.0.apk" |
||||
}, |
||||
"fdroid": { |
||||
"url": "https://f-droid.org/repo/org.floens.chan_56.apk" |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"check_interval": 432000000 |
||||
} |
@ -0,0 +1,29 @@ |
||||
update_api.json describes the update check api that Clover loads periodically. |
||||
|
||||
api_version |
||||
Version of this api, always 1. |
||||
|
||||
check_interval |
||||
the interval of loading the file, overrides the default interval of 5 days if set. |
||||
|
||||
messages |
||||
array of messages |
||||
|
||||
type: |
||||
type of the message, only "update" is supported |
||||
|
||||
code: |
||||
code of the new version. if this is higher than the code of the calling app then the message will be processed. |
||||
|
||||
date: |
||||
ISO8601 date, parsed but not used for now. |
||||
|
||||
message_html: |
||||
message shown to the user, parsed with Html.fromHtml() |
||||
|
||||
apk: |
||||
set of apks for each flavor. each key is only parsed if it equals to the flavor name that the app is compiled for. |
||||
|
||||
url: |
||||
url of the apk file to download and install. |
||||
|
Loading…
Reference in new issue