package org.wikipedia.dataclient; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.text.TextUtils; import com.google.gson.annotations.SerializedName; import org.wikipedia.language.AppLanguageLookUpTable; import org.wikipedia.page.PageTitle; import org.wikipedia.settings.Prefs; import org.wikipedia.util.UriUtil; import java.util.Locale; /** * The base URL and Wikipedia language code for a MediaWiki site. Examples: * * <ul> * <lh>Name: scheme / authority / language code</lh> * <li>English Wikipedia: HTTPS / en.wikipedia.org / en</li> * <li>Chinese Wikipedia: HTTPS / zh.wikipedia.org / zh-hans or zh-hant</li> * <li>Meta-Wiki: HTTPS / meta.wikimedia.org / (none)</li> * <li>Test Wikipedia: HTTPS / test.wikipedia.org / test</li> * <li>Võro Wikipedia: HTTPS / fiu-vro.wikipedia.org / fiu-vro</li> * <li>Simple English Wikipedia: HTTPS / simple.wikipedia.org / simple</li> * <li>Simple English Wikipedia (beta cluster mirror): HTTP / simple.wikipedia.beta.wmflabs.org / simple</li> * <li>Development: HTTP / 192.168.1.11:8080 / (none)</li> * </ul> * * <strong>As shown above, the language code or mapping is part of the authority:</strong> * <ul> * <lh>Validity: authority / language code</lh> * <li>Correct: "test.wikipedia.org" / "test"</li> * <li>Correct: "wikipedia.org", ""</li> * <li>Correct: "no.wikipedia.org", "nb"</li> * <li>Incorrect: "wikipedia.org", "test"</li> * </ul> */ public class WikiSite implements Parcelable { public static final Parcelable.Creator<WikiSite> CREATOR = new Parcelable.Creator<WikiSite>() { @Override public WikiSite createFromParcel(Parcel in) { return new WikiSite(in); } @Override public WikiSite[] newArray(int size) { return new WikiSite[size]; } }; // todo: remove @SerializedName. this is now in the TypeAdapter and a "uri" case may be added @SerializedName("domain") @NonNull private final Uri uri; @NonNull private final String languageCode; // possibly empty /** * @return True if the authority is supported by the app. */ public static boolean supportedAuthority(@NonNull String authority) { return authority.endsWith(Prefs.getMediaWikiBaseUri().getAuthority()); } public static WikiSite forLanguageCode(@NonNull String languageCode) { Uri uri = Prefs.getMediaWikiBaseUri(); boolean secureSchema = uri.getScheme().equals("https"); return new WikiSite(secureSchema, (languageCode.isEmpty() ? "" : (languageCodeToSubdomain(languageCode) + ".")) + uri.getAuthority(), languageCode); } /** This method cannot resolve multi-dialect wikis like Simplified and Traditional Chinese as the variant is unavailable. */ public WikiSite(@NonNull String authority) { this(authority, authorityToLanguageCode(authority)); } public WikiSite(@NonNull String authority, @NonNull String languageCode) { this(true, authority, languageCode); } public WikiSite(boolean secureScheme, @NonNull String authority, @NonNull String languageCode) { this(new Uri.Builder() .scheme(secureScheme ? "https" : "http") .encodedAuthority(authority) .build(), languageCode); } /** This method cannot resolve multi-dialect wikis like Simplified and Traditional Chinese as the variant is unavailable. */ public WikiSite(@NonNull Uri uri) { this(uri, authorityToLanguageCode(uri.getAuthority())); } public WikiSite(@NonNull Uri uri, @NonNull String languageCode) { // todo: uncomment // if (!supportedAuthority(uri.getAuthority())) { // throw new IllegalArgumentException("Unsupported authority=" + uri.getAuthority()); // } this.uri = uri; this.languageCode = languageCode; } /** * @return True if the URL scheme is secure. Examples: * * <ul> * <lh>Scheme: return value</lh> * <li>HTTPS: true</li> * <li>HTTP: false</li> * </ul> */ public boolean secureScheme() { return uri.getScheme().equals("https"); } @NonNull public String scheme() { return uri.getScheme(); } /** * @return The complete wiki authority including language subdomain but not including scheme, * authentication, port, nor trailing slash. * * @see <a href='https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax'>URL syntax</a> */ @NonNull public String authority() { return uri.getAuthority(); } @NonNull public String mobileAuthority() { return authorityToMobile(authority()); } @NonNull public String host() { return uri.getHost(); } /** * Like {@link #host} but with a "m." between the language subdomain and the rest of the host. * Examples: * * <ul> * <li>English Wikipedia: en.m.wikipedia.org</li> * <li>Chinese Wikipedia: zh.m.wikipedia.org</li> * <li>Meta-Wiki: meta.m.wikimedia.org</li> * <li>Test Wikipedia: test.m.wikipedia.org</li> * <li>Võro Wikipedia: fiu-vro.m.wikipedia.org</li> * <li>Simple English Wikipedia: simple.m.wikipedia.org</li> * <li>Simple English Wikipedia (beta cluster mirror): simple.m.wikipedia.beta.wmflabs.org</li> * <li>Development: m.192.168.1.11</li> * </ul> */ @NonNull public String mobileHost() { return authorityToMobile(host()); } /** @return the port if specified or -1 if invalid or not present */ public int port() { return uri.getPort(); } /** * @return A path without an authority for the segment including a leading "/". */ @NonNull public String path(@NonNull String segment) { return "/w/" + segment; } @NonNull public Uri uri() { return uri; } /** * @return The canonical URL. e.g., https://en.wikipedia.org. */ @NonNull public String url() { return uri.toString(); } /** * @return The canonical URL for segment. e.g., https://en.wikipedia.org/w/foo. */ @NonNull public String url(@NonNull String segment) { return url() + path(segment); } /** * @return The wiki language code which may differ from the language subdomain. Empty if * language code is unknown. Ex: "en", "zh-hans", "" * * @see AppLanguageLookUpTable */ @NonNull public String languageCode() { return languageCode; } // TODO: this method doesn't have much to do with WikiSite. Move to PageTitle? /** * Create a PageTitle object from an internal link string. * * @param internalLink Internal link target text (eg. /wiki/Target). * Should be URL decoded before passing in * @return A {@link PageTitle} object representing the internalLink passed in. */ public PageTitle titleForInternalLink(String internalLink) { // FIXME: Handle language variant links properly // Strip the /wiki/ from the href return new PageTitle(UriUtil.removeInternalLinkPrefix(internalLink), this); } // TODO: this method doesn't have much to do with WikiSite. Move to PageTitle? /** * Create a PageTitle object from a Uri, taking into account any fragment (section title) in the link. * @param uri Uri object to be turned into a PageTitle. * @return {@link PageTitle} object that corresponds to the given Uri. */ public PageTitle titleForUri(Uri uri) { String path = uri.getPath(); if (!TextUtils.isEmpty(uri.getFragment())) { path += "#" + uri.getFragment(); } return titleForInternalLink(path); } @NonNull public String dbName() { return languageCodeToSubdomain(languageCode) + "wiki"; } // Auto-generated @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WikiSite wiki = (WikiSite) o; if (!uri.equals(wiki.uri)) { return false; } return languageCode.equals(wiki.languageCode); } // Auto-generated @Override public int hashCode() { int result = uri.hashCode(); result = 31 * result + languageCode.hashCode(); return result; } // Auto-generated @Override public String toString() { return "WikiSite{" + "uri=" + uri + ", languageCode='" + languageCode + '\'' + '}'; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(uri, 0); dest.writeString(languageCode); } protected WikiSite(@NonNull Parcel in) { this(in.<Uri>readParcelable(Uri.class.getClassLoader()), in.readString()); } @NonNull private static String languageCodeToSubdomain(@NonNull String languageCode) { switch (languageCode) { case AppLanguageLookUpTable.SIMPLIFIED_CHINESE_LANGUAGE_CODE: case AppLanguageLookUpTable.TRADITIONAL_CHINESE_LANGUAGE_CODE: return Locale.CHINA.getLanguage(); case AppLanguageLookUpTable.NORWEGIAN_BOKMAL_LANGUAGE_CODE: return AppLanguageLookUpTable.NORWEGIAN_LEGACY_LANGUAGE_CODE; // T114042 default: return languageCode; } } @NonNull private static String authorityToLanguageCode(@NonNull String authority) { String[] parts = authority.split("\\."); final int minLengthForSubdomain = 3; if (parts.length < minLengthForSubdomain || parts.length == minLengthForSubdomain && parts[0].equals("m")) { // "" // wikipedia.org // m.wikipedia.org return ""; } return parts[0]; } /** @param authority Host and optional port. */ @NonNull private String authorityToMobile(@NonNull String authority) { if (authority.startsWith("m.") || authority.contains(".m.")) { return authority; } String subdomain = languageCodeToSubdomain(languageCode); return authority.replaceFirst("^" + subdomain + "\\.?", "$0m."); } }