<commit_before>// Copyright 2016 InnerFunction Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
package com.innerfunction.http;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.text.TextUtils;

import com.innerfunction.q.Q;
import com.innerfunction.util.Files;
import com.innerfunction.util.RunQueue;

import static com.innerfunction.util.DataLiterals.*;

import java.io.File;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * An HTTP client.
 * Provides asynchronous methods for fetching files and data from an HTTP server.
 *
 * TODO Setup caching behaviour, as per https://developer.android.com/reference/android/net/http/HttpResponseCache.html
 * TODO Need to figure out how this will work with the filesystem content cache.
 *
 * Attached by juliangoacher on 08/07/16.
 */
public class Client {

    static final String Tag = "HTTPClient";

    // Setup cookie management.
    static final CookieManager CookieManager = new CookieManager();
    static {
        CookieHandler.setDefault( CookieManager );
    }

    /** A delegate object used to perform HTTP authentication, when required. */
    private AuthenticationDelegate authenticationDelegate;
    /** An object for checking network connectivity. */
    private ConnectivityManager connectivityManager;
    /** The app's cache location. Used for temporary download files. */
    private File cacheDir;

    public Client(Context context) {
        this.connectivityManager = (ConnectivityManager)context.getSystemService( Context.CONNECTIVITY_SERVICE );
        this.cacheDir = context.getCacheDir();
    }

    public void setAuthenticationDelegate(AuthenticationDelegate delegate) {
        this.authenticationDelegate = delegate;
    }

    /** Get an HTTP URL. */
    public Q.Promise<Response> get(String url) throws MalformedURLException {
        return get( url, null );
    }

    /** Get an HTTP URL, using the specified values as query parameters. */
    public Q.Promise<Response> get(String url, Map<String,Object> params) throws MalformedURLException {
        if( params != null ) {
            String query = makeQueryString( params );
            // Does the URL already have a query string?
            if( url.indexOf('?') > -1 ) {
                // Append additional parameters to end of query string.
                url = String.format("%s&%s", url, query );
            }
            else {
                // Append parameters as new query string.
                url = String.format("%s?%s", url, query );
            }
        }
        Request request = new DataRequest( url, "GET");
        return send( request );
    }

    /**
     * Get a file from an HTTP URL.
     * @param url       The URL to get.
     * @param dataFile  A file to write the URL's contents to.
     */
    public Q.Promise<Response> getFile(String url, File dataFile) throws MalformedURLException {
        Request request = new FileRequest( url, "GET", dataFile );
        return send( request );
    }

    /**
     * Get a file from an HTTP URL.
     * Writes the response to a temporary file. The file can be retrieved through a call to
     * Response.getDataFile(). The file should be moved to suitable location if it needs to
     * be kept. (Note that the file object returned has a modified renameTo() method that can
     * properly handle moving files between separate disk partitions).
     * @param url       The URL to get.
     */
    public Q.Promise<Response> getFile(String url) throws MalformedURLException {
        String filename = String.format("%s_%d.tmp", getClass().getName(), System.currentTimeMillis() );
        File dataFile = new File( cacheDir, filename ) {
            @Override
            public void finalize() throws Throwable {
                try {
                    // Delete temporary file as soon as this object is GC'd.
                    delete();
                }
                catch(SecurityException e) {}
                super.finalize();
            }
        };
        Request request = new FileRequest( url, "GET", dataFile );
        return send( request );
    }

    /**
     * Post to an HTTP URL.
     * Performs an HTTP form post.
     * @param url       The URL to post to.
     * @param data      The data to post.
     * @return
     * @throws MalformedURLException
     */
    public Q.Promise<Response> post(String url, Map<String,Object> data) throws MalformedURLException {
        Request request = new DataRequest( url, "POST");
        request.setBody( makeQueryString( data ) );
        request.setHeaders( m(
            kv("Content-Type", "application/x-www-form-urlencoded"),
            // The following is necessary because the string version of setBody is used above,
            // which converts the body string to a byte array using the platform default encoding.
            kv("charset", Charset.defaultCharset().name() )
        ));
        return send( request );
    }

    /**
     * Submit a request to an HTTP URL.
     * @param method    The HTTP method to use, e.g. GET or POST.
     * @param url       The URL to submit the request to.
     * @param data      Data to include in the request.
     * @return
     * @throws MalformedURLException
     */
    public Q.Promise<Response> submit(String method, String url, Map<String,Object> data) throws MalformedURLException {
        return "POST".equals( method ) ? post( url, data ) : get( url, data );
    }

