Merge pull request #98 from NoodleMage/download_updates

Download updates
This commit is contained in:
inorichi 2016-02-03 17:17:15 +01:00
commit 3deac86bbe
10 changed files with 358 additions and 10 deletions

View File

@ -104,6 +104,7 @@ dependencies {
compile 'org.jsoup:jsoup:1.8.3' compile 'org.jsoup:jsoup:1.8.3'
compile 'io.reactivex:rxandroid:1.1.0' compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava: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.f2prateek.rx.preferences:rx-preferences:1.0.1'
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION" compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION" compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
@ -131,7 +132,7 @@ dependencies {
transitive = true transitive = true
} }
//Google material icons SVG. // Google material icons SVG.
compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar' compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar'
compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {

View File

@ -23,6 +23,10 @@ public class App extends Application {
AppComponent applicationComponent; AppComponent applicationComponent;
ComponentReflectionInjector<AppComponent> componentInjector; ComponentReflectionInjector<AppComponent> componentInjector;
public static App get(Context context) {
return (App) context.getApplicationContext();
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -38,20 +42,16 @@ public class App extends Application {
ACRA.init(this); ACRA.init(this);
} }
public static App get(Context context) {
return (App) context.getApplicationContext();
}
public AppComponent getComponent() { public AppComponent getComponent() {
return applicationComponent; return applicationComponent;
} }
public ComponentReflectionInjector<AppComponent> getComponentReflection() {
return componentInjector;
}
// Needed to replace the component with a test specific one // Needed to replace the component with a test specific one
public void setComponent(AppComponent applicationComponent) { public void setComponent(AppComponent applicationComponent) {
this.applicationComponent = applicationComponent; this.applicationComponent = applicationComponent;
} }
public ComponentReflectionInjector<AppComponent> getComponentReflection() {
return componentInjector;
}
} }

View File

@ -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<Release> getLatestVersion();
}

View File

@ -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> 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> 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;
}
}
}

View File

@ -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> T createRetrofitService(final Class<T> clazz, final String endPoint) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.build();
return restAdapter.create(clazz);
}
}

View File

@ -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<Release> 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();
}
}

View File

@ -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<String, Void, Void> {
/**
* 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;
}
}

View File

@ -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.source.base.Source;
import eu.kanade.tachiyomi.data.sync.LibraryUpdateService; import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService; 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.AppModule;
import eu.kanade.tachiyomi.injection.module.DataModule; import eu.kanade.tachiyomi.injection.module.DataModule;
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter; import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
@ -50,6 +51,7 @@ public interface AppComponent {
void inject(ReaderActivity readerActivity); void inject(ReaderActivity readerActivity);
void inject(MangaActivity mangaActivity); void inject(MangaActivity mangaActivity);
void inject(SettingsAccountsFragment settingsAccountsFragment); void inject(SettingsAccountsFragment settingsAccountsFragment);
void inject(SettingsActivity settingsActivity); void inject(SettingsActivity settingsActivity);
void inject(Source source); void inject(Source source);
@ -59,6 +61,8 @@ public interface AppComponent {
void inject(LibraryUpdateService libraryUpdateService); void inject(LibraryUpdateService libraryUpdateService);
void inject(DownloadService downloadService); void inject(DownloadService downloadService);
void inject(UpdateMangaSyncService updateMangaSyncService); void inject(UpdateMangaSyncService updateMangaSyncService);
void inject(UpdateDownloader updateDownloader);
Application application(); Application application();
} }

View File

@ -6,6 +6,8 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.afollestad.materialdialogs.MaterialDialog;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -15,8 +17,23 @@ import java.util.TimeZone;
import eu.kanade.tachiyomi.BuildConfig; import eu.kanade.tachiyomi.BuildConfig;
import eu.kanade.tachiyomi.R; 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 { 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) { public static SettingsNestedFragment newInstance(int resourcePreference, int resourceTitle) {
SettingsNestedFragment fragment = new SettingsAboutFragment(); SettingsNestedFragment fragment = new SettingsAboutFragment();
@ -25,14 +42,36 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
} }
@Override @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 version = findPreference(getString(R.string.pref_version));
Preference buildTime = findPreference(getString(R.string.pref_build_time)); Preference buildTime = findPreference(getString(R.string.pref_build_time));
version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT : version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT :
BuildConfig.VERSION_NAME); BuildConfig.VERSION_NAME);
//Set onClickListener to check for new version
version.setOnPreferenceClickListener(preference -> {
if (!BuildConfig.DEBUG)
checkVersion();
return true;
});
buildTime.setSummary(getFormattedBuildTime()); buildTime.setSummary(getFormattedBuildTime());
return super.onCreateView(inflater, container, savedState); return super.onCreateView(inflater, container, savedState);
@ -54,4 +93,41 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
} }
return ""; 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);
}
} }

View File

@ -210,4 +210,12 @@
<!-- File Picker Titles --> <!-- File Picker Titles -->
<string name="file_select_cover">Select cover image</string> <string name="file_select_cover">Select cover image</string>
<!--UpdateCheck-->
<string name="update_check_title">New update available!</string>
<string name="update_check_confirm">Download</string>
<string name="update_check_ignore">Ignore</string>
<string name="update_check_no_new_updates">No new updates available</string>
<string name="update_check_download_started">Download Started</string>
<string name="update_check_look_for_updates">Looking for updates</string>
</resources> </resources>