From d7aef2e97a9aaffde1cd117bbe8bfb30f32a7710 Mon Sep 17 00:00:00 2001 From: NoodleMage Date: Sat, 30 Jan 2016 21:16:30 +0100 Subject: [PATCH] Application can now check if update available --- app/build.gradle | 3 +- .../main/java/eu/kanade/tachiyomi/App.java | 16 +-- .../tachiyomi/data/rest/GithubService.java | 15 +++ .../kanade/tachiyomi/data/rest/Release.java | 93 +++++++++++++++++ .../tachiyomi/data/rest/ServiceFactory.java | 21 ++++ .../tachiyomi/data/updater/UpdateChecker.java | 31 ++++++ .../data/updater/UpdateDownloader.java | 99 +++++++++++++++++++ .../injection/component/AppComponent.java | 4 + .../ui/setting/SettingsAboutFragment.java | 78 ++++++++++++++- app/src/main/res/values/strings.xml | 8 ++ 10 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/rest/GithubService.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/rest/ServiceFactory.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.java diff --git a/app/build.gradle b/app/build.gradle index fba22db768..e389be871b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,6 +104,7 @@ dependencies { compile 'org.jsoup:jsoup:1.8.3' compile 'io.reactivex:rxandroid:1.1.0' compile 'io.reactivex:rxjava:1.1.0' + compile 'com.squareup.retrofit:retrofit:1.9.0' compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1' compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION" compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION" @@ -131,7 +132,7 @@ dependencies { transitive = true } - //Google material icons SVG. + // Google material icons SVG. compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar' compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.java b/app/src/main/java/eu/kanade/tachiyomi/App.java index c90143050c..1e6305797c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.java +++ b/app/src/main/java/eu/kanade/tachiyomi/App.java @@ -23,6 +23,10 @@ public class App extends Application { AppComponent applicationComponent; ComponentReflectionInjector componentInjector; + public static App get(Context context) { + return (App) context.getApplicationContext(); + } + @Override public void onCreate() { super.onCreate(); @@ -38,20 +42,16 @@ public class App extends Application { ACRA.init(this); } - public static App get(Context context) { - return (App) context.getApplicationContext(); - } - public AppComponent getComponent() { return applicationComponent; } - public ComponentReflectionInjector getComponentReflection() { - return componentInjector; - } - // Needed to replace the component with a test specific one public void setComponent(AppComponent applicationComponent) { this.applicationComponent = applicationComponent; } + + public ComponentReflectionInjector getComponentReflection() { + return componentInjector; + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/rest/GithubService.java b/app/src/main/java/eu/kanade/tachiyomi/data/rest/GithubService.java new file mode 100644 index 0000000000..2f34f6b5b8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/rest/GithubService.java @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.data.rest; + +import retrofit.http.GET; +import rx.Observable; + + +/** + * Used to connect with the Github API + */ +public interface GithubService { + String SERVICE_ENDPOINT = "https://api.github.com"; + + @GET("/repos/inorichi/tachiyomi/releases/latest") Observable getLatestVersion(); + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java b/app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java new file mode 100644 index 0000000000..b0f2398cbe --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java @@ -0,0 +1,93 @@ +package eu.kanade.tachiyomi.data.rest; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Release object + * Contains information about the latest release + */ +public class Release { + /** + * Version name V0.0.0 + */ + @SerializedName("tag_name") + private final String version; + + /** Change Log */ + @SerializedName("body") + private final String log; + + /** Assets containing download url */ + @SerializedName("assets") + private final List assets; + + /** + * Release constructor + * + * @param version version of latest release + * @param log log of latest release + * @param assets assets of latest release + */ + public Release(String version, String log, List assets) { + this.version = version; + this.log = log; + this.assets = assets; + } + + /** + * Get latest release version + * + * @return latest release version + */ + public String getVersion() { + return version; + } + + /** + * Get change log of latest release + * + * @return change log of latest release + */ + public String getChangeLog() { + return log; + } + + /** + * Get download link of latest release + * + * @return download link of latest release + */ + public String getDownloadLink() { + return assets.get(0).getDownloadLink(); + } + + /** + * Assets class containing download url + */ + class Assets { + @SerializedName("browser_download_url") + private final String download_url; + + + /** + * Assets Constructor + * + * @param download_url download url + */ + @SuppressWarnings("unused") public Assets(String download_url) { + this.download_url = download_url; + } + + /** + * Get download link of latest release + * + * @return download link of latest release + */ + public String getDownloadLink() { + return download_url; + } + } +} + diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/rest/ServiceFactory.java b/app/src/main/java/eu/kanade/tachiyomi/data/rest/ServiceFactory.java new file mode 100644 index 0000000000..6cf455fda7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/rest/ServiceFactory.java @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.data.rest; + +import retrofit.RestAdapter; + +public class ServiceFactory { + + /** + * Creates a retrofit service from an arbitrary class (clazz) + * + * @param clazz Java interface of the retrofit service + * @param endPoint REST endpoint url + * @return retrofit service with defined endpoint + */ + public static T createRetrofitService(final Class clazz, final String endPoint) { + final RestAdapter restAdapter = new RestAdapter.Builder() + .setEndpoint(endPoint) + .build(); + + return restAdapter.create(clazz); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.java b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.java new file mode 100644 index 0000000000..1f12d8f108 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.java @@ -0,0 +1,31 @@ +package eu.kanade.tachiyomi.data.updater; + + +import android.content.Context; + +import eu.kanade.tachiyomi.R; +import eu.kanade.tachiyomi.data.rest.GithubService; +import eu.kanade.tachiyomi.data.rest.Release; +import eu.kanade.tachiyomi.data.rest.ServiceFactory; +import eu.kanade.tachiyomi.util.ToastUtil; +import rx.Observable; + + +public class UpdateChecker { + private final Context context; + + public UpdateChecker(Context context) { + this.context = context; + } + + /** + * Returns observable containing release information + * + */ + public Observable checkForApplicationUpdate() { + ToastUtil.showShort(context, context.getString(R.string.update_check_look_for_updates)); + //Create Github service to retrieve Github data + GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT); + return service.getLatestVersion(); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.java b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.java new file mode 100644 index 0000000000..465510fd6e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.java @@ -0,0 +1,99 @@ +package eu.kanade.tachiyomi.data.updater; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import javax.inject.Inject; + +import eu.kanade.tachiyomi.App; +import eu.kanade.tachiyomi.data.preference.PreferencesHelper; + +public class UpdateDownloader extends AsyncTask { + /** + * Name of cache directory. + */ + private static final String PARAMETER_CACHE_DIRECTORY = "apk_downloads"; + /** + * Interface to global information about an application environment. + */ + private final Context context; + /** + * Cache directory used for cache management. + */ + private final File cacheDir; + @Inject PreferencesHelper preferencesHelper; + + /** + * Constructor of UpdaterCache. + * + * @param context application environment interface. + */ + public UpdateDownloader(Context context) { + App.get(context).getComponent().inject(this); + this.context = context; + + // Get cache directory from parameter. + cacheDir = new File(preferencesHelper.getDownloadsDirectory(), PARAMETER_CACHE_DIRECTORY); + + // Create cache directory. + createCacheDir(); + } + + /** + * Create cache directory if it doesn't exist + * + * @return true if cache dir is created otherwise false. + */ + @SuppressWarnings("UnusedReturnValue") + private boolean createCacheDir() { + return !cacheDir.exists() && cacheDir.mkdirs(); + } + + + @Override + protected Void doInBackground(String... arg0) { + try { + createCacheDir(); + + URL url = new URL(arg0[0]); + HttpURLConnection c = (HttpURLConnection) url.openConnection(); + c.connect(); + + File outputFile = new File(cacheDir, "update.apk"); + if (outputFile.exists()) { + //noinspection ResultOfMethodCallIgnored + outputFile.delete(); + } + FileOutputStream fos = new FileOutputStream(outputFile); + + InputStream is = c.getInputStream(); + + byte[] buffer = new byte[1024]; + int len1; + while ((len1 = is.read(buffer)) != -1) { + fos.write(buffer, 0, len1); + } + fos.close(); + is.close(); + + // Prompt install interface + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error! + context.startActivity(intent); + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} + diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java index 4746f0463a..99b76491ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList; import eu.kanade.tachiyomi.data.source.base.Source; import eu.kanade.tachiyomi.data.sync.LibraryUpdateService; import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService; +import eu.kanade.tachiyomi.data.updater.UpdateDownloader; import eu.kanade.tachiyomi.injection.module.AppModule; import eu.kanade.tachiyomi.injection.module.DataModule; import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter; @@ -50,6 +51,7 @@ public interface AppComponent { void inject(ReaderActivity readerActivity); void inject(MangaActivity mangaActivity); void inject(SettingsAccountsFragment settingsAccountsFragment); + void inject(SettingsActivity settingsActivity); void inject(Source source); @@ -59,6 +61,8 @@ public interface AppComponent { void inject(LibraryUpdateService libraryUpdateService); void inject(DownloadService downloadService); void inject(UpdateMangaSyncService updateMangaSyncService); + + void inject(UpdateDownloader updateDownloader); Application application(); } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.java index b25c644965..b6573100e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.java @@ -6,6 +6,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.afollestad.materialdialogs.MaterialDialog; + import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -15,8 +17,23 @@ import java.util.TimeZone; import eu.kanade.tachiyomi.BuildConfig; import eu.kanade.tachiyomi.R; +import eu.kanade.tachiyomi.data.updater.UpdateChecker; +import eu.kanade.tachiyomi.data.updater.UpdateDownloader; +import eu.kanade.tachiyomi.util.ToastUtil; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; public class SettingsAboutFragment extends SettingsNestedFragment { + /** + * Checks for new releases + */ + private UpdateChecker updateChecker; + + /** + * The subscribtion service of the obtained release object + */ + private Subscription releaseSubscription; public static SettingsNestedFragment newInstance(int resourcePreference, int resourceTitle) { SettingsNestedFragment fragment = new SettingsAboutFragment(); @@ -25,14 +42,36 @@ public class SettingsAboutFragment extends SettingsNestedFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { + public void onCreate(Bundle savedInstanceState) { + //Check for update + updateChecker = new UpdateChecker(getActivity()); + super.onCreate(savedInstanceState); + } + + @Override + public void onDestroyView() { + if (releaseSubscription != null) + releaseSubscription.unsubscribe(); + + super.onDestroyView(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { Preference version = findPreference(getString(R.string.pref_version)); Preference buildTime = findPreference(getString(R.string.pref_build_time)); version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT : BuildConfig.VERSION_NAME); + //Set onClickListener to check for new version + version.setOnPreferenceClickListener(preference -> { + if (!BuildConfig.DEBUG) + checkVersion(); + return true; + }); + buildTime.setSummary(getFormattedBuildTime()); return super.onCreateView(inflater, container, savedState); @@ -54,4 +93,41 @@ public class SettingsAboutFragment extends SettingsNestedFragment { } return ""; } + + /** + * Checks version and shows a user prompt when update available. + */ + private void checkVersion() { + releaseSubscription = updateChecker.checkForApplicationUpdate() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(next -> { + //Get version of latest releaseSubscription + String newVersion = next.getVersion(); + newVersion = newVersion.replaceAll("[^\\d.]", ""); + + //Check if latest version is different from current version + if (!newVersion.equals(BuildConfig.VERSION_NAME)) { + String downloadLink = next.getDownloadLink(); + String body = next.getChangeLog(); + + //Create confirmation window + new MaterialDialog.Builder(getActivity()) + .title(getString(R.string.update_check_title)) + .content(body) + .positiveText(getString(R.string.update_check_confirm)) + .negativeText(getString(R.string.update_check_ignore)) + .onPositive((dialog, which) -> { + // User output that download has started + ToastUtil.showShort(getActivity(), getString(R.string.update_check_download_started)); + // Start download + new UpdateDownloader(getActivity()).execute(downloadLink); + }) + .show(); + } else { + ToastUtil.showShort(getActivity(), getString(R.string.update_check_no_new_updates)); + } + }, + Throwable::printStackTrace); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 339a012ad6..7532f1ef0d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -210,4 +210,12 @@ Select cover image + + New update available! + Download + Ignore + No new updates available + Download Started + Looking for updates +