    /** Test whether a response represents an authentication error. */
    private boolean isAuthenticationErrorResponse(Response response) {
        if( authenticationDelegate != null ) {
            return authenticationDelegate.isAuthenticationErrorResponse( this, response );
        }<commit_msg>Changes to HTTP client API.<commit_after>// Copyright 2016 InnerFunction Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
package com.innerfunction.http;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.text.TextUtils;

import com.innerfunction.q.Q;
import com.innerfunction.util.Files;
import com.innerfunction.util.RunQueue;

import static com.innerfunction.util.DataLiterals.*;

import java.io.File;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * An HTTP client.
 * Provides asynchronous methods for fetching files and data from an HTTP server.
 *
 * TODO Setup caching behaviour, as per https://developer.android.com/reference/android/net/http/HttpResponseCache.html
 * TODO Need to figure out how this will work with the filesystem content cache.
 *
 * Attached by juliangoacher on 08/07/16.
 */
public class Client {

    static final String Tag = "HTTPClient";

    // Setup cookie management.
    static final CookieManager CookieManager = new CookieManager();
    static {
        CookieHandler.setDefault( CookieManager );
    }

    /** A delegate object used to perform HTTP authentication, when required. */
    private AuthenticationDelegate authenticationDelegate;
    /** An object for checking network connectivity. */
    private ConnectivityManager connectivityManager;
    /** The app's cache location. Used for temporary download files. */
    private File cacheDir;

    public Client(Context context) {
        this.connectivityManager = (ConnectivityManager)context.getSystemService( Context.CONNECTIVITY_SERVICE );
        this.cacheDir = context.getCacheDir();
    }

    public void setAuthenticationDelegate(AuthenticationDelegate delegate) {
        this.authenticationDelegate = delegate;
    }

    /** Get an HTTP URL. */
    public Q.Promise<Response> get(String url) {
        return get( url, null, null );
    }

    /** Get an HTTP URL, using the specified values as query parameters. */
    public Q.Promise<Response> get(String url, Map<String,Object> params) {
        return get( url, params, null );
    }


    /** Get an HTTP URL, using the specified query parameters and request headers. */
    public Q.Promise<Response> get(String url, Map<String,Object> params, Map<String,Object> headers) {
        if( params != null ) {
            String query = makeQueryString( params );
            // Does the URL already have a query string?
            if( url.indexOf('?') > -1 ) {
                // Append additional parameters to end of query string.
                url = String.format("%s&%s", url, query );
            }
            else {
                // Append parameters as new query string.
                url = String.format("%s?%s", url, query );
            }
        }
        try {
            Request request = new DataRequest( url, "GET" );
            request.setHeaders( headers );
            return send( request );
        }
        catch(MalformedURLException e) {
            return Q.reject( e );
        }
    }

    /**
     * Get a file from an HTTP URL.
     * @param url       The URL to get.
     * @param dataFile  A file to write the URL's contents to.
     */
    public Q.Promise<Response> getFile(String url, File dataFile) {
        try {
            Request request = new FileRequest( url, "GET", dataFile );
            return send( request );
        }
        catch(MalformedURLException e) {
            return Q.reject( e );
        }
    }

    /**
     * Get a file from an HTTP URL.
     * Writes the response to a temporary file. The file can be retrieved through a call to
     * Response.getDataFile(). The file should be moved to suitable location if it needs to
     * be kept. (Note that the file object returned has a modified renameTo() method that can
     * properly handle moving files between separate disk partitions).
     * @param url The URL to get.
     */
    public Q.Promise<Response> getFile(String url) {
        String filename = String.format("%s_%d.tmp", getClass().getName(), System.currentTimeMillis() );
        File dataFile = new File( cacheDir, filename ) {
            @Override
            public void finalize() throws Throwable {
                try {
                    // Delete temporary file as soon as this object is GC'd.
                    delete();
                }
                catch(SecurityException e) {}
                super.finalize();
            }
        };
        try {
            Request request = new FileRequest( url, "GET", dataFile );
            return send( request );
        }
        catch(MalformedURLException e) {
            return Q.reject( e );
        }
    }

    /**
     * Post to an HTTP URL.
     * Performs an HTTP form post.
     * @param url       The URL to post to.
     * @param data      The data to post.
     * @return
     * @throws MalformedURLException
     */
    public Q.Promise<Response> post(String url, Map<String,Object> data) {
        try {
            Request request = new DataRequest( url, "POST" );
            request.setBody( makeQueryString( data ) );
            request.setHeaders( m(
                kv( "Content-Type", "application/x-www-form-urlencoded" ),
                // The following is necessary because the string version of setBody is used above,
                // which converts the body string to a byte array using the platform default encoding.
                kv( "charset", Charset.defaultCharset().name() )
            ) );
            return send( request );
        }
        catch(MalformedURLException e) {
            return Q.reject( e );
        }
    }

    /**
     * Submit a request to an HTTP URL.
     * @param method    The HTTP method to use, e.g. GET or POST.
     * @param url       The URL to submit the request to.
     * @param data      Data to include in the request.
     * @return
     * @throws MalformedURLException
     */
    public Q.Promise<Response> submit(String method, String url, Map<String,Object> data) {
        return "POST".equals( method ) ? post( url, data ) : get( url, data );
    }

    /** Test whether a response represents an authentication error. */
    private boolean isAuthenticationErrorResponse(Response response) {
        if( authenticationDelegate != null ) {
            return authenticationDelegate.isAuthenticationErrorResponse( this, response );
        }
{'fraction_non_alphanumeric': 0.09196165191740413, 'fraction_numerical': 0.0022123893805309734, 'mean_word_length': 2.4030112923462985, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 26, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3223169', 'n_tokens_mistral': 3486, 'n_tokens_neox': 3384, 'n_words': 1284}