Android Open Source - norilib Danbooru Legacy






From Project

Back to project page norilib.

License

The source code is released under:

Copyright (c) 2013-2014, vomitcuddle <shinku@dollbooru.org> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the a...

If you think the Android project norilib listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * This file is part of nori./*  ww w  . ja  v  a 2  s  .  co m*/
 * Copyright (c) 2014 vomitcuddle <shinku@dollbooru.org>
 * License: ISC
 */

package com.cuddlesoft.norilib.clients;

import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;

import com.cuddlesoft.norilib.Image;
import com.cuddlesoft.norilib.SearchResult;
import com.cuddlesoft.norilib.Tag;
import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.IOException;
import java.io.StringReader;
import java.net.Proxy;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * Client for the Danbooru 1.x API.
 */
public class DanbooruLegacy implements SearchClient {
  /**
   * Number of images per search results page.
   * Best to use a large value to minimize number of unique HTTP requests.
   */
  private static final int DEFAULT_LIMIT = 100;
  /** OkHTTP Client. */
  private final OkHttpClient okHttpClient = new OkHttpClient();
  /** Human-readable service name. */
  protected final String name;
  /** URL to the HTTP API Endpoint - the server implementing the API. */
  protected final String apiEndpoint;
  /** Username used for authentication. (optional) */
  protected final String username;
  /** Password used for authentication. (optional) */
  protected final String password;

  /**
   * Create a new Danbooru 1.x client without authentication.
   *
   * @param endpoint URL to the HTTP API Endpoint - the server implementing the API.
   */
  public DanbooruLegacy(String name, String endpoint) {
    this.name = name;
    this.apiEndpoint = endpoint;
    this.username = null;
    this.password = null;
  }

