mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +01:00 
			
		
		
		
	Merge branch 'master' into media_detail
This commit is contained in:
		
						commit
						7edfe34a3e
					
				
					 55 changed files with 1122 additions and 564 deletions
				
			
		|  | @ -8,41 +8,30 @@ import android.app.Application; | |||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Build; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.util.LruCache; | ||||
| 
 | ||||
| import com.facebook.drawee.backends.pipeline.Fresco; | ||||
| import com.facebook.stetho.Stetho; | ||||
| 
 | ||||
| import fr.free.nrw.commons.caching.CacheController; | ||||
| import fr.free.nrw.commons.category.Category; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.auth.AccountUtil; | ||||
| import fr.free.nrw.commons.nearby.NearbyPlaces; | ||||
| 
 | ||||
| import com.squareup.leakcanary.LeakCanary; | ||||
| 
 | ||||
| import org.acra.ACRA; | ||||
| import org.acra.ReportingInteractionMode; | ||||
| import org.acra.annotation.ReportsCrashes; | ||||
| import org.apache.http.conn.ClientConnectionManager; | ||||
| import org.apache.http.conn.scheme.PlainSocketFactory; | ||||
| import org.apache.http.conn.scheme.Scheme; | ||||
| import org.apache.http.conn.scheme.SchemeRegistry; | ||||
| import org.apache.http.conn.ssl.SSLSocketFactory; | ||||
| import org.apache.http.impl.client.AbstractHttpClient; | ||||
| import org.apache.http.impl.client.DefaultHttpClient; | ||||
| import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; | ||||
| import org.apache.http.params.BasicHttpParams; | ||||
| import org.apache.http.params.CoreProtocolPNames; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.auth.AccountUtil; | ||||
| import fr.free.nrw.commons.caching.CacheController; | ||||
| import fr.free.nrw.commons.category.Category; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.nearby.NearbyPlaces; | ||||
| import fr.free.nrw.commons.utils.FileUtils; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -76,9 +65,8 @@ public class CommonsApplication extends Application { | |||
|     public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback"; | ||||
| 
 | ||||
|     private static CommonsApplication instance = null; | ||||
|     private AbstractHttpClient httpClient = null; | ||||
|     private MWApi api = null; | ||||
|     LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024); | ||||
|     private MediaWikiApi api = null; | ||||
|     private LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024); | ||||
|     private CacheController cacheData = null; | ||||
|     private DBOpenHelper dbOpenHelper = null; | ||||
|     private NearbyPlaces nearbyPlaces = null; | ||||
|  | @ -98,35 +86,13 @@ public class CommonsApplication extends Application { | |||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     public AbstractHttpClient getHttpClient() { | ||||
|         if (httpClient == null) { | ||||
|             httpClient = newHttpClient(); | ||||
|         } | ||||
|         return httpClient; | ||||
|     } | ||||
| 
 | ||||
|     private AbstractHttpClient newHttpClient() { | ||||
|         BasicHttpParams params = new BasicHttpParams(); | ||||
|         SchemeRegistry schemeRegistry = new SchemeRegistry(); | ||||
|         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); | ||||
|         final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory(); | ||||
|         schemeRegistry.register(new Scheme("https", sslSocketFactory, 443)); | ||||
|         ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); | ||||
|         params.setParameter(CoreProtocolPNames.USER_AGENT, "Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE); | ||||
|         return new DefaultHttpClient(cm, params); | ||||
|     } | ||||
| 
 | ||||
|     public MWApi getMWApi() { | ||||
|     public MediaWikiApi getMWApi() { | ||||
|         if (api == null) { | ||||
|             api = newMWApi(); | ||||
|             api = new ApacheHttpClientMediaWikiApi(API_URL); | ||||
|         } | ||||
|         return api; | ||||
|     } | ||||
| 
 | ||||
|     private MWApi newMWApi() { | ||||
|         return new MWApi(API_URL, getHttpClient()); | ||||
|     } | ||||
| 
 | ||||
|     public CacheController getCacheData() { | ||||
|         if (cacheData == null) { | ||||
|             cacheData = new CacheController(); | ||||
|  | @ -174,9 +140,6 @@ public class CommonsApplication extends Application { | |||
| 
 | ||||
|         Fresco.initialize(this); | ||||
| 
 | ||||
|         // Initialize EventLogging | ||||
|         EventLog.setApp(this); | ||||
| 
 | ||||
|         //For caching area -> categories | ||||
|         cacheData  = new CacheController(); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,130 +0,0 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Build; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| import org.apache.http.HttpResponse; | ||||
| import org.apache.http.impl.client.AbstractHttpClient; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.net.MalformedURLException; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import in.yuvi.http.fluent.Http; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class EventLog { | ||||
| 
 | ||||
|     private static CommonsApplication app; | ||||
| 
 | ||||
|     private static class LogTask extends AsyncTask<LogBuilder, Void, Boolean> { | ||||
| 
 | ||||
|         @Override | ||||
|         protected Boolean doInBackground(LogBuilder... logBuilders) { | ||||
| 
 | ||||
|             boolean  allSuccess = true; | ||||
|             // Not using the default URL connection, since that seems to have different behavior than the rest of the code | ||||
|             for(LogBuilder logBuilder: logBuilders) { | ||||
|                 try { | ||||
|                     URL url = logBuilder.toUrl(); | ||||
|                     AbstractHttpClient httpClient = CommonsApplication.getInstance().getHttpClient(); | ||||
|                     HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse(); | ||||
| 
 | ||||
|                     if(response.getStatusLine().getStatusCode() != 204) { | ||||
|                         allSuccess = false; | ||||
|                     } | ||||
|                     Timber.d("EventLog hit %s", url); | ||||
| 
 | ||||
|                 } catch (IOException e) { | ||||
|                     // Probably just ignore for now. Can be much more robust with a service, etc later on. | ||||
|                     Timber.d("IO Error, EventLog hit skipped"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return allSuccess; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static final String DEVICE; | ||||
|     static { | ||||
|         if (Build.MODEL.startsWith(Build.MANUFACTURER)) { | ||||
|             DEVICE = Utils.capitalize(Build.MODEL); | ||||
|         } else { | ||||
|             DEVICE = Utils.capitalize(Build.MANUFACTURER) + " " + Build.MODEL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void setApp(CommonsApplication app) { | ||||
|         EventLog.app = app; | ||||
|     } | ||||
| 
 | ||||
|     public static class LogBuilder { | ||||
|         private JSONObject data; | ||||
|         private long rev; | ||||
|         private String schema; | ||||
| 
 | ||||
|         private LogBuilder(String schema, long revision) { | ||||
|             data = new JSONObject(); | ||||
|             this.schema = schema; | ||||
|             this.rev = revision; | ||||
|         } | ||||
| 
 | ||||
|         public LogBuilder param(String key, Object value) { | ||||
|             try { | ||||
|                 data.put(key, value); | ||||
|             } catch (JSONException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         private URL toUrl() { | ||||
|             JSONObject fullData = new JSONObject(); | ||||
|             try { | ||||
|                 fullData.put("schema", schema); | ||||
|                 fullData.put("revision", rev); | ||||
|                 fullData.put("wiki", CommonsApplication.EVENTLOG_WIKI); | ||||
|                 data.put("device", DEVICE); | ||||
|                 data.put("platform", "Android/" + Build.VERSION.RELEASE); | ||||
|                 data.put("appversion", "Android/" + BuildConfig.VERSION_NAME); | ||||
|                 fullData.put("event", data); | ||||
|                 return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";"); | ||||
|             } catch (MalformedURLException | JSONException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // force param disregards user preference | ||||
|         // Use *only* for tracking the user preference change for EventLogging | ||||
|         // Attempting to use anywhere else will cause kitten explosions | ||||
|         public void log(boolean force) { | ||||
|             SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(app); | ||||
|             if(!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) { | ||||
|                 return; // User has disabled tracking | ||||
|             } | ||||
|             LogTask logTask = new LogTask(); | ||||
|             logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this); | ||||
|         } | ||||
| 
 | ||||
|         public void log() { | ||||
|             log(false); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static LogBuilder schema(String schema, long revision) { | ||||
|         return new LogBuilder(schema, revision); | ||||
|     } | ||||
| 
 | ||||
|     public static LogBuilder schema(Object[] scid) { | ||||
|         if(scid.length != 2) { | ||||
|             throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second"); | ||||
|         } | ||||
|         return schema((String)scid[0], (Long)scid[1]); | ||||
|     } | ||||
| } | ||||
|  | @ -1,94 +0,0 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.http.impl.client.AbstractHttpClient; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| /** | ||||
|  * @author Addshore | ||||
|  */ | ||||
| public class MWApi extends org.mediawiki.api.MWApi { | ||||
| 
 | ||||
|     /** We don't actually use this but need to pass it in requests */ | ||||
|     private static String LOGIN_RETURN_TO_URL = "https://commons.wikimedia.org"; | ||||
| 
 | ||||
|     public MWApi(String apiURL, AbstractHttpClient client) { | ||||
|         super(apiURL, client); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param username String | ||||
|      * @param password String | ||||
|      * @return String as returned by this.getErrorCodeToReturn() | ||||
|      * @throws IOException On api request IO issue | ||||
|      */ | ||||
|     public String login(String username, String password) throws IOException { | ||||
|         String token = this.getLoginToken(); | ||||
|         ApiResult loginApiResult = this.action("clientlogin") | ||||
|                 .param("rememberMe", "1") | ||||
|                 .param("username", username) | ||||
|                 .param("password", password) | ||||
|                 .param("logintoken", token) | ||||
|                 .param("loginreturnurl", LOGIN_RETURN_TO_URL) | ||||
|                 .post(); | ||||
|         return this.getErrorCodeToReturn( loginApiResult ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param username String | ||||
|      * @param password String | ||||
|      * @param twoFactorCode String | ||||
|      * @return String as returned by this.getErrorCodeToReturn() | ||||
|      * @throws IOException On api request IO issue | ||||
|      */ | ||||
|     public String login(String username, String password, String twoFactorCode) throws IOException { | ||||
|         String token = this.getLoginToken();//TODO cache this instead of calling again when 2FAing | ||||
|         ApiResult loginApiResult = this.action("clientlogin") | ||||
|                 .param("rememberMe", "1") | ||||
|                 .param("username", username) | ||||
|                 .param("password", password) | ||||
|                 .param("logintoken", token) | ||||
|                 .param("logincontinue", "1") | ||||
|                 .param("OATHToken", twoFactorCode) | ||||
|                 .post(); | ||||
| 
 | ||||
|         return this.getErrorCodeToReturn( loginApiResult ); | ||||
|     } | ||||
| 
 | ||||
|     private String getLoginToken() throws IOException { | ||||
|         ApiResult tokenResult = this.action("query") | ||||
|                 .param("action", "query") | ||||
|                 .param("meta", "tokens") | ||||
|                 .param("type", "login") | ||||
|                 .post(); | ||||
|         return tokenResult.getString("/api/query/tokens/@logintoken"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param loginApiResult ApiResult Any clientlogin api result | ||||
|      * @return String On success: "PASS" | ||||
|      *                   continue: "2FA" (More information required for 2FA) | ||||
|      *                   failure: A failure message code (defined by mediawiki) | ||||
|      *                   misc:    genericerror-UI, genericerror-REDIRECT, genericerror-RESTART | ||||
|      */ | ||||
|     private String getErrorCodeToReturn( ApiResult loginApiResult ) { | ||||
|         String status = loginApiResult.getString("/api/clientlogin/@status"); | ||||
|         if (status.equals("PASS")) { | ||||
|             this.isLoggedIn = true; | ||||
|             return status; | ||||
|         } else if (status.equals("FAIL")) { | ||||
|             return loginApiResult.getString("/api/clientlogin/@messagecode"); | ||||
|         } else if ( | ||||
|                 status.equals("UI") | ||||
|                         && loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest") | ||||
|                         && loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).") | ||||
|                 ) { | ||||
|             return "2FA"; | ||||
|         } | ||||
| 
 | ||||
|         // UI, REDIRECT, RESTART | ||||
|         return "genericerror-" + status; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -23,6 +23,8 @@ import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.mwapi.MediaResult; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -64,29 +66,15 @@ public class MediaDataExtractor { | |||
|             throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again."); | ||||
|         } | ||||
| 
 | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         ApiResult result = api.action("query") | ||||
|                 .param("prop", "revisions") | ||||
|                 .param("titles", filename) | ||||
|                 .param("rvprop", "content") | ||||
|                 .param("rvlimit", 1) | ||||
|                 .param("rvgeneratexml", 1) | ||||
|                 .get(); | ||||
| 
 | ||||
|         processResult(result); | ||||
|         fetched = true; | ||||
|     } | ||||
| 
 | ||||
|     private void processResult(ApiResult result) throws IOException { | ||||
| 
 | ||||
|         String wikiSource = result.getString("/api/query/pages/page/revisions/rev"); | ||||
|         String parseTreeXmlSource = result.getString("/api/query/pages/page/revisions/rev/@parsetree"); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         MediaResult result = api.fetchMediaByFilename(filename); | ||||
| 
 | ||||
|         // In-page category links are extracted from source, as XML doesn't cover [[links]] | ||||
|         extractCategories(wikiSource); | ||||
|         extractCategories(result.getWikiSource()); | ||||
| 
 | ||||
|         // Description template info is extracted from preprocessor XML | ||||
|         processWikiParseTree(parseTreeXmlSource); | ||||
|         processWikiParseTree(result.getParseTreeXmlSource()); | ||||
|         fetched = true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -3,10 +3,9 @@ package fr.free.nrw.commons; | |||
| import android.os.AsyncTask; | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import org.mediawiki.api.ApiResult; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| 
 | ||||
| class MediaThumbnailFetchTask extends AsyncTask<String, String, String> { | ||||
|     private static final String THUMB_SIZE = "640"; | ||||
|     protected final Media media; | ||||
| 
 | ||||
|     public MediaThumbnailFetchTask(@NonNull Media media) { | ||||
|  | @ -16,15 +15,8 @@ class MediaThumbnailFetchTask extends AsyncTask<String, String, String> { | |||
|     @Override | ||||
|     protected String doInBackground(String... params) { | ||||
|         try { | ||||
|             MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|             ApiResult result =api.action("query") | ||||
|                     .param("format", "xml") | ||||
|                     .param("prop", "imageinfo") | ||||
|                     .param("iiprop", "url") | ||||
|                     .param("iiurlwidth", THUMB_SIZE) | ||||
|                     .param("titles", params[0]) | ||||
|                     .get(); | ||||
|             return result.getString("/api/query/pages/page/imageinfo/ii/@thumburl"); | ||||
|             MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|             return api.findThumbnailByFilename(params[0]); | ||||
|         } catch (Exception e) { | ||||
|             // Do something better! | ||||
|         } | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import com.viewpagerindicator.CirclePageIndicator; | |||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.theme.BaseActivity; | ||||
| 
 | ||||
| public class WelcomeActivity extends BaseActivity { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import android.content.ContentResolver; | |||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import java.io.IOException; | |||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.theme.NavigationBaseActivity; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public abstract class AuthenticatedActivity extends NavigationBaseActivity { | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,9 +19,10 @@ import android.widget.Toast; | |||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.WelcomeActivity; | ||||
| 
 | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,8 +9,8 @@ import android.os.Bundle; | |||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.EventLog; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| class LoginTask extends AsyncTask<String, String, String> { | ||||
|  |  | |||
|  | @ -8,13 +8,13 @@ import android.accounts.NetworkErrorException; | |||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| 
 | ||||
| public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | ||||
| 
 | ||||
|  | @ -75,7 +75,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | |||
|     } | ||||
| 
 | ||||
|     private String getAuthCookie(String username, String password) throws IOException { | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         //TODO add 2fa support here | ||||
|         String result = api.login(username, password); | ||||
|         if(result.equals("PASS")) { | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ public class CategorizationFragment extends Fragment { | |||
|         //Override onPostExecute to access the results of async API call | ||||
|         titleCategoriesSub = new TitleCategories(title) { | ||||
|             @Override | ||||
|             protected void onPostExecute(ArrayList<String> result) { | ||||
|             protected void onPostExecute(List<String> result) { | ||||
|                 super.onPostExecute(result); | ||||
|                 Timber.d("Results in onPostExecute: %s", result); | ||||
|                 titleCatItems.addAll(result); | ||||
|  | @ -277,8 +277,8 @@ public class CategorizationFragment extends Fragment { | |||
| 
 | ||||
|         prefixUpdaterSub = new PrefixUpdater(this) { | ||||
|             @Override | ||||
|             protected ArrayList<String> doInBackground(Void... voids) { | ||||
|                 ArrayList<String> result = new ArrayList<>(); | ||||
|             protected List<String> doInBackground(Void... voids) { | ||||
|                 List<String> result = new ArrayList<>(); | ||||
|                 try { | ||||
|                     result = super.doInBackground(); | ||||
|                     latch.await(); | ||||
|  | @ -291,7 +291,7 @@ public class CategorizationFragment extends Fragment { | |||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             protected void onPostExecute(ArrayList<String> result) { | ||||
|             protected void onPostExecute(List<String> result) { | ||||
|                 super.onPostExecute(result); | ||||
| 
 | ||||
|                 results.addAll(result); | ||||
|  | @ -309,7 +309,7 @@ public class CategorizationFragment extends Fragment { | |||
| 
 | ||||
|         methodAUpdaterSub = new MethodAUpdater(this) { | ||||
|             @Override | ||||
|             protected void onPostExecute(ArrayList<String> result) { | ||||
|             protected void onPostExecute(List<String> result) { | ||||
|                 results.clear(); | ||||
|                 super.onPostExecute(result); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,15 +3,14 @@ package fr.free.nrw.commons.category; | |||
| import android.os.AsyncTask; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Calendar; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -19,12 +18,12 @@ import timber.log.Timber; | |||
|  * the keyword typed in by the user. The 'srsearch' action-specific parameter is used for this | ||||
|  * purpose. This class should be subclassed in CategorizationFragment.java to aggregate the results. | ||||
|  */ | ||||
| public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | ||||
| class MethodAUpdater extends AsyncTask<Void, Void, List<String>> { | ||||
| 
 | ||||
|     private String filter; | ||||
|     CategorizationFragment catFragment; | ||||
|     private CategorizationFragment catFragment; | ||||
| 
 | ||||
|     public MethodAUpdater(CategorizationFragment catFragment) { | ||||
|     MethodAUpdater(CategorizationFragment catFragment) { | ||||
|         this.catFragment = catFragment; | ||||
|     } | ||||
| 
 | ||||
|  | @ -42,10 +41,11 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|      * Remove categories that contain a year in them (starting with 19__ or 20__), except for this year | ||||
|      * and previous year | ||||
|      * Rationale: https://github.com/commons-app/apps-android-commons/issues/47 | ||||
|      * | ||||
|      * @param items Unfiltered list of categories | ||||
|      * @return Filtered category list | ||||
|      */ | ||||
|     private ArrayList<String> filterYears(ArrayList<String> items) { | ||||
|     private List<String> filterYears(List<String> items) { | ||||
| 
 | ||||
|         Iterator<String> iterator; | ||||
| 
 | ||||
|  | @ -60,12 +60,12 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|         Timber.d("Previous year: %s", prevYearInString); | ||||
| 
 | ||||
|         //Copy to Iterator to prevent ConcurrentModificationException when removing item | ||||
|         for(iterator = items.iterator(); iterator.hasNext();) { | ||||
|         for (iterator = items.iterator(); iterator.hasNext(); ) { | ||||
|             String s = iterator.next(); | ||||
| 
 | ||||
|             //Check if s contains a 4-digit word anywhere within the string (.* is wildcard) | ||||
|             //And that s does not equal the current year or previous year | ||||
|             if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { | ||||
|             if (s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { | ||||
|                 Timber.d("Filtering out year %s", s); | ||||
|                 iterator.remove(); | ||||
|             } | ||||
|  | @ -76,37 +76,22 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ArrayList<String> doInBackground(Void... voids) { | ||||
|     protected List<String> doInBackground(Void... voids) { | ||||
| 
 | ||||
|         //otherwise if user has typed something in that isn't in cache, search API for matching categories | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         ApiResult result; | ||||
|         ArrayList<String> categories = new ArrayList<>(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         List<String> categories = new ArrayList<>(); | ||||
| 
 | ||||
|         //URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch= | ||||
|         try { | ||||
|             result = api.action("query") | ||||
|                     .param("format", "xml") | ||||
|                     .param("list", "search") | ||||
|                     .param("srwhat", "text") | ||||
|                     .param("srnamespace", "14") | ||||
|                     .param("srlimit", catFragment.SEARCH_CATS_LIMIT) | ||||
|                     .param("srsearch", filter) | ||||
|                     .get(); | ||||
|             Timber.d("Method A URL filter %s", result); | ||||
|             categories = api.searchCategories(CategorizationFragment.SEARCH_CATS_LIMIT, filter); | ||||
|             Timber.d("Method A URL filter %s", categories); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception: "); | ||||
|             //Return empty arraylist | ||||
|             return categories; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/search/p/@title"); | ||||
|         for(ApiResult categoryNode: categoryNodes) { | ||||
|             String cat = categoryNode.getDocument().getTextContent(); | ||||
|             String catString = cat.replace("Category:", ""); | ||||
|             categories.add(catString); | ||||
|         } | ||||
| 
 | ||||
|         Timber.d("Found categories from Method A search, waiting for filter"); | ||||
|         return new ArrayList<>(filterYears(categories)); | ||||
|     } | ||||
|  |  | |||
|  | @ -4,15 +4,14 @@ import android.os.AsyncTask; | |||
| import android.text.TextUtils; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Calendar; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -21,7 +20,7 @@ import timber.log.Timber; | |||
|  * for this purpose. This class should be subclassed in CategorizationFragment.java to aggregate | ||||
|  * the results. | ||||
|  */ | ||||
| public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | ||||
| public class PrefixUpdater extends AsyncTask<Void, Void, List<String>> { | ||||
| 
 | ||||
|     private String filter; | ||||
|     private CategorizationFragment catFragment; | ||||
|  | @ -44,10 +43,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|      * Remove categories that contain a year in them (starting with 19__ or 20__), except for this year | ||||
|      * and previous year | ||||
|      * Rationale: https://github.com/commons-app/apps-android-commons/issues/47 | ||||
|      * | ||||
|      * @param items Unfiltered list of categories | ||||
|      * @return Filtered category list | ||||
|      */ | ||||
|     private ArrayList<String> filterYears(ArrayList<String> items) { | ||||
|     private List<String> filterYears(List<String> items) { | ||||
| 
 | ||||
|         Iterator<String> iterator; | ||||
| 
 | ||||
|  | @ -62,12 +62,12 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|         Timber.d("Previous year: %s", prevYearInString); | ||||
| 
 | ||||
|         //Copy to Iterator to prevent ConcurrentModificationException when removing item | ||||
|         for(iterator = items.iterator(); iterator.hasNext();) { | ||||
|         for (iterator = items.iterator(); iterator.hasNext(); ) { | ||||
|             String s = iterator.next(); | ||||
| 
 | ||||
|             //Check if s contains a 4-digit word anywhere within the string (.* is wildcard) | ||||
|             //And that s does not equal the current year or previous year | ||||
|             if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { | ||||
|             if (s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { | ||||
|                 Timber.d("Filtering out year %s", s); | ||||
|                 iterator.remove(); | ||||
|             } | ||||
|  | @ -78,16 +78,16 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ArrayList<String> doInBackground(Void... voids) { | ||||
|     protected List<String> doInBackground(Void... voids) { | ||||
|         //If user hasn't typed anything in yet, get GPS and recent items | ||||
|         if(TextUtils.isEmpty(filter)) { | ||||
|         if (TextUtils.isEmpty(filter)) { | ||||
|             ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems()); | ||||
|             Timber.d("Merged items, waiting for filter"); | ||||
|             return new ArrayList<>(filterYears(mergedItems)); | ||||
|         } | ||||
| 
 | ||||
|         //if user types in something that is in cache, return cached category | ||||
|         if(catFragment.categoriesCache.containsKey(filter)) { | ||||
|         if (catFragment.categoriesCache.containsKey(filter)) { | ||||
|             ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter)); | ||||
|             Timber.d("Found cache items, waiting for filter"); | ||||
|             return new ArrayList<>(filterYears(cachedItems)); | ||||
|  | @ -95,27 +95,17 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { | |||
| 
 | ||||
|         //otherwise if user has typed something in that isn't in cache, search API for matching categories | ||||
|         //URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25 | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         ApiResult result; | ||||
|         ArrayList<String> categories = new ArrayList<>(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         List<String> categories = new ArrayList<>(); | ||||
|         try { | ||||
|             result = api.action("query") | ||||
|                     .param("list", "allcategories") | ||||
|                     .param("acprefix", filter) | ||||
|                     .param("aclimit", catFragment.SEARCH_CATS_LIMIT) | ||||
|                     .get(); | ||||
|             Timber.d("Prefix URL filter %s", result); | ||||
|             categories = api.allCategories(CategorizationFragment.SEARCH_CATS_LIMIT, this.filter); | ||||
|             Timber.d("Prefix URL filter %s", categories); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception: "); | ||||
|             //Return empty arraylist | ||||
|             return categories; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/allcategories/c"); | ||||
|         for(ApiResult categoryNode: categoryNodes) { | ||||
|             categories.add(categoryNode.getDocument().getTextContent()); | ||||
|         } | ||||
| 
 | ||||
|         Timber.d("Found categories from Prefix search, waiting for filter"); | ||||
|         return new ArrayList<>(filterYears(categories)); | ||||
|     } | ||||
|  |  | |||
|  | @ -2,13 +2,12 @@ package fr.free.nrw.commons.category; | |||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -16,13 +15,13 @@ import timber.log.Timber; | |||
|  * the title entered in previous screen. The 'srsearch' action-specific parameter is used for this | ||||
|  * purpose. This class should be subclassed in CategorizationFragment.java to add the results to recent and GPS cats. | ||||
|  */ | ||||
| public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> { | ||||
| class TitleCategories extends AsyncTask<Void, Void, List<String>> { | ||||
| 
 | ||||
|     private final static int SEARCH_CATS_LIMIT = 25; | ||||
| 
 | ||||
|     private String title; | ||||
| 
 | ||||
|     public TitleCategories(String title) { | ||||
|     TitleCategories(String title) { | ||||
|         this.title = title; | ||||
|     } | ||||
| 
 | ||||
|  | @ -32,39 +31,23 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> { | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ArrayList<String> doInBackground(Void... voids) { | ||||
|     protected List<String> doInBackground(Void... voids) { | ||||
| 
 | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         ApiResult result; | ||||
|         ArrayList<String> items = new ArrayList<>(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         List<String> titleCategories = new ArrayList<>(); | ||||
| 
 | ||||
|         //URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch= | ||||
|         try { | ||||
|             result = api.action("query") | ||||
|                     .param("format", "xml") | ||||
|                     .param("list", "search") | ||||
|                     .param("srwhat", "text") | ||||
|                     .param("srnamespace", "14") | ||||
|                     .param("srlimit", SEARCH_CATS_LIMIT) | ||||
|                     .param("srsearch", title) | ||||
|                     .get(); | ||||
|             Timber.d("Searching for cats for title: %s", result); | ||||
|             titleCategories = api.searchTitles(SEARCH_CATS_LIMIT, this.title); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception: "); | ||||
|             //Return empty arraylist | ||||
|             return items; | ||||
|             return titleCategories; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/search/p/@title"); | ||||
|         for(ApiResult categoryNode: categoryNodes) { | ||||
|             String cat = categoryNode.getDocument().getTextContent(); | ||||
|             String catString = cat.replace("Category:", ""); | ||||
|             items.add(catString); | ||||
|         } | ||||
|         Timber.d("Title cat query results: %s", titleCategories); | ||||
| 
 | ||||
|         Timber.d("Title cat query results: %s", items); | ||||
| 
 | ||||
|         return items; | ||||
|         return titleCategories; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package fr.free.nrw.commons.concurrency; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| 
 | ||||
| public class BackgroundPoolExceptionHandler implements ExceptionHandler { | ||||
|  |  | |||
|  | @ -15,10 +15,9 @@ import java.util.Locale; | |||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.EventLog; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| 
 | ||||
| public class Contribution extends Media { | ||||
| 
 | ||||
|  | @ -63,8 +62,6 @@ public class Contribution extends Media { | |||
|         isMultiple = multiple; | ||||
|     } | ||||
| 
 | ||||
|     public EventLog.LogBuilder event; | ||||
| 
 | ||||
|     public Contribution(Uri localUri, String remoteUri, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) { | ||||
|         super(localUri, remoteUri, filename, description, dataLength, dateCreated, dateUploaded, creator); | ||||
|         this.decimalCoords = decimalCoords; | ||||
|  | @ -132,14 +129,14 @@ public class Contribution extends Media { | |||
|     public String getPageContents() { | ||||
|         StringBuffer buffer = new StringBuffer(); | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); | ||||
|          | ||||
| 
 | ||||
|         buffer | ||||
|             .append("== {{int:filedesc}} ==\n") | ||||
|                 .append("== {{int:filedesc}} ==\n") | ||||
|                 .append("{{Information\n") | ||||
|                     .append("|description=").append(getDescription()).append("\n") | ||||
|                     .append("|source=").append("{{own}}\n") | ||||
|                     .append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n"); | ||||
|         if(dateCreated != null) { | ||||
|                 .append("|description=").append(getDescription()).append("\n") | ||||
|                 .append("|source=").append("{{own}}\n") | ||||
|                 .append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n"); | ||||
|         if (dateCreated != null) { | ||||
|             buffer | ||||
|                     .append("|date={{According to EXIF data|").append(isoFormat.format(dateCreated)).append("}}\n"); | ||||
|         } | ||||
|  | @ -148,13 +145,13 @@ public class Contribution extends Media { | |||
| 
 | ||||
|         //Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null | ||||
|         if (decimalCoords != null) { | ||||
|                 buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n"); | ||||
|             buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n"); | ||||
|         } | ||||
| 
 | ||||
|         buffer.append("== {{int:license-header}} ==\n") | ||||
|                 .append(Utils.licenseTemplateFor(getLicense())).append("\n\n") | ||||
|             .append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n") | ||||
|             .append(getTrackingTemplates()); | ||||
|                 .append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n") | ||||
|                 .append(getTrackingTemplates()); | ||||
|         return buffer.toString(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -164,19 +161,19 @@ public class Contribution extends Media { | |||
| 
 | ||||
|     public void save() { | ||||
|         try { | ||||
|             if(contentUri == null) { | ||||
|             if (contentUri == null) { | ||||
|                 contentUri = client.insert(ContributionsContentProvider.BASE_URI, this.toContentValues()); | ||||
|             } else { | ||||
|                 client.update(contentUri, toContentValues(), null, null); | ||||
|             } | ||||
|         } catch(RemoteException e) { | ||||
|         } catch (RemoteException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void delete() { | ||||
|         try { | ||||
|             if(contentUri == null) { | ||||
|             if (contentUri == null) { | ||||
|                 // noooo | ||||
|                 throw new RuntimeException("tried to delete item with no content URI"); | ||||
|             } else { | ||||
|  | @ -191,20 +188,20 @@ public class Contribution extends Media { | |||
|     public ContentValues toContentValues() { | ||||
|         ContentValues cv = new ContentValues(); | ||||
|         cv.put(Table.COLUMN_FILENAME, getFilename()); | ||||
|         if(getLocalUri() != null) { | ||||
|         if (getLocalUri() != null) { | ||||
|             cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString()); | ||||
|         } | ||||
|         if(getImageUrl() != null) { | ||||
|         if (getImageUrl() != null) { | ||||
|             cv.put(Table.COLUMN_IMAGE_URL, getImageUrl()); | ||||
|         } | ||||
|         if(getDateUploaded() != null) { | ||||
|         if (getDateUploaded() != null) { | ||||
|             cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime()); | ||||
|         } | ||||
|         cv.put(Table.COLUMN_LENGTH, getDataLength()); | ||||
|         cv.put(Table.COLUMN_TIMESTAMP, getTimestamp().getTime()); | ||||
|         cv.put(Table.COLUMN_STATE, getState()); | ||||
|         cv.put(Table.COLUMN_TRANSFERRED, transferred); | ||||
|         cv.put(Table.COLUMN_SOURCE,  source); | ||||
|         cv.put(Table.COLUMN_SOURCE, source); | ||||
|         cv.put(Table.COLUMN_DESCRIPTION, description); | ||||
|         cv.put(Table.COLUMN_CREATOR, creator); | ||||
|         cv.put(Table.COLUMN_MULTIPLE, isMultiple ? 1 : 0); | ||||
|  | @ -240,7 +237,7 @@ public class Contribution extends Media { | |||
|             c.timestamp = cursor.getLong(4) == 0 ? null : new Date(cursor.getLong(4)); | ||||
|             c.state = cursor.getInt(5); | ||||
|             c.dataLength = cursor.getLong(6); | ||||
|             c.dateUploaded =  cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7)); | ||||
|             c.dateUploaded = cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7)); | ||||
|             c.transferred = cursor.getLong(8); | ||||
|             c.source = cursor.getString(9); | ||||
|             c.description = cursor.getString(10); | ||||
|  | @ -324,7 +321,7 @@ public class Contribution extends Media { | |||
|                 + "width INTEGER," | ||||
|                 + "height INTEGER," | ||||
|                 + "LICENSE STRING" | ||||
|         + ");"; | ||||
|                 + ");"; | ||||
| 
 | ||||
| 
 | ||||
|         public static void onCreate(SQLiteDatabase db) { | ||||
|  | @ -337,36 +334,36 @@ public class Contribution extends Media { | |||
|         } | ||||
| 
 | ||||
|         public static void onUpdate(SQLiteDatabase db, int from, int to) { | ||||
|             if(from == to) { | ||||
|             if (from == to) { | ||||
|                 return; | ||||
|             } | ||||
|             if(from == 1) { | ||||
|             if (from == 1) { | ||||
|                 db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN description STRING;"); | ||||
|                 db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN creator STRING;"); | ||||
|                 from++; | ||||
|                 onUpdate(db, from, to); | ||||
|                 return; | ||||
|             } | ||||
|             if(from == 2) { | ||||
|             if (from == 2) { | ||||
|                 db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN multiple INTEGER;"); | ||||
|                 db.execSQL("UPDATE " + TABLE_NAME + " SET multiple = 0"); | ||||
|                 from++; | ||||
|                 onUpdate(db, from, to); | ||||
|                 return; | ||||
|             } | ||||
|             if(from == 3) { | ||||
|             if (from == 3) { | ||||
|                 // Do nothing | ||||
|                 from++; | ||||
|                 onUpdate(db, from, to); | ||||
|                 return; | ||||
|             } | ||||
|             if(from == 4) { | ||||
|             if (from == 4) { | ||||
|                 // Do nothing -- added Category | ||||
|                 from++; | ||||
|                 onUpdate(db, from, to); | ||||
|                 return; | ||||
|             } | ||||
|             if(from == 5) { | ||||
|             if (from == 5) { | ||||
|                 // Added width and height fields | ||||
|                 db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN width INTEGER;"); | ||||
|                 db.execSQL("UPDATE " + TABLE_NAME + " SET width = 0"); | ||||
|  |  | |||
|  | @ -3,15 +3,12 @@ package fr.free.nrw.commons.contributions; | |||
| import android.app.Activity; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Environment; | ||||
| import android.provider.MediaStore; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v4.content.FileProvider; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import fr.free.nrw.commons.upload.ShareActivity; | ||||
|  |  | |||
|  | @ -12,15 +12,15 @@ import android.os.Bundle; | |||
| import android.os.RemoteException; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.mwapi.LogEventResult; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | ||||
|  | @ -61,29 +61,17 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|     public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { | ||||
|         // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you! | ||||
|         String user = account.name; | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE); | ||||
|         String lastModified = prefs.getString("lastSyncTimestamp", ""); | ||||
|         Date curTime = new Date(); | ||||
|         ApiResult result; | ||||
|         LogEventResult result; | ||||
|         Boolean done = false; | ||||
|         String queryContinue = null; | ||||
|         while(!done) { | ||||
| 
 | ||||
|             try { | ||||
|                 MWApi.RequestBuilder builder = api.action("query") | ||||
|                         .param("list", "logevents") | ||||
|                         .param("letype", "upload") | ||||
|                         .param("leprop", "title|timestamp|ids") | ||||
|                         .param("leuser", user) | ||||
|                         .param("lelimit", getLimit()); | ||||
|                 if(!TextUtils.isEmpty(lastModified)) { | ||||
|                     builder.param("leend", lastModified); | ||||
|                 } | ||||
|                 if(!TextUtils.isEmpty(queryContinue)) { | ||||
|                     builder.param("lestart", queryContinue); | ||||
|                 } | ||||
|                 result = builder.get(); | ||||
|                 result = api.logEvents(user, lastModified, queryContinue, getLimit()); | ||||
|             } catch (IOException e) { | ||||
|                 // There isn't really much we can do, eh? | ||||
|                 // FIXME: Perhaps add EventLogging? | ||||
|  | @ -93,22 +81,21 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|             } | ||||
|             Timber.d("Last modified at %s", lastModified); | ||||
| 
 | ||||
|             ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item"); | ||||
|             Timber.d("%d results!", uploads.size()); | ||||
|             List<LogEventResult.LogEvent> logEvents = result.getLogEvents(); | ||||
|             Timber.d("%d results!", logEvents.size()); | ||||
|             ArrayList<ContentValues> imageValues = new ArrayList<>(); | ||||
|             for(ApiResult image: uploads) { | ||||
|                 String pageId = image.getString("@pageid"); | ||||
|                 if (pageId.equals("0")) { | ||||
|             for (LogEventResult.LogEvent image : logEvents) { | ||||
|                 if (image.isDeleted()) { | ||||
|                     // means that this upload was deleted. | ||||
|                     continue; | ||||
|                 } | ||||
|                 String filename = image.getString("@title"); | ||||
|                 String filename = image.getFilename(); | ||||
|                 if(fileExists(contentProviderClient, filename)) { | ||||
|                     Timber.d("Skipping %s", filename); | ||||
|                     continue; | ||||
|                 } | ||||
|                 String thumbUrl = Utils.makeThumbBaseUrl(filename); | ||||
|                 Date dateUpdated = Utils.parseMWDate(image.getString("@timestamp")); | ||||
|                 Date dateUpdated = image.getDateUpdated(); | ||||
|                 Contribution contrib = new Contribution(null, thumbUrl, filename, "", -1, dateUpdated, dateUpdated, user, "", ""); | ||||
|                 contrib.setState(Contribution.STATE_COMPLETED); | ||||
|                 imageValues.add(contrib.toContentValues()); | ||||
|  | @ -130,7 +117,8 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|                     throw new RuntimeException(e); | ||||
|                 } | ||||
|             } | ||||
|             queryContinue = result.getString("/api/query-continue/logevents/@lestart"); | ||||
| 
 | ||||
|             queryContinue = result.getQueryContinue(); | ||||
|             if(TextUtils.isEmpty(queryContinue)) { | ||||
|                 done = true; | ||||
|             } | ||||
|  |  | |||
|  | @ -28,11 +28,11 @@ import android.view.View; | |||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.EventLog; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| 
 | ||||
| public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPageChangeListener { | ||||
|     private ViewPager pager; | ||||
|  |  | |||
|  | @ -12,15 +12,13 @@ import android.database.Cursor; | |||
| import android.os.Bundle; | ||||
| import android.os.RemoteException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | ||||
|  | @ -41,14 +39,14 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|         } | ||||
| 
 | ||||
|         // Exit early if nothing to do | ||||
|         if(allModifications == null || allModifications.getCount() == 0) { | ||||
|         if (allModifications == null || allModifications.getCount() == 0) { | ||||
|             Timber.d("No modifications to perform"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         String authCookie; | ||||
|         try { | ||||
|              authCookie = AccountManager.get(getContext()).blockingGetAuthToken(account, "", false); | ||||
|             authCookie = AccountManager.get(getContext()).blockingGetAuthToken(account, "", false); | ||||
|         } catch (OperationCanceledException | AuthenticatorException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } catch (IOException e) { | ||||
|  | @ -56,16 +54,15 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(Utils.isNullOrWhiteSpace(authCookie)) { | ||||
|         if (Utils.isNullOrWhiteSpace(authCookie)) { | ||||
|             Timber.d("Could not authenticate :("); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         api.setAuthCookie(authCookie); | ||||
|         String editToken; | ||||
| 
 | ||||
|         ApiResult requestResult, responseResult; | ||||
|         try { | ||||
|             editToken = api.getEditToken(); | ||||
|         } catch (IOException e) { | ||||
|  | @ -81,7 +78,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|         try { | ||||
|             contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); | ||||
| 
 | ||||
|             while(!allModifications.isAfterLast()) { | ||||
|             while (!allModifications.isAfterLast()) { | ||||
|                 ModifierSequence sequence = ModifierSequence.fromCursor(allModifications); | ||||
|                 sequence.setContentProviderClient(contentProviderClient); | ||||
|                 Contribution contrib; | ||||
|  | @ -95,41 +92,31 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|                 contributionCursor.moveToFirst(); | ||||
|                 contrib = Contribution.fromCursor(contributionCursor); | ||||
| 
 | ||||
|                 if(contrib.getState() == Contribution.STATE_COMPLETED) { | ||||
| 
 | ||||
|                 if (contrib.getState() == Contribution.STATE_COMPLETED) { | ||||
|                     String pageContent; | ||||
|                     try { | ||||
|                         requestResult = api.action("query") | ||||
|                                 .param("prop", "revisions") | ||||
|                                 .param("rvprop", "timestamp|content") | ||||
|                                 .param("titles", contrib.getFilename()) | ||||
|                                 .get(); | ||||
|                         pageContent = api.revisionsByFilename(contrib.getFilename()); | ||||
|                     } catch (IOException e) { | ||||
|                         Timber.d("Network fuckup on modifications sync!"); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     Timber.d("Page content is %s", Utils.getStringFromDOM(requestResult.getDocument())); | ||||
|                     String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev"); | ||||
|                     String processedPageContent = sequence.executeModifications(contrib.getFilename(),  pageContent); | ||||
|                     Timber.d("Page content is %s", pageContent); | ||||
|                     String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent); | ||||
| 
 | ||||
|                     String editResult; | ||||
|                     try { | ||||
|                         responseResult = api.action("edit") | ||||
|                                 .param("title", contrib.getFilename()) | ||||
|                                 .param("token", editToken) | ||||
|                                 .param("text", processedPageContent) | ||||
|                                 .param("summary", sequence.getEditSummary()) | ||||
|                                 .post(); | ||||
|                         editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary()); | ||||
|                     } catch (IOException e) { | ||||
|                         Timber.d("Network fuckup on modifications sync!"); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     Timber.d("Response is %s", Utils.getStringFromDOM(responseResult.getDocument())); | ||||
|                     Timber.d("Response is %s", editResult); | ||||
| 
 | ||||
|                     String result = responseResult.getString("/api/edit/@result"); | ||||
|                     if(!result.equals("Success")) { | ||||
|                     if (!editResult.equals("Success")) { | ||||
|                         // FIXME: Log this somewhere else | ||||
|                         Timber.d("Non success result! %s", result); | ||||
|                         Timber.d("Non success result! %s", editResult); | ||||
|                     } else { | ||||
|                         sequence.delete(); | ||||
|                     } | ||||
|  | @ -137,7 +124,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|                 allModifications.moveToNext(); | ||||
|             } | ||||
|         } finally { | ||||
|             if(contributionsClient != null) { | ||||
|             if (contributionsClient != null) { | ||||
|                 contributionsClient.release(); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -0,0 +1,374 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.apache.http.HttpResponse; | ||||
| import org.apache.http.conn.ClientConnectionManager; | ||||
| import org.apache.http.conn.scheme.PlainSocketFactory; | ||||
| import org.apache.http.conn.scheme.Scheme; | ||||
| import org.apache.http.conn.scheme.SchemeRegistry; | ||||
| import org.apache.http.conn.ssl.SSLSocketFactory; | ||||
| import org.apache.http.impl.client.AbstractHttpClient; | ||||
| import org.apache.http.impl.client.DefaultHttpClient; | ||||
| import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; | ||||
| import org.apache.http.params.BasicHttpParams; | ||||
| import org.apache.http.params.CoreProtocolPNames; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| import org.mediawiki.api.MWApi; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URL; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import in.yuvi.http.fluent.Http; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * @author Addshore | ||||
|  */ | ||||
| public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | ||||
|     private static final String THUMB_SIZE = "640"; | ||||
|     private AbstractHttpClient httpClient; | ||||
|     private MWApi api; | ||||
| 
 | ||||
|     public ApacheHttpClientMediaWikiApi(String apiURL) { | ||||
|         BasicHttpParams params = new BasicHttpParams(); | ||||
|         SchemeRegistry schemeRegistry = new SchemeRegistry(); | ||||
|         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); | ||||
|         final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory(); | ||||
|         schemeRegistry.register(new Scheme("https", sslSocketFactory, 443)); | ||||
|         ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); | ||||
|         params.setParameter(CoreProtocolPNames.USER_AGENT, "Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE); | ||||
|         httpClient = new DefaultHttpClient(cm, params); | ||||
|         api = new MWApi(apiURL, httpClient); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param username String | ||||
|      * @param password String | ||||
|      * @return String as returned by this.getErrorCodeToReturn() | ||||
|      * @throws IOException On api request IO issue | ||||
|      */ | ||||
|     public String login(String username, String password) throws IOException { | ||||
|         return getErrorCodeToReturn(api.action("clientlogin") | ||||
|                 .param("rememberMe", "1") | ||||
|                 .param("username", username) | ||||
|                 .param("password", password) | ||||
|                 .param("logintoken", getLoginToken()) | ||||
|                 .param("loginreturnurl", "https://commons.wikimedia.org") | ||||
|                 .post()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param username      String | ||||
|      * @param password      String | ||||
|      * @param twoFactorCode String | ||||
|      * @return String as returned by this.getErrorCodeToReturn() | ||||
|      * @throws IOException On api request IO issue | ||||
|      */ | ||||
|     public String login(String username, String password, String twoFactorCode) throws IOException { | ||||
|         return getErrorCodeToReturn(api.action("clientlogin") | ||||
|                 .param("rememberMe", "1") | ||||
|                 .param("username", username) | ||||
|                 .param("password", password) | ||||
|                 .param("logintoken", getLoginToken()) | ||||
|                 .param("logincontinue", "1") | ||||
|                 .param("OATHToken", twoFactorCode) | ||||
|                 .post()); | ||||
|     } | ||||
| 
 | ||||
|     private String getLoginToken() throws IOException { | ||||
|         return api.action("query") | ||||
|                 .param("action", "query") | ||||
|                 .param("meta", "tokens") | ||||
|                 .param("type", "login") | ||||
|                 .post() | ||||
|                 .getString("/api/query/tokens/@logintoken"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param loginApiResult ApiResult Any clientlogin api result | ||||
|      * @return String On success: "PASS" | ||||
|      * continue: "2FA" (More information required for 2FA) | ||||
|      * failure: A failure message code (defined by mediawiki) | ||||
|      * misc:    genericerror-UI, genericerror-REDIRECT, genericerror-RESTART | ||||
|      */ | ||||
|     private String getErrorCodeToReturn(ApiResult loginApiResult) { | ||||
|         String status = loginApiResult.getString("/api/clientlogin/@status"); | ||||
|         if (status.equals("PASS")) { | ||||
|             api.isLoggedIn = true; | ||||
|             return status; | ||||
|         } else if (status.equals("FAIL")) { | ||||
|             return loginApiResult.getString("/api/clientlogin/@messagecode"); | ||||
|         } else if ( | ||||
|                 status.equals("UI") | ||||
|                         && loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest") | ||||
|                         && loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).") | ||||
|                 ) { | ||||
|             return "2FA"; | ||||
|         } | ||||
| 
 | ||||
|         // UI, REDIRECT, RESTART | ||||
|         return "genericerror-" + status; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getAuthCookie() { | ||||
|         return api.getAuthCookie(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setAuthCookie(String authCookie) { | ||||
|         api.setAuthCookie(authCookie); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean validateLogin() throws IOException { | ||||
|         return api.validateLogin(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getEditToken() throws IOException { | ||||
|         return api.getEditToken(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean fileExistsWithName(String fileName) throws IOException { | ||||
|         return api.action("query") | ||||
|                 .param("prop", "imageinfo") | ||||
|                 .param("titles", "File:" + fileName) | ||||
|                 .get() | ||||
|                 .getNodes("/api/query/pages/page/imageinfo").size() > 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Nullable | ||||
|     public String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException { | ||||
|         return api.action("edit") | ||||
|                 .param("title", filename) | ||||
|                 .param("token", editToken) | ||||
|                 .param("text", processedPageContent) | ||||
|                 .param("summary", summary) | ||||
|                 .post() | ||||
|                 .getString("/api/edit/@result"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String findThumbnailByFilename(String filename) throws IOException { | ||||
|         return api.action("query") | ||||
|                 .param("format", "xml") | ||||
|                 .param("prop", "imageinfo") | ||||
|                 .param("iiprop", "url") | ||||
|                 .param("iiurlwidth", THUMB_SIZE) | ||||
|                 .param("titles", filename) | ||||
|                 .get() | ||||
|                 .getString("/api/query/pages/page/imageinfo/ii/@thumburl"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public MediaResult fetchMediaByFilename(String filename) throws IOException { | ||||
|         ApiResult apiResult = api.action("query") | ||||
|                 .param("prop", "revisions") | ||||
|                 .param("titles", filename) | ||||
|                 .param("rvprop", "content") | ||||
|                 .param("rvlimit", 1) | ||||
|                 .param("rvgeneratexml", 1) | ||||
|                 .get(); | ||||
| 
 | ||||
|         return new MediaResult( | ||||
|                 apiResult.getString("/api/query/pages/page/revisions/rev"), | ||||
|                 apiResult.getString("/api/query/pages/page/revisions/rev/@parsetree")); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public List<String> searchCategories(int searchCatsLimit, String filterValue) throws IOException { | ||||
|         List<ApiResult> categoryNodes = api.action("query") | ||||
|                 .param("format", "xml") | ||||
|                 .param("list", "search") | ||||
|                 .param("srwhat", "text") | ||||
|                 .param("srnamespace", "14") | ||||
|                 .param("srlimit", searchCatsLimit) | ||||
|                 .param("srsearch", filterValue) | ||||
|                 .get() | ||||
|                 .getNodes("/api/query/search/p/@title"); | ||||
| 
 | ||||
|         if (categoryNodes == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
| 
 | ||||
|         List<String> categories = new ArrayList<>(); | ||||
|         for (ApiResult categoryNode : categoryNodes) { | ||||
|             String cat = categoryNode.getDocument().getTextContent(); | ||||
|             String catString = cat.replace("Category:", ""); | ||||
|             categories.add(catString); | ||||
|         } | ||||
| 
 | ||||
|         return categories; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public List<String> allCategories(int searchCatsLimit, String filterValue) throws IOException { | ||||
|         ArrayList<ApiResult> categoryNodes = api.action("query") | ||||
|                 .param("list", "allcategories") | ||||
|                 .param("acprefix", filterValue) | ||||
|                 .param("aclimit", searchCatsLimit) | ||||
|                 .get() | ||||
|                 .getNodes("/api/query/allcategories/c"); | ||||
| 
 | ||||
|         if (categoryNodes == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
| 
 | ||||
|         List<String> categories = new ArrayList<>(); | ||||
|         for (ApiResult categoryNode : categoryNodes) { | ||||
|             categories.add(categoryNode.getDocument().getTextContent()); | ||||
|         } | ||||
| 
 | ||||
|         return categories; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public List<String> searchTitles(int searchCatsLimit, String title) throws IOException { | ||||
|         ArrayList<ApiResult> categoryNodes = api.action("query") | ||||
|                 .param("format", "xml") | ||||
|                 .param("list", "search") | ||||
|                 .param("srwhat", "text") | ||||
|                 .param("srnamespace", "14") | ||||
|                 .param("srlimit", searchCatsLimit) | ||||
|                 .param("srsearch", title) | ||||
|                 .get() | ||||
|                 .getNodes("/api/query/search/p/@title"); | ||||
| 
 | ||||
|         if (categoryNodes == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
| 
 | ||||
|         List<String> titleCategories = new ArrayList<>(); | ||||
|         for (ApiResult categoryNode : categoryNodes) { | ||||
|             String cat = categoryNode.getDocument().getTextContent(); | ||||
|             String catString = cat.replace("Category:", ""); | ||||
|             titleCategories.add(catString); | ||||
|         } | ||||
| 
 | ||||
|         return titleCategories; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException { | ||||
|         org.mediawiki.api.MWApi.RequestBuilder builder = api.action("query") | ||||
|                 .param("list", "logevents") | ||||
|                 .param("letype", "upload") | ||||
|                 .param("leprop", "title|timestamp|ids") | ||||
|                 .param("leuser", user) | ||||
|                 .param("lelimit", limit); | ||||
|         if (!TextUtils.isEmpty(lastModified)) { | ||||
|             builder.param("leend", lastModified); | ||||
|         } | ||||
|         if (!TextUtils.isEmpty(queryContinue)) { | ||||
|             builder.param("lestart", queryContinue); | ||||
|         } | ||||
|         ApiResult result = builder.get(); | ||||
| 
 | ||||
|         return new LogEventResult( | ||||
|                 getLogEventsFromResult(result), | ||||
|                 result.getString("/api/query-continue/logevents/@lestart")); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private ArrayList<LogEventResult.LogEvent> getLogEventsFromResult(ApiResult result) { | ||||
|         ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item"); | ||||
|         Timber.d("%d results!", uploads.size()); | ||||
|         ArrayList<LogEventResult.LogEvent> logEvents = new ArrayList<>(); | ||||
|         for (ApiResult image : uploads) { | ||||
|             logEvents.add(new LogEventResult.LogEvent( | ||||
|                     image.getString("@pageid"), | ||||
|                     image.getString("@title"), | ||||
|                     Utils.parseMWDate(image.getString("@timestamp"))) | ||||
|             ); | ||||
|         } | ||||
|         return logEvents; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @Nullable | ||||
|     public String revisionsByFilename(String filename) throws IOException { | ||||
|         return api.action("query") | ||||
|                 .param("prop", "revisions") | ||||
|                 .param("rvprop", "timestamp|content") | ||||
|                 .param("titles", filename) | ||||
|                 .get() | ||||
|                 .getString("/api/query/pages/page/revisions/rev"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean existingFile(String fileSha1) throws IOException { | ||||
|         return api.action("query") | ||||
|                 .param("format", "xml") | ||||
|                 .param("list", "allimages") | ||||
|                 .param("aisha1", fileSha1) | ||||
|                 .get() | ||||
|                 .getNodes("/api/query/allimages/img").size() > 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean logEvents(LogBuilder[] logBuilders) { | ||||
|         boolean allSuccess = true; | ||||
|         // Not using the default URL connection, since that seems to have different behavior than the rest of the code | ||||
|         for (LogBuilder logBuilder : logBuilders) { | ||||
|             try { | ||||
|                 URL url = logBuilder.toUrl(); | ||||
|                 HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse(); | ||||
| 
 | ||||
|                 if (response.getStatusLine().getStatusCode() != 204) { | ||||
|                     allSuccess = false; | ||||
|                 } | ||||
|                 Timber.d("EventLog hit %s", url); | ||||
| 
 | ||||
|             } catch (IOException e) { | ||||
|                 // Probably just ignore for now. Can be much more robust with a service, etc later on. | ||||
|                 Timber.d("IO Error, EventLog hit skipped"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return allSuccess; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @NonNull | ||||
|     public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException { | ||||
|         ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, new in.yuvi.http.fluent.ProgressListener() { | ||||
|             @Override | ||||
|             public void onProgress(long transferred, long total) { | ||||
|                 progressListener.onProgress(transferred, total); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         Log.e("WTF", "Result: "+result.toString()); | ||||
| 
 | ||||
|         String resultStatus = result.getString("/api/upload/@result"); | ||||
|         if (!resultStatus.equals("Success")) { | ||||
|             String errorCode = result.getString("/api/error/@code"); | ||||
|             return new UploadResult(resultStatus, errorCode); | ||||
|         } else { | ||||
|             Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); | ||||
|             String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename | ||||
|             String imageUrl = result.getString("/api/upload/imageinfo/@url"); | ||||
|             return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Utils; | ||||
| 
 | ||||
| public class EventLog { | ||||
|     static final String DEVICE; | ||||
| 
 | ||||
|     static { | ||||
|         if (Build.MODEL.startsWith(Build.MANUFACTURER)) { | ||||
|             DEVICE = Utils.capitalize(Build.MODEL); | ||||
|         } else { | ||||
|             DEVICE = Utils.capitalize(Build.MANUFACTURER) + " " + Build.MODEL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static LogBuilder schema(String schema, long revision) { | ||||
|         return new LogBuilder(schema, revision); | ||||
|     } | ||||
| 
 | ||||
|     public static LogBuilder schema(Object[] scid) { | ||||
|         if (scid.length != 2) { | ||||
|             throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second"); | ||||
|         } | ||||
|         return schema((String) scid[0], (Long) scid[1]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										71
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Build; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.MalformedURLException; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| 
 | ||||
| public class LogBuilder { | ||||
|     private JSONObject data; | ||||
|     private long rev; | ||||
|     private String schema; | ||||
| 
 | ||||
|     LogBuilder(String schema, long revision) { | ||||
|         data = new JSONObject(); | ||||
|         this.schema = schema; | ||||
|         this.rev = revision; | ||||
|     } | ||||
| 
 | ||||
|     public LogBuilder param(String key, Object value) { | ||||
|         try { | ||||
|             data.put(key, value); | ||||
|         } catch (JSONException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     URL toUrl() { | ||||
|         JSONObject fullData = new JSONObject(); | ||||
|         try { | ||||
|             fullData.put("schema", schema); | ||||
|             fullData.put("revision", rev); | ||||
|             fullData.put("wiki", CommonsApplication.EVENTLOG_WIKI); | ||||
|             data.put("device", EventLog.DEVICE); | ||||
|             data.put("platform", "Android/" + Build.VERSION.RELEASE); | ||||
|             data.put("appversion", "Android/" + BuildConfig.VERSION_NAME); | ||||
|             fullData.put("event", data); | ||||
|             return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";"); | ||||
|         } catch (MalformedURLException | JSONException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // force param disregards user preference | ||||
|     // Use *only* for tracking the user preference change for EventLogging | ||||
|     // Attempting to use anywhere else will cause kitten explosions | ||||
|     public void log(boolean force) { | ||||
|         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|         if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) { | ||||
|             return; // User has disabled tracking | ||||
|         } | ||||
|         LogTask logTask = new LogTask(); | ||||
|         logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this); | ||||
|     } | ||||
| 
 | ||||
|     public void log() { | ||||
|         log(false); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class LogEventResult { | ||||
|     private final List<LogEvent> logEvents; | ||||
|     private final String queryContinue; | ||||
| 
 | ||||
|     LogEventResult(@NonNull List<LogEvent> logEvents, String queryContinue) { | ||||
|         this.logEvents = logEvents; | ||||
|         this.queryContinue = queryContinue; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public List<LogEvent> getLogEvents() { | ||||
|         return logEvents; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public String getQueryContinue() { | ||||
|         return queryContinue; | ||||
|     } | ||||
| 
 | ||||
|     public static class LogEvent { | ||||
|         private final String pageId; | ||||
|         private final String filename; | ||||
|         private final Date dateUpdated; | ||||
| 
 | ||||
|         LogEvent(String pageId, String filename, Date dateUpdated) { | ||||
|             this.pageId = pageId; | ||||
|             this.filename = filename; | ||||
|             this.dateUpdated = dateUpdated; | ||||
|         } | ||||
| 
 | ||||
|         public boolean isDeleted() { | ||||
|             return pageId.equals("0"); | ||||
|         } | ||||
| 
 | ||||
|         public String getFilename() { | ||||
|             return filename; | ||||
|         } | ||||
| 
 | ||||
|         public Date getDateUpdated() { | ||||
|             return dateUpdated; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| 
 | ||||
| class LogTask extends AsyncTask<LogBuilder, Void, Boolean> { | ||||
|     @Override | ||||
|     protected Boolean doInBackground(LogBuilder... logBuilders) { | ||||
|         return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| public class MediaResult { | ||||
|     private final String wikiSource; | ||||
|     private final String parseTreeXmlSource; | ||||
| 
 | ||||
|     MediaResult(String wikiSource, String parseTreeXmlSource) { | ||||
|         this.wikiSource = wikiSource; | ||||
|         this.parseTreeXmlSource = parseTreeXmlSource; | ||||
|     } | ||||
| 
 | ||||
|     public String getWikiSource() { | ||||
|         return wikiSource; | ||||
|     } | ||||
| 
 | ||||
|     public String getParseTreeXmlSource() { | ||||
|         return parseTreeXmlSource; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public interface MediaWikiApi { | ||||
|     String getAuthCookie(); | ||||
| 
 | ||||
|     void setAuthCookie(String authCookie); | ||||
| 
 | ||||
|     String login(String username, String password) throws IOException; | ||||
| 
 | ||||
|     String login(String username, String password, String twoFactorCode) throws IOException; | ||||
| 
 | ||||
|     boolean validateLogin() throws IOException; | ||||
| 
 | ||||
|     String getEditToken() throws IOException; | ||||
| 
 | ||||
|     boolean fileExistsWithName(String fileName) throws IOException; | ||||
| 
 | ||||
|     String findThumbnailByFilename(String filename) throws IOException; | ||||
| 
 | ||||
|     boolean logEvents(LogBuilder[] logBuilders); | ||||
| 
 | ||||
|     @NonNull | ||||
|     UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException; | ||||
| 
 | ||||
|     @Nullable | ||||
|     String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     MediaResult fetchMediaByFilename(String filename) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<String> searchCategories(int searchCatsLimit, String filterValue) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<String> allCategories(int searchCatsLimit, String filter) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     List<String> searchTitles(int searchCatsLimit, String title) throws IOException; | ||||
| 
 | ||||
|     @Nullable | ||||
|     String revisionsByFilename(String filename) throws IOException; | ||||
| 
 | ||||
|     boolean existingFile(String fileSha1) throws IOException; | ||||
| 
 | ||||
|     @NonNull | ||||
|     LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException; | ||||
| 
 | ||||
|     interface ProgressListener { | ||||
|         void onProgress(long transferred, long total); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| public class UploadResult { | ||||
|     private String errorCode; | ||||
|     private String resultStatus; | ||||
|     private Date dateUploaded; | ||||
|     private String imageUrl; | ||||
|     private String canonicalFilename; | ||||
| 
 | ||||
|     UploadResult(String resultStatus, String errorCode) { | ||||
|         this.resultStatus = resultStatus; | ||||
|         this.errorCode = errorCode; | ||||
|     } | ||||
| 
 | ||||
|     UploadResult(String resultStatus, Date dateUploaded, String canonicalFilename, String imageUrl) { | ||||
|         this.resultStatus = resultStatus; | ||||
|         this.dateUploaded = dateUploaded; | ||||
|         this.canonicalFilename = canonicalFilename; | ||||
|         this.imageUrl = imageUrl; | ||||
|     } | ||||
| 
 | ||||
|     public Date getDateUploaded() { | ||||
|         return dateUploaded; | ||||
|     } | ||||
| 
 | ||||
|     public String getImageUrl() { | ||||
|         return imageUrl; | ||||
|     } | ||||
| 
 | ||||
|     public String getCanonicalFilename() { | ||||
|         return canonicalFilename; | ||||
|     } | ||||
| 
 | ||||
|     public String getErrorCode() { | ||||
|         return errorCode; | ||||
|     } | ||||
| 
 | ||||
|     public String getResultStatus() { | ||||
|         return resultStatus; | ||||
|     } | ||||
| } | ||||
|  | @ -6,15 +6,12 @@ import android.content.Intent; | |||
| import android.os.AsyncTask; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| 
 | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.MWApi; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -49,27 +46,18 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { | |||
| 
 | ||||
|     @Override | ||||
|     protected Boolean doInBackground(Void... voids) { | ||||
|         MWApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         ApiResult result; | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
| 
 | ||||
|         // https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba | ||||
|         boolean fileExists; | ||||
|         try { | ||||
|             result = api.action("query") | ||||
|                     .param("format", "xml") | ||||
|                     .param("list", "allimages") | ||||
|                     .param("aisha1", fileSha1) | ||||
|                     .get(); | ||||
|             Timber.d("Searching Commons API for existing file: %s", result); | ||||
|             String fileSha1 = this.fileSha1; | ||||
|             fileExists = api.existingFile(fileSha1); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception: "); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img"); | ||||
|         Timber.d("Result nodes: %s", resultNodes); | ||||
| 
 | ||||
|         boolean fileExists = !resultNodes.isEmpty(); | ||||
| 
 | ||||
|         Timber.d("File already exists in Commons: %s", fileExists); | ||||
|         return fileExists; | ||||
|     } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import android.location.Location; | |||
| import android.location.LocationListener; | ||||
| import android.location.LocationManager; | ||||
| import android.media.ExifInterface; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
|  |  | |||
|  | @ -20,12 +20,10 @@ import android.view.inputmethod.InputMethodManager; | |||
| import android.widget.AdapterView; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.EventLog; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.auth.AuthenticatedActivity; | ||||
|  | @ -36,6 +34,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier; | |||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.modifications.TemplateRemoveModifier; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public  class       MultipleShareActivity | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ import android.net.Uri; | |||
| import android.os.Bundle; | ||||
| import android.support.graphics.drawable.VectorDrawableCompat; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.text.Editable; | ||||
| import android.text.TextUtils; | ||||
| import android.text.TextWatcher; | ||||
|  |  | |||
|  | @ -32,7 +32,6 @@ import java.util.List; | |||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.EventLog; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.auth.AuthenticatedActivity; | ||||
|  | @ -42,6 +41,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier; | |||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.modifications.TemplateRemoveModifier; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -209,16 +209,6 @@ public  class       ShareActivity | |||
|     protected void onAuthCookieAcquired(String authCookie) { | ||||
|         app.getMWApi().setAuthCookie(authCookie); | ||||
| 
 | ||||
|         SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView"); | ||||
|         categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization"); | ||||
|         if(shareView == null && categorizationFragment == null) { | ||||
|             shareView = new SingleUploadFragment(); | ||||
|             getSupportFragmentManager() | ||||
|                     .beginTransaction() | ||||
|                     .add(R.id.single_upload_fragment_container, shareView, "shareView") | ||||
|                     .commitAllowingStateLoss(); | ||||
|         } | ||||
|         uploadController.prepareService(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -311,6 +301,18 @@ public  class       ShareActivity | |||
|             } | ||||
|         } | ||||
|         performPreuploadProcessingOfFile(); | ||||
| 
 | ||||
| 
 | ||||
|         SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView"); | ||||
|         categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization"); | ||||
|         if(shareView == null && categorizationFragment == null) { | ||||
|             shareView = new SingleUploadFragment(); | ||||
|             getSupportFragmentManager() | ||||
|                     .beginTransaction() | ||||
|                     .add(R.id.single_upload_fragment_container, shareView, "shareView") | ||||
|                     .commitAllowingStateLoss(); | ||||
|         } | ||||
|         uploadController.prepareService(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  |  | |||
|  | @ -32,9 +32,9 @@ import butterknife.ButterKnife; | |||
| import butterknife.OnClick; | ||||
| import butterknife.OnItemSelected; | ||||
| import butterknife.OnTouch; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class SingleUploadFragment extends Fragment { | ||||
|  |  | |||
|  | @ -18,9 +18,9 @@ import java.util.Date; | |||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.HandlerService; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class UploadController { | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.app.Notification; | ||||
| import android.app.NotificationManager; | ||||
| import android.app.PendingIntent; | ||||
|  | @ -13,23 +14,25 @@ import android.support.v4.app.NotificationCompat; | |||
| import android.webkit.MimeTypeMap; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import fr.free.nrw.commons.*; | ||||
| import org.mediawiki.api.ApiResult; | ||||
| 
 | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.Date; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.HandlerService; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
| import in.yuvi.http.fluent.ProgressListener; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.UploadResult; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class UploadService extends HandlerService<Contribution> { | ||||
|  | @ -64,7 +67,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         super("UploadService"); | ||||
|     } | ||||
| 
 | ||||
|     private class NotificationUpdateProgressListener implements ProgressListener { | ||||
|     private class NotificationUpdateProgressListener implements MediaWikiApi.ProgressListener { | ||||
| 
 | ||||
|         String notificationTag; | ||||
|         boolean notificationTitleChanged; | ||||
|  | @ -175,10 +178,10 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         return START_REDELIVER_INTENT; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("StringFormatInvalid") | ||||
|     private void uploadContribution(Contribution contribution) { | ||||
|         MWApi api = app.getMWApi(); | ||||
|         MediaWikiApi api = app.getMWApi(); | ||||
| 
 | ||||
|         ApiResult result; | ||||
|         InputStream file = null; | ||||
| 
 | ||||
|         String notificationTag = contribution.getLocalUri().toString(); | ||||
|  | @ -235,32 +238,27 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                     getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()), | ||||
|                     contribution | ||||
|             ); | ||||
|             result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); | ||||
|             UploadResult uploadResult = api.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); | ||||
| 
 | ||||
|             Timber.d("Response is %s", Utils.getStringFromDOM(result.getDocument())); | ||||
|             Timber.d("Response is %s", uploadResult.toString()); | ||||
| 
 | ||||
|             curProgressNotification = null; | ||||
| 
 | ||||
|             String resultStatus = result.getString("/api/upload/@result"); | ||||
|             String resultStatus = uploadResult.getResultStatus(); | ||||
|             if(!resultStatus.equals("Success")) { | ||||
|                 String errorCode = result.getString("/api/error/@code"); | ||||
|                 showFailedNotification(contribution); | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|                         .param("username", app.getCurrentAccount().name) | ||||
|                         .param("source", contribution.getSource()) | ||||
|                         .param("multiple", contribution.getMultiple()) | ||||
|                         .param("result", errorCode) | ||||
|                         .param("result", uploadResult.getErrorCode()) | ||||
|                         .param("filename", contribution.getFilename()) | ||||
|                         .log(); | ||||
|             } else { | ||||
|                 Date dateUploaded = null; | ||||
|                 dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); | ||||
|                 String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename | ||||
|                 String imageUrl = result.getString("/api/upload/imageinfo/@url"); | ||||
|                 contribution.setFilename(canonicalFilename); | ||||
|                 contribution.setImageUrl(imageUrl); | ||||
|                 contribution.setFilename(uploadResult.getCanonicalFilename()); | ||||
|                 contribution.setImageUrl(uploadResult.getImageUrl()); | ||||
|                 contribution.setState(Contribution.STATE_COMPLETED); | ||||
|                 contribution.setDateUploaded(dateUploaded); | ||||
|                 contribution.setDateUploaded(uploadResult.getDateUploaded()); | ||||
|                 contribution.save(); | ||||
| 
 | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|  | @ -274,7 +272,6 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         } catch(IOException e) { | ||||
|             Timber.d("I have a network fuckup"); | ||||
|             showFailedNotification(contribution); | ||||
|             return; | ||||
|         } finally { | ||||
|             if ( filename != null ) { | ||||
|                 unfinishedUploads.remove(filename); | ||||
|  | @ -288,8 +285,9 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("StringFormatInvalid") | ||||
|     private void showFailedNotification(Contribution contribution) { | ||||
|         Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true) | ||||
|          Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true) | ||||
|                 .setSmallIcon(R.drawable.ic_launcher) | ||||
|                 .setAutoCancel(true) | ||||
|                 .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0)) | ||||
|  | @ -304,7 +302,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|     } | ||||
| 
 | ||||
|     private String findUniqueFilename(String fileName) throws IOException { | ||||
|         MWApi api = app.getMWApi(); | ||||
|         MediaWikiApi api = app.getMWApi(); | ||||
|         String sequenceFileName; | ||||
|         for ( int sequenceNumber = 1; true; sequenceNumber++ ) { | ||||
|             if (sequenceNumber == 1) { | ||||
|  | @ -320,7 +318,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                     sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2"); | ||||
|                 } | ||||
|             } | ||||
|             if ( fileExistsWithName(api, sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) { | ||||
|             if ( api.fileExistsWithName(sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) { | ||||
|                 continue; | ||||
|             } else { | ||||
|                 break; | ||||
|  | @ -328,15 +326,4 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         } | ||||
|         return sequenceFileName; | ||||
|     } | ||||
| 
 | ||||
|     private static boolean fileExistsWithName(MWApi api, String fileName) throws IOException { | ||||
|         ApiResult result; | ||||
| 
 | ||||
|         result = api.action("query") | ||||
|                 .param("prop", "imageinfo") | ||||
|                 .param("titles", "File:" + fileName) | ||||
|                 .get(); | ||||
| 
 | ||||
|         return result.getNodes("/api/query/pages/page/imageinfo").size() > 0; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| package fr.free.nrw.commons.utils; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| 
 | ||||
| import java.text.NumberFormat; | ||||
| 
 | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| 
 | ||||
| public class LengthUtils { | ||||
|     /** Returns a formatted distance string between two points. | ||||
|      * @param point1 LatLng type point1 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Mikel
						Mikel