From 4c45c88ade80039c97cf1baf92c9f893413a8566 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 24 Sep 2013 14:36:54 -0700 Subject: [PATCH] Extract and show license key List of available licenses is pre-extracted from UploadWizard MediaWiki extension. This should eventually be switched to use some live query to the site configuration. The license display is more or less localizable, for known templates. Unrecognized templates specified as parameters to {{self}} will be recognized (unlocalized) but others may not be. Change-Id: I9df5fe807798a191a3bb0a45464760c75f19e366 --- commons/res/layout/detail_main_panel.xml | 8 ++ commons/res/values/strings.xml | 33 ++++++++ commons/res/xml/wikimedia_licenses.xml | 31 ++++++++ .../java/org/wikimedia/commons/License.java | 46 ++++++++++++ .../org/wikimedia/commons/LicenseList.java | 75 +++++++++++++++++++ .../wikimedia/commons/MediaDataExtractor.java | 48 +++++++++++- .../java/org/wikimedia/commons/Utils.java | 31 ++++++++ .../commons/media/MediaDetailFragment.java | 19 ++++- update-license-info/Makefile | 14 ++++ update-license-info/include-stubs.php | 68 +++++++++++++++++ update-license-info/licenses.php | 71 ++++++++++++++++++ 11 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 commons/res/xml/wikimedia_licenses.xml create mode 100644 commons/src/main/java/org/wikimedia/commons/License.java create mode 100644 commons/src/main/java/org/wikimedia/commons/LicenseList.java create mode 100644 update-license-info/Makefile create mode 100644 update-license-info/include-stubs.php create mode 100644 update-license-info/licenses.php diff --git a/commons/res/layout/detail_main_panel.xml b/commons/res/layout/detail_main_panel.xml index dd6f9ae15..2a321f856 100644 --- a/commons/res/layout/detail_main_panel.xml +++ b/commons/res/layout/detail_main_panel.xml @@ -31,6 +31,14 @@ android:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." android:id="@+id/mediaDetailDesc" android:layout_gravity="left|start"/> + Download License + CC\u00A0Attribution-ShareAlike\u00A03.0 CC\u00A0Attribution\u00A03.0 CC0 + + + CC BY-SA 3.0 + CC BY-SA 3.0 (Austria) + CC BY-SA 3.0 (Germany) + CC BY-SA 3.0 (Estonia) + CC BY-SA 3.0 (Spain) + CC BY-SA 3.0 (Croatia) + CC BY-SA 3.0 (Luxembourg) + CC BY-SA 3.0 (Netherlands) + CC BY-SA 3.0 (Norway) + CC BY-SA 3.0 (Poland) + CC BY-SA 3.0 (Romania) + CC BY 3.0 + CC Zero + own-pd + CC BY-SA 2.5 + CC BY 2.5 + CC BY-SA 2.0 + CC BY-SA 2.0 + CC BY 2.0 + Free Art License + Public domain (author died over 100 years ago) + Public domain (copyright expired) + Public domain (art) + Public domain (US) + Public domain (US government) + Public domain (NASA) + Public domain (ineligible for copyright) + Attribution + GNU Free Documentation License + Contribute your images. Help Wikipedia articles come to life! Images on Wikipedia come from Wikimedia Commons. Your images help educate people around the world. diff --git a/commons/res/xml/wikimedia_licenses.xml b/commons/res/xml/wikimedia_licenses.xml new file mode 100644 index 000000000..e23bcbfc5 --- /dev/null +++ b/commons/res/xml/wikimedia_licenses.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/commons/src/main/java/org/wikimedia/commons/License.java b/commons/src/main/java/org/wikimedia/commons/License.java new file mode 100644 index 000000000..caa9bc4d6 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/License.java @@ -0,0 +1,46 @@ +package org.wikimedia.commons; + +public class License { + String key; + String template; + String url; + String name; + + public License(String key, String template, String url, String name) { + if (key == null) { + throw new RuntimeException("License.key must not be null"); + } + if (template == null) { + throw new RuntimeException("License.template must not be null"); + } + this.key = key; + this.template = template; + this.url = url; + this.name = name; + } + + public String getKey() { + return key; + } + + public String getTemplate() { + return template; + } + + public String getName() { + if (name == null) { + // hack + return getKey(); + } else { + return name; + } + } + + public String getUrl(String language) { + if (url == null) { + return null; + } else { + return url.replace("$lang", language); + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/LicenseList.java b/commons/src/main/java/org/wikimedia/commons/LicenseList.java new file mode 100644 index 000000000..b9da572b2 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/LicenseList.java @@ -0,0 +1,75 @@ +package org.wikimedia.commons; + +import android.app.Activity; +import android.content.res.Resources; +import android.util.Log; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class LicenseList { + Map licenses = new HashMap(); + Resources res; + + private static String XMLNS_LICENSE = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses"; + + public LicenseList(Activity activity) { + res = activity.getResources(); + XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses); + while (Utils.xmlFastForward(parser, XMLNS_LICENSE, "license")) { + String id = parser.getAttributeValue(null, "id"); + String template = parser.getAttributeValue(null, "template"); + String url = parser.getAttributeValue(null, "url"); + String name = nameForTemplate(template); + License license = new License(id, template, url, name); + licenses.put(id, license); + } + + } + + public Set keySet() { + return licenses.keySet(); + } + + public Collection values() { + return licenses.values(); + } + + public License get(String key) { + return licenses.get(key); + } + + public License licenseForTemplate(String template) { + String ucTemplate = Utils.capitalize(template); + for (License license : values()) { + if (ucTemplate.equals(Utils.capitalize(license.getTemplate()))) { + return license; + } + } + return null; + } + + public String nameIdForTemplate(String template) { + // hack :D + // cc-by-sa-3.0 -> cc_by_sa_3_0 + return "license_name_" + template.toLowerCase().replace("-", "_").replace(".", "_"); + } + + private int stringIdByName(String stringId) { + return res.getIdentifier("org.wikimedia.commons:string/" + stringId, null, null); + } + + public String nameForTemplate(String template) { + Log.d("Commons", "LicenseList.nameForTemplate: template: " + template); + String stringId = nameIdForTemplate(template); + Log.d("Commons", "LicenseList.nameForTemplate: stringId: " + stringId); + int nameId = stringIdByName(stringId); + Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId); + String name = res.getString(nameId); + Log.d("Commons", "LicenseList.nameForTemplate: name: " + name); + return name; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java index 8f0c049dd..357025615 100644 --- a/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java +++ b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java @@ -33,16 +33,19 @@ public class MediaDataExtractor { private Map descriptions; private String author; private Date date; + private String license; + private LicenseList licenseList; /** * @param filename of the target media object, should include 'File:' prefix */ - public MediaDataExtractor(String filename) { + public MediaDataExtractor(String filename, LicenseList licenseList) { this.filename = filename; categories = new ArrayList(); descriptions = new HashMap(); fetched = false; processed = false; + this.licenseList = licenseList; } /** @@ -114,7 +117,41 @@ public class MediaDataExtractor { descriptions = getMultilingualText(descriptionNode); Node authorNode = findTemplateParameter(templateNode, "author"); - author = Utils.getStringFromDOM(authorNode); + author = getFlatText(authorNode); + } + + /* + Pull up the license data list... + look for the templates in two ways: + * look for 'self' template and check its first parameter + * if none, look for any of the known templates + */ + Log.d("Commons", "MediaDataExtractor searching for license"); + Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self"); + if (selfLicenseNode != null) { + Node firstNode = findTemplateParameter(selfLicenseNode, 1); + String licenseTemplate = getFlatText(firstNode); + License license = licenseList.licenseForTemplate(licenseTemplate); + if (license == null) { + Log.d("Commons", "MediaDataExtractor found no matching license for self parameter: " + licenseTemplate + "; faking it"); + this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system. + } else { + // fixme: record the self-ness in here too... sigh + // all this needs better server-side metadata + this.license = license.getKey(); + Log.d("Commons", "MediaDataExtractor found self-license " + this.license); + } + } else { + for (License license : licenseList.values()) { + String templateName = license.getTemplate(); + Node template = findTemplate(doc.getDocumentElement(), templateName); + if (template != null) { + // Found! + this.license = license.getKey(); + Log.d("Commons", "MediaDataExtractor found non-self license " + this.license); + break; + } + } } } @@ -201,6 +238,10 @@ public class MediaDataExtractor { throw new IOException("No matching template parameter node found."); } + private String getFlatText(Node parentNode) throws IOException { + return parentNode.getTextContent(); + } + // Extract a dictionary of multilingual texts from a subset of the parse tree. // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}. // Text outside those wrappers is stuffed into a 'default' faux language key if present. @@ -246,6 +287,9 @@ public class MediaDataExtractor { media.setCategories(categories); media.setDescriptions(descriptions); + if (license != null) { + media.setLicense(license); + } // add author, date, etc fields } diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java index 49868d57d..54e5f3013 100644 --- a/commons/src/main/java/org/wikimedia/commons/Utils.java +++ b/commons/src/main/java/org/wikimedia/commons/Utils.java @@ -10,6 +10,8 @@ import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.net.URLCodec; import org.w3c.dom.Node; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import javax.xml.transform.*; import java.io.*; @@ -194,4 +196,33 @@ public class Utils { String uriStr = CommonsApplication.HOME_URL + urlEncode(underscored); return Uri.parse(uriStr); } + + /** + * Fast-forward an XmlPullParser to the next instance of the given element + * in the input stream (namespaced). + * + * @param parser + * @param namespace + * @param element + * @return true on match, false on failure + */ + public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) { + try { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.getEventType() == XmlPullParser.START_TAG && + parser.getNamespace().equals(namespace) && + parser.getName().equals(element)) { + // We found it! + return true; + } + } + return false; + } catch (XmlPullParserException e) { + e.printStackTrace(); + return false; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } } diff --git a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java index e06d441b8..a09638bd3 100644 --- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java @@ -59,6 +59,7 @@ public class MediaDetailFragment extends SherlockFragment { private TextView title; private TextView desc; + private TextView license; private ListView listView; private ArrayList categoryNames; private boolean categoriesLoaded = false; @@ -133,6 +134,7 @@ public class MediaDetailFragment extends SherlockFragment { spacer = (MediaDetailSpacer) detailView.findViewById(R.id.mediaDetailSpacer); title = (TextView) detailView.findViewById(R.id.mediaDetailTitle); desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc); + license = (TextView) detailView.findViewById(R.id.mediaDetailLicense); // Enable or disable editing on the title /* @@ -160,10 +162,12 @@ public class MediaDetailFragment extends SherlockFragment { // FIXME: cache this data detailFetchTask = new AsyncTask() { private MediaDataExtractor extractor; + private LicenseList licenseList; @Override protected void onPreExecute() { - extractor = new MediaDataExtractor(media.getFilename()); + licenseList = new LicenseList(getActivity()); + extractor = new MediaDataExtractor(media.getFilename(), licenseList); } @Override @@ -187,6 +191,16 @@ public class MediaDetailFragment extends SherlockFragment { // Fill some fields desc.setText(media.getDescription("en")); + String licenseKey = media.getLicense(); + License licenseObj = licenseList.get(licenseKey); + if (licenseObj == null) { + license.setText(licenseKey); + } else { + license.setText(licenseObj.getName()); + } + Log.d("Commons", "Media license is: " + media.getLicense()); + + categoryNames.removeAll(categoryNames); categoryNames.addAll(media.getCategories()); @@ -231,7 +245,8 @@ public class MediaDetailFragment extends SherlockFragment { } title.setText(media.getDisplayTitle()); - desc.setText(""); + desc.setText(""); // fill in from network... + license.setText(""); // fill in from network... /* title.addTextChangedListener(new TextWatcher() { diff --git a/update-license-info/Makefile b/update-license-info/Makefile new file mode 100644 index 000000000..a234667a5 --- /dev/null +++ b/update-license-info/Makefile @@ -0,0 +1,14 @@ +.FAKE : build update clean install + +build : ../commons/res/xml/wikimedia_licenses.xml + +../commons/res/xml/wikimedia_licenses.xml : licenses.php mediawiki-extensions-UploadWizard + php licenses.php > ../commons/res/xml/wikimedia_licenses.xml + +mediawiki-extensions-UploadWizard : update + +update : + if [ -d mediawiki-extensions-UploadWizard ]; then (cd mediawiki-extensions-UploadWizard && git pull origin master); else git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/UploadWizard mediawiki-extensions-UploadWizard; fi + +clean : + rm -rf mediawiki-extensions-UploadWizard diff --git a/update-license-info/include-stubs.php b/update-license-info/include-stubs.php new file mode 100644 index 000000000..c0a01a0d6 --- /dev/null +++ b/update-license-info/include-stubs.php @@ -0,0 +1,68 @@ + 'English' ); + } +} +$wgMemc = new FakeMemc(); + +class FakeMessage { + function plain() { + return 'stub-message-plain'; + } + function parse() { + return 'stub-message-parsed'; + } +} + +function wfMessage() { + return new FakeMessage(); +} + +/** + * Converts shorthand byte notation to integer form + * + * @param $string String + * @return Integer + */ +function wfShorthandToInteger( $string = '' ) { + $string = trim( $string ); + if ( $string === '' ) { + return -1; + } + $last = $string[strlen( $string ) - 1]; + $val = intval( $string ); + switch ( $last ) { + case 'g': + case 'G': + $val *= 1024; + // break intentionally missing + case 'm': + case 'M': + $val *= 1024; + // break intentionally missing + case 'k': + case 'K': + $val *= 1024; + } + + return $val; +} + +$wgAPIModules = array(); diff --git a/update-license-info/licenses.php b/update-license-info/licenses.php new file mode 100644 index 000000000..badda1a08 --- /dev/null +++ b/update-license-info/licenses.php @@ -0,0 +1,71 @@ + +// 2013-09-30 + +require 'include-stubs.php'; +$config = require "mediawiki-extensions-UploadWizard/UploadWizard.config.php"; +require "mediawiki-extensions-UploadWizard/UploadWizard.i18n.php"; +$licenseList = array(); + +foreach ( $config['licenses'] as $key => $license ) { + // Determine template -> license mappings + if ( isset( $license['templates'] ) ) { + $templates = $license['templates']; + } else { + $templates = array( $key ); + } + + if ( count( $templates ) < 1 ) { + throw new Exception("No templates for $key, this is wrong."); + } + if ( count( $templates ) > 1 ) { + //echo "Skipping multi-template license: $key\n"; + continue; + } + $template = $templates[0]; + if ( preg_match( '/^subst:/i', $template ) ) { + //echo "Skipping subst license: $key\n"; + continue; + } + + $msg = $messages['en'][$license['msg']]; + + $licenseInfo = array( + 'desc' => $msg, + 'template' => $template + ); + if ( isset( $license['url'] ) ) { + $url = $license['url']; + if ( substr( $url, 0, 2 ) == '//' ) { + $url = 'https:' . $url; + } + if ( isset( $license['languageCodePrefix'] ) ) { + $url .= $license['languageCodePrefix'] . '$lang'; + } + $licenseInfo['url'] = $url; + } + $licenseList[$key] = $licenseInfo; +} + +//var_dump( $licenseList ); + +echo "\n"; +echo "\n"; +foreach( $licenseList as $key => $licenseInfo ) { + $encId = htmlspecialchars( $key ); + echo " \n"; + +} +echo "\n"; + \ No newline at end of file