  /**
   * Create a new Danbooru 1.x client with authentication.
   *
   * @param endpoint URL to the HTTP API Endpoint - the server implementing the API.
   * @param username Username used for authentication.
   * @param password Password used for authentication.
   */
  public DanbooruLegacy(String name, String endpoint, String username, String password) {
    this.name = name;
    this.apiEndpoint = endpoint;
    this.username = username;
    this.password = password;

    // Enable HTTP basic authentication.
    okHttpClient.setAuthenticator(new Authenticator() {
      @Override
      public Request authenticate(Proxy proxy, Response response) throws IOException {
        final String credential = Credentials.basic(DanbooruLegacy.this.username, DanbooruLegacy.this.password);
        return response.request().newBuilder()
            .header("Authorization", credential)
            .build();
      }

      @Override
      public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        return null;
      }
    });
  }

  @Override
  public SearchResult search(String tags) throws IOException {
    // Return results for page 0.
    return search(tags, 0);
  }

  @Override
  public SearchResult search(String tags, int pid) throws IOException {
    // Create HTTP request.
    final Request request = new Request.Builder()
        .url(createSearchURL(tags, pid, DEFAULT_LIMIT))
        .build();
    // Get HTTP response.
    final Response response = okHttpClient.newCall(request).execute();
    final String body = response.body().string();

    // Return parsed SearchResult.
    return parseXMLResponse(body, tags, pid);
  }

  @Override
  public void search(String tags, SearchCallback callback) {
    // Return results for page 0.
    search(tags, 0, callback);
  }

  @Override
  public void search(final String tags, final int pid, final SearchCallback callback) {
    // Fetch results on a background thread.
    new AsyncTask<Void, Void, SearchResult>() {
      /** Error returned when attempting to fetch the SearchResult. */
      private IOException error;

      @Override
      protected SearchResult doInBackground(Void... voids) {
        try {
          return DanbooruLegacy.this.search(tags, pid);
        } catch (IOException e) {
          // Hold on to the error for now and handle it on the main UI thread in #postExecute().
          error = e;
        }
        return null;
      }

      @Override
      protected void onPostExecute(SearchResult searchResult) {
        // Pass the result or error to the SearchCallback.
        if (error != null || searchResult == null) {
          callback.onFailure(error);
        } else {
          callback.onSuccess(searchResult);
        }
      }
    }.execute();
  }

  /**
   * Parse an XML response returned by the API.
   *
   * @param body   HTTP Response body.
   * @param tags   Tags used to retrieve the response.
   * @param offset Current paging offset.
   * @return A {@link com.cuddlesoft.norilib.SearchResult} parsed from given XML.
   */
  protected SearchResult parseXMLResponse(String body, String tags, int offset) throws IOException {
    // Create variables to hold the values as XML is being parsed.
    final List<Image> imageList = new ArrayList<>(DEFAULT_LIMIT);

    try {
      // Create an XML parser factory and disable namespace awareness for security reasons.
      // See: (http://lists.w3.org/Archives/Public/public-xmlsec/2009Dec/att-0000/sws5-jensen.pdf).
      final XmlPullParserFactory xmlParserFactory = XmlPullParserFactory.newInstance();
      xmlParserFactory.setNamespaceAware(false);

      // Create a new XML parser and feed HTTP response data into it.
      final XmlPullParser xpp = xmlParserFactory.newPullParser();
      xpp.setInput(new StringReader(body));

      // Iterate over each XML element and handle pull parser "events".
      while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
        if (xpp.getEventType() == XmlPullParser.START_TAG) {
          if (xpp.getName().equals("post")) {
            // <post> tags contain metadata for each image.
            final Image image = new Image();

            // Extract image metadata from XML attributes.
            for (int i = 0; i < xpp.getAttributeCount(); i++) {
              // Get name and value of current XML attribute.
              final String name = xpp.getAttributeName(i);
              final String value = xpp.getAttributeValue(i);

              // Set the appropriate value for each tag name.
              if (name.equals("file_url")) {
                image.fileUrl = normalizeUrl(value);
              } else if (name.equals("width")) {
                image.width = Integer.parseInt(value);
              } else if (name.equals("height")) {
                image.height = Integer.parseInt(value);
              } else if (name.equals("preview_url")) {
                image.previewUrl = normalizeUrl(value);
              } else if (name.equals("preview_width")) {
                image.previewWidth = Integer.valueOf(value);
              } else if (name.equals("preview_height")) {
                image.previewHeight = Integer.valueOf(value);
              } else if (name.equals("sample_url")) {
                image.sampleUrl = normalizeUrl(value);
              } else if (name.equals("sample_width")) {
                image.sampleWidth = Integer.valueOf(value);
              } else if (name.equals("sample_height")) {
                image.sampleHeight = Integer.valueOf(value);
              } else if (name.equals("tags")) {
                image.tags = Tag.arrayFromString(value, Tag.Type.GENERAL);
              } else if (name.equals("id")) {
                image.id = value;
              } else if (name.equals("parent_id")) {
                image.parentId = value;
              } else if (name.equals("rating")) {
                image.obscenityRating = Image.ObscenityRating.fromString(value);
              } else if (name.equals("score")) {
                image.score = Integer.parseInt(value);
              } else if (name.equals("md5")) {
                image.md5 = value;
              } else if (name.equals("created_at") || name.equals("date")) {
                image.createdAt = dateFromString(value);
              }
            }

            // Append values not returned by the API.
            image.webUrl = webUrlFromId(image.id);
            image.pixivId = Image.getPixivIdFromUrl(image.source);
            // Use original file if low-resolution sample does not exist.
            if (image.sampleUrl == null) {
              image.sampleUrl = image.fileUrl;
              image.sampleWidth = image.width;
              image.sampleHeight = image.height;
            }
            // Add Image to search result.
            imageList.add(image);
          }
        }
        // Get next XMLPullParser event.
        xpp.next();
      }
    } catch (XmlPullParserException | ParseException e) {
      // Convert into IOException.
      // Needed for consistent method signatures in the SearchClient interface for different APIs.
      // (Throwing an XmlPullParserException would be fine, until dealing with an API using JSON, etc.)
      throw new IOException(e);
    }
    // Create and return a SearchResult.
    return new SearchResult(imageList.toArray(new Image[imageList.size()]), Tag.arrayFromString(tags), offset);
  }

  @Override
  public String getDefaultQuery() {
    // Show all safe-for-work images by default.
    return "";
  }

  @Override
  public Settings getSettings() {
    return new Settings(Settings.APIType.DANBOORU_LEGACY, name, apiEndpoint, username, password);
  }

  /**
   * Convert a relative image URL to an absolute URL.
   *
   * @param url URL to convert.
   * @return Absolute URL.
   */
  protected String normalizeUrl(String url) {
    // Return empty string for empty URLs.
    if (url == null || url.isEmpty()) {
      return "";
    }
    // Prepend API endpoint path if url is relative.
    final Uri uri = Uri.parse(url);
    if (uri.isRelative()) {
      return apiEndpoint + url;
    }
    // URL already absolute.
    return url;
  }

  /**
   * Get a URL viewable in the system web browser for given Image ID.
   *
   * @param id {@link com.cuddlesoft.norilib.Image} ID.
   * @return URL for viewing the image in the browser.
   */
  protected String webUrlFromId(String id) {
    return apiEndpoint + "/post/show/" + id;
  }

  /**
   * Create a {@link java.util.Date} object from String date representation used by this API.
   *
   * @param date Date string.
   * @return Date converted from given String.
   */
  protected Date dateFromString(String date) throws ParseException {
    // Parser for the date format used by upstream Danbooru 1.x.
    final DateFormat DATE_FORMAT_DEFAULT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);

    if (TextUtils.isDigitsOnly(date)) {
      // Moebooru-based boards (Danbooru 1.x fork) use Unix timestamps.
      return new Date(Integer.valueOf(date));
    } else {
      return DATE_FORMAT_DEFAULT.parse(date);
    }
  }

  /**
   * Generate request URL to the search API endpoint.
   *
   * @param tags  Space-separated tags.
   * @param pid   Page number (0-indexed).
   * @param limit Images to fetch per page.
   * @return URL to search results API.
   */
  protected String createSearchURL(String tags, int pid, int limit) {
    // Page numbers are 1-indexed for this API.
    final int page = pid + 1;

    return String.format(Locale.US, apiEndpoint + "/post/index.xml?tags=%s&limit=%d&page=%d", Uri.encode(tags), limit, page);
  }

  @Override
  public AuthenticationType requiresAuthentication() {
    return AuthenticationType.OPTIONAL;
  }
}




Java Source Code List

com.cuddlesoft.norilib.Image.java
com.cuddlesoft.norilib.SearchResult.java
com.cuddlesoft.norilib.Tag.java
com.cuddlesoft.norilib.clients.DanbooruLegacy.java
com.cuddlesoft.norilib.clients.Danbooru.java
com.cuddlesoft.norilib.clients.Gelbooru.java
com.cuddlesoft.norilib.clients.SearchClient.java
com.cuddlesoft.norilib.clients.Shimmie.java
com.cuddlesoft.norilib.service.ServiceTypeDetectionService.java