Jcasariego has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/351692 )

Change subject: Retrofit ImageLicenseFetchTask
......................................................................

Retrofit ImageLicenseFetchTask

Port ImageLicenseFetchTask to Retrofit and try to stay as consistent as 
possible with the existing code.

Bug T152406

Change-Id: Ib6ad70b969507aaad7356fce2e2e9bf27aaaeddc
---
A app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryImageLicensePage.java
M app/src/main/java/org/wikipedia/gallery/GalleryItem.java
A app/src/main/java/org/wikipedia/page/ImageLicenseFetchClient.java
D app/src/main/java/org/wikipedia/page/ImageLicenseFetchTask.java
M app/src/main/java/org/wikipedia/page/snippet/ShareHandler.java
A app/src/test/java/org/wikipedia/page/ImageLicenseFetchClientTest.java
A app/src/test/res/raw/image_license.json
7 files changed, 403 insertions(+), 73 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia 
refs/changes/92/351692/1

diff --git 
a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryImageLicensePage.java 
b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryImageLicensePage.java
new file mode 100644
index 0000000..5a371f6
--- /dev/null
+++ 
b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryImageLicensePage.java
@@ -0,0 +1,76 @@
+package org.wikipedia.dataclient.mwapi;
+
+import android.support.annotation.Nullable;
+
+import java.util.List;
+
+public class MwQueryImageLicensePage extends MwQueryPage {
+    @SuppressWarnings("unused") @Nullable private List<ImageInfo> imageinfo;
+
+    public String imageLicense() {
+        return imageinfo != null
+                && imageinfo.get(0).extmetadata() != null
+                && imageinfo.get(0).extmetadata().license() != null
+                ? imageinfo.get(0).extmetadata().license().value() : "";
+    }
+
+    public String imageLicenseShortName() {
+        return imageinfo != null
+                && imageinfo.get(0).extmetadata() != null
+                && imageinfo.get(0).extmetadata().licenseShortName() != null
+                ? imageinfo.get(0).extmetadata().licenseShortName().value() : 
"";
+    }
+
+    public String imageLicenseUrl() {
+        return imageinfo != null
+                && imageinfo.get(0).extmetadata() != null
+                && imageinfo.get(0).extmetadata().licenseUrl() != null
+                ? imageinfo.get(0).extmetadata().licenseUrl().value() : "";
+    }
+
+    static class ImageInfo {
+        @SuppressWarnings("unused") private Extmetadata extmetadata;
+        Extmetadata extmetadata() {
+            return extmetadata;
+        }
+    }
+
+    static class Extmetadata {
+        @SuppressWarnings({"unused", "CheckStyle"}) private License License;
+        @SuppressWarnings({"unused", "CheckStyle"}) private LicenseShortName 
LicenseShortName;
+        @SuppressWarnings({"unused", "CheckStyle"}) private LicenseUrl 
LicenseUrl;
+
+        License license() {
+            return License;
+        }
+
+        LicenseShortName licenseShortName() {
+            return LicenseShortName;
+        }
+
+        LicenseUrl licenseUrl() {
+            return LicenseUrl;
+        }
+    }
+
+    static class License {
+        @SuppressWarnings("unused") private String value;
+        String value() {
+            return value;
+        }
+    }
+
+    static class LicenseShortName {
+        @SuppressWarnings("unused") private String value;
+        String value() {
+            return value;
+        }
+    }
+
+    static class LicenseUrl {
+        @SuppressWarnings("unused") private String value;
+        String value() {
+            return value;
+        }
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/gallery/GalleryItem.java 
b/app/src/main/java/org/wikipedia/gallery/GalleryItem.java
index cd1ace8..f5841ca 100644
--- a/app/src/main/java/org/wikipedia/gallery/GalleryItem.java
+++ b/app/src/main/java/org/wikipedia/gallery/GalleryItem.java
@@ -7,7 +7,6 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.wikipedia.page.ImageLicense;
-import org.wikipedia.page.ImageLicenseFetchTask;
 
 import java.util.HashMap;
 import java.util.Iterator;
@@ -149,9 +148,21 @@
                 String value = 
extmetadata.getJSONObject(key).getString("value");
                 metadata.put(key, value);
             }
-            license = 
ImageLicenseFetchTask.imageLicenseFromMetadata(extmetadata);
+            license = imageLicenseFromMetadata(extmetadata);
         } else {
             license = new ImageLicense();
         }
     }
+
+    @NonNull
+    public static ImageLicense imageLicenseFromMetadata(JSONObject 
extmetadata) {
+        return new ImageLicense(getValueForOptionalKey(extmetadata, "License"),
+                getValueForOptionalKey(extmetadata, "LicenseShortName"),
+                getValueForOptionalKey(extmetadata, "LicenseUrl"));
+    }
+
+    @NonNull
+    private static String getValueForOptionalKey(JSONObject object, String 
key) {
+        return object.has(key) ? object.optJSONObject(key).optString("value") 
: "";
+    }
 }
diff --git a/app/src/main/java/org/wikipedia/page/ImageLicenseFetchClient.java 
b/app/src/main/java/org/wikipedia/page/ImageLicenseFetchClient.java
new file mode 100644
index 0000000..2b3e0c9
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/page/ImageLicenseFetchClient.java
@@ -0,0 +1,78 @@
+package org.wikipedia.page;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.mwapi.MwException;
+import org.wikipedia.dataclient.mwapi.MwQueryImageLicensePage;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.dataclient.retrofit.MwCachedService;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+public class ImageLicenseFetchClient {
+    @NonNull private MwCachedService<Service> cachedService = new 
MwCachedService<>(Service.class);
+
+    public interface Callback {
+        void success(@NonNull Call<MwQueryResponse<QueryResult>> call, 
@NonNull List<MwQueryImageLicensePage> results);
+        void failure(@NonNull Call<MwQueryResponse<QueryResult>> call, 
@NonNull Throwable caught);
+    }
+
+    public Call<MwQueryResponse<QueryResult>> request(@NonNull WikiSite wiki,
+                                                      @NonNull PageTitle title,
+                                                      @NonNull Callback cb) {
+        List<PageTitle> titles = new ArrayList<>();
+        titles.add(title);
+
+        return request(wiki, cachedService.service(wiki), titles, cb);
+    }
+
+    @VisibleForTesting Call<MwQueryResponse<QueryResult>> request(final 
WikiSite wiki, @NonNull Service service,
+                                                                  @NonNull 
final List<PageTitle> titles,
+                                                                  @NonNull 
final Callback cb) {
+        Call<MwQueryResponse<QueryResult>> call = 
service.request(TextUtils.join("|", titles));
+
+        call.enqueue(new retrofit2.Callback<MwQueryResponse<QueryResult>>() {
+            @Override public void 
onResponse(Call<MwQueryResponse<QueryResult>> call,
+                                             
Response<MwQueryResponse<QueryResult>> response) {
+                if (response.body().success()) {
+                    cb.success(call, response.body().query().pages());
+                } else if (response.body().hasError()) {
+                    cb.failure(call, new 
MwException(response.body().getError()));
+                } else {
+                    cb.failure(call, new IOException("An unknown error 
occurred."));
+                }
+            }
+
+            @Override
+            public void onFailure(Call<MwQueryResponse<QueryResult>> call, 
Throwable t) {
+                cb.failure(call, t);
+            }
+        });
+
+        return call;
+    }
+
+
+    public class QueryResult {
+        @SuppressWarnings("unused") @Nullable private 
List<MwQueryImageLicensePage> pages;
+        @Nullable List<MwQueryImageLicensePage> pages() {
+            return pages;
+        }
+    }
+
+    @VisibleForTesting interface Service {
+        
@GET("w/api.php?action=query&format=json&formatversion=2&prop=imageinfo&iiprop=extmetadata")
+        Call<MwQueryResponse<QueryResult>> request(@NonNull @Query("titles") 
String titles);
+    }
+}
diff --git a/app/src/main/java/org/wikipedia/page/ImageLicenseFetchTask.java 
b/app/src/main/java/org/wikipedia/page/ImageLicenseFetchTask.java
deleted file mode 100644
index 43cc84c..0000000
--- a/app/src/main/java/org/wikipedia/page/ImageLicenseFetchTask.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.wikipedia.page;
-
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mediawiki.api.json.Api;
-import org.mediawiki.api.json.RequestBuilder;
-import org.wikipedia.dataclient.WikiSite;
-
-/**
- * Fetch license info for a single image.
- */
-public class ImageLicenseFetchTask extends PageQueryTask<ImageLicense> {
-    private static final String TAG = "ImageLicenseFetchTask";
-
-    public ImageLicenseFetchTask(Api api, WikiSite wiki, PageTitle title) {
-        super(api, wiki, title);
-    }
-
-    @Override
-    public void buildQueryParams(RequestBuilder builder) {
-        builder.param("prop", "imageinfo").param("iiprop", "extmetadata");
-    }
-
-    @Override
-    public ImageLicense processPage(int pageId, PageTitle pageTitle, 
JSONObject result) {
-        try {
-            JSONObject imageInfo = (JSONObject) 
result.getJSONArray("imageinfo").get(0);
-            return 
imageLicenseFromMetadata(imageInfo.getJSONObject("extmetadata"));
-        } catch (JSONException e) {
-            Log.w(TAG, e);
-        }
-        return new ImageLicense();
-    }
-
-    @NonNull
-    public static ImageLicense imageLicenseFromMetadata(JSONObject 
extmetadata) {
-        return new ImageLicense(getValueForOptionalKey(extmetadata, "License"),
-                getValueForOptionalKey(extmetadata, "LicenseShortName"),
-                getValueForOptionalKey(extmetadata, "LicenseUrl"));
-    }
-
-    @NonNull
-    private static String getValueForOptionalKey(JSONObject object, String 
key) {
-        return object.has(key) ? object.optJSONObject(key).optString("value") 
: "";
-    }
-}
diff --git a/app/src/main/java/org/wikipedia/page/snippet/ShareHandler.java 
b/app/src/main/java/org/wikipedia/page/snippet/ShareHandler.java
index 356b6fd..5b96470 100755
--- a/app/src/main/java/org/wikipedia/page/snippet/ShareHandler.java
+++ b/app/src/main/java/org/wikipedia/page/snippet/ShareHandler.java
@@ -25,8 +25,10 @@
 import org.wikipedia.activity.ActivityUtil;
 import org.wikipedia.analytics.ShareAFactFunnel;
 import org.wikipedia.bridge.CommunicationBridge;
+import org.wikipedia.dataclient.mwapi.MwQueryImageLicensePage;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
 import org.wikipedia.page.ImageLicense;
-import org.wikipedia.page.ImageLicenseFetchTask;
+import org.wikipedia.page.ImageLicenseFetchClient;
 import org.wikipedia.page.NoDimBottomSheetDialog;
 import org.wikipedia.page.Page;
 import org.wikipedia.page.PageFragment;
@@ -42,7 +44,9 @@
 import org.wikipedia.wiktionary.WiktionaryDialog;
 
 import java.util.Arrays;
-import java.util.Map;
+import java.util.List;
+
+import retrofit2.Call;
 
 import static org.wikipedia.analytics.ShareAFactFunnel.ShareMode;
 
@@ -126,29 +130,30 @@
         final String selectedText = StringUtil.sanitizeText(input.toString());
         final PageTitle title = fragment.getTitle();
 
-        (new 
ImageLicenseFetchTask(WikipediaApp.getInstance().getAPIForSite(title.getWikiSite()),
-                    title.getWikiSite(),
-                    new PageTitle("File:" + 
fragment.getPage().getPageProperties().getLeadImageName(), 
title.getWikiSite())) {
+        new ImageLicenseFetchClient().request(title.getWikiSite(),
+                new PageTitle("File:" + 
fragment.getPage().getPageProperties().getLeadImageName(), title.getWikiSite()),
+                new ImageLicenseFetchClient.Callback() {
+                    @Override public void success(@NonNull 
Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call,
+                                                  @NonNull 
List<MwQueryImageLicensePage> pages) {
+                        MwQueryImageLicensePage page = pages.get(0);
+                        ImageLicense leadImageLicense = new 
ImageLicense(page.imageLicense(),
+                                page.imageLicenseShortName(),
+                                page.imageLicenseUrl());
 
-            @Override
-            public void onFinish(@NonNull Map<PageTitle, ImageLicense> result) 
{
-                ImageLicense leadImageLicense = (ImageLicense) 
result.values().toArray()[0];
+                        final Bitmap snippetBitmap = 
SnippetImage.getSnippetImage(fragment.getContext(),
+                                fragment.getLeadImageBitmap(),
+                                title.getDisplayText(),
+                                fragment.getPage().isMainPage() ? "" : 
StringUtils.capitalize(title.getDescription()),
+                                selectedText,
+                                leadImageLicense);
 
-                final Bitmap snippetBitmap = 
SnippetImage.getSnippetImage(fragment.getContext(),
-                        fragment.getLeadImageBitmap(),
-                        title.getDisplayText(),
-                        fragment.getPage().isMainPage() ? "" : 
StringUtils.capitalize(title.getDescription()),
-                        selectedText,
-                        leadImageLicense);
+                        fragment.showBottomSheet(new PreviewDialog(fragment, 
snippetBitmap, title, selectedText, funnel));
+                    }
 
-                fragment.showBottomSheet(new PreviewDialog(fragment, 
snippetBitmap, title, selectedText, funnel));
-            }
-
-            @Override
-            public void onCatch(Throwable caught) {
-                L.d("Error fetching image license info for " + 
title.getDisplayText() + ": " + caught.getMessage(), caught);
-            }
-        }).execute();
+                    @Override public void failure(@NonNull 
Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call, @NonNull 
Throwable caught) {
+                        L.d("Error fetching image license info for " + 
title.getDisplayText() + ": " + caught.getMessage(), caught);
+                    }
+                });
     }
 
     /**
diff --git 
a/app/src/test/java/org/wikipedia/page/ImageLicenseFetchClientTest.java 
b/app/src/test/java/org/wikipedia/page/ImageLicenseFetchClientTest.java
new file mode 100644
index 0000000..2a6e5c0
--- /dev/null
+++ b/app/src/test/java/org/wikipedia/page/ImageLicenseFetchClientTest.java
@@ -0,0 +1,100 @@
+package org.wikipedia.page;
+
+import android.support.annotation.NonNull;
+
+import com.google.gson.stream.MalformedJsonException;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.mwapi.MwException;
+import org.wikipedia.dataclient.mwapi.MwQueryImageLicensePage;
+import org.wikipedia.dataclient.mwapi.MwQueryResponse;
+import org.wikipedia.dataclient.okhttp.HttpStatusException;
+import org.wikipedia.test.MockWebServerTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+import retrofit2.Call;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class ImageLicenseFetchClientTest extends MockWebServerTest{
+    private static final WikiSite WIKISITE_TEST = 
WikiSite.forLanguageCode("test");
+    private static final PageTitle PAGE_TITLE_MARK_SELBY =
+            new 
PageTitle("File:Mark_Selby_at_Snooker_German_Masters_(DerHexer)_2015-02-04_02.jpg",
+                          WIKISITE_TEST);
+
+    @NonNull private final ImageLicenseFetchClient subject = new 
ImageLicenseFetchClient();
+
+    @Test public void testRequestSuccess() throws Throwable {
+        enqueueFromFile("image_license.json");
+
+        ImageLicenseFetchClient.Callback cb = 
mock(ImageLicenseFetchClient.Callback.class);
+        Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call = 
request(cb);
+
+        server().takeRequest();
+        ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+        verify(cb).success(eq(call), captor.capture());
+
+        List<MwQueryImageLicensePage> result = captor.getValue();
+        MwQueryImageLicensePage mark = result.get(0);
+
+        assertThat(mark.title(), is("File:Mark Selby at Snooker German Masters 
(DerHexer) 2015-02-04 02.jpg"));
+        assertThat(mark.imageLicense(), is("cc-by-sa-4.0"));
+        assertThat(mark.imageLicenseShortName(), is("CC BY-SA 4.0"));
+        assertThat(mark.imageLicenseUrl(), 
is("http://creativecommons.org/licenses/by-sa/4.0";));
+    }
+
+    @Test public void testRequestResponseApiError() throws Throwable {
+        enqueueFromFile("api_error.json");
+
+        ImageLicenseFetchClient.Callback cb = 
mock(ImageLicenseFetchClient.Callback.class);
+        Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call = 
request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, MwException.class);
+    }
+
+    @Test public void testRequestResponseFailure() throws Throwable {
+        enqueue404();
+
+        ImageLicenseFetchClient.Callback cb = 
mock(ImageLicenseFetchClient.Callback.class);
+        Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call = 
request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, HttpStatusException.class);
+    }
+
+    @Test public void testRequestResponseMalformed() throws Throwable {
+        server().enqueue("'");
+
+        ImageLicenseFetchClient.Callback cb = 
mock(ImageLicenseFetchClient.Callback.class);
+        Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call = 
request(cb);
+
+        server().takeRequest();
+        assertCallbackFailure(call, cb, MalformedJsonException.class);
+    }
+
+    private void assertCallbackFailure(@NonNull 
Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> call,
+                                       @NonNull 
ImageLicenseFetchClient.Callback cb,
+                                       @NonNull Class<? extends Throwable> 
throwable) {
+        //noinspection unchecked
+        verify(cb, never()).success(any(Call.class), any(List.class));
+        verify(cb).failure(eq(call), isA(throwable));
+    }
+
+    private Call<MwQueryResponse<ImageLicenseFetchClient.QueryResult>> request(
+            @NonNull ImageLicenseFetchClient.Callback cb) {
+        return subject.request(WIKISITE_TEST, 
service(ImageLicenseFetchClient.Service.class),
+                Arrays.asList(PAGE_TITLE_MARK_SELBY), cb);
+    }
+}
diff --git a/app/src/test/res/raw/image_license.json 
b/app/src/test/res/raw/image_license.json
new file mode 100644
index 0000000..79141bd
--- /dev/null
+++ b/app/src/test/res/raw/image_license.json
@@ -0,0 +1,109 @@
+{
+  "batchcomplete": true,
+  "query": {
+    "normalized": [
+      {
+        "fromencoded": false,
+        "from": 
"File:Mark_Selby_at_Snooker_German_Masters_(DerHexer)_2015-02-04_02.jpg",
+        "to": "File:Mark Selby at Snooker German Masters (DerHexer) 2015-02-04 
02.jpg"
+      }
+    ],
+    "pages": [
+      {
+        "ns": 6,
+        "title": "File:Mark Selby at Snooker German Masters (DerHexer) 
2015-02-04 02.jpg",
+        "missing": true,
+        "known": true,
+        "imagerepository": "shared",
+        "imageinfo": [
+          {
+            "extmetadata": {
+              "DateTime": {
+                "value": "2015-02-11 20:27:39",
+                "source": "mediawiki-metadata",
+                "hidden": ""
+              },
+              "ObjectName": {
+                "value": "Mark Selby at Snooker German Masters (DerHexer) 
2015-02-04 02",
+                "source": "mediawiki-metadata",
+                "hidden": ""
+              },
+              "CommonsMetadataExtension": {
+                "value": 1.2,
+                "source": "extension",
+                "hidden": ""
+              },
+              "Categories": {
+                "value": "Files by DerHexer|German Masters 2015-Day 1, Session 
2|Images with extracted images|Mark Selby|Personality rights 
warning|Self-published work|Uploaded with VicuñaUploader",
+                "source": "commons-categories",
+                "hidden": ""
+              },
+              "Assessments": {
+                "value": "",
+                "source": "commons-categories",
+                "hidden": ""
+              },
+              "ImageDescription": {
+                "value": "Picture taken in Berlin during the <a 
href=\"https://en.wikipedia.org/wiki/2015_German_Masters\"; class=\"extiw\" 
title=\"en:2015 German Masters\">Snooker German Masters in 2015</a>. Mark 
Selby.",
+                "source": "commons-desc-page"
+              },
+              "DateTimeOriginal": {
+                "value": "2015-02-04 15:19",
+                "source": "commons-desc-page"
+              },
+              "Credit": {
+                "value": "<span class=\"int-own-work\" lang=\"en\">Own 
work</span>",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "Artist": {
+                "value": "<a 
href=\"//commons.wikimedia.org/wiki/User:DerHexer\" 
title=\"User:DerHexer\">DerHexer</a>",
+                "source": "commons-desc-page"
+              },
+              "LicenseShortName": {
+                "value": "CC BY-SA 4.0",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "UsageTerms": {
+                "value": "Creative Commons Attribution-Share Alike 4.0",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "AttributionRequired": {
+                "value": "true",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "Attribution": {
+                "value": "DerHexer, Wikimedia Commons, CC-by-sa 4.0",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "LicenseUrl": {
+                "value": "http://creativecommons.org/licenses/by-sa/4.0";,
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "Copyrighted": {
+                "value": "True",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "Restrictions": {
+                "value": "personality",
+                "source": "commons-desc-page",
+                "hidden": ""
+              },
+              "License": {
+                "value": "cc-by-sa-4.0",
+                "source": "commons-templates",
+                "hidden": ""
+              }
+            }
+          }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/351692
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib6ad70b969507aaad7356fce2e2e9bf27aaaeddc
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Jcasariego <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to