Search in sources :

Example 1 with EasyThread

use of com.codename1.util.EasyThread in project CodenameOne by codenameone.

the class Util method downloadUrlSafely.

/**
 * <p>
 * Safely download the given URL to the Storage or to the FileSystemStorage:
 * this method is resistant to network errors and capable of resume the
 * download as soon as network conditions allow and in a completely
 * transparent way for the user; note that in the global network error
 * handling, there must be an automatic
 * <pre>.retry()</pre>, as in the code example below.</p>
 * <p>
 * This method is useful if the server correctly returns Content-Length and
 * if it supports partial downloads: if not, it works like a normal
 * download.</p>
 * <p>
 * Pros: always allows you to complete downloads, even if very heavy (e.g.
 * 100MB), even if the connection is unstable (network errors) and even if
 * the app goes temporarily in the background (on some platforms the
 * download will continue in the background, on others it will be
 * temporarily suspended).</p>
 * <p>
 * Cons: since this method is based on splitting the download into small
 * parts (512kbytes is the default), this approach causes many GET requests
 * that slightly slow down the download and cause more traffic than normally
 * needed.</p>
 * <p>
 * Usage example:</p>
 * <script src="https://gist.github.com/jsfan3/554590a12c3102a3d77e17533e7eca98.js"></script>
 *
 * @param url
 * @param fileName must be a valid Storage file name or FileSystemStorage
 * file path
 * @param percentageCallback invoked (in EDT) during the download to notify
 * the progress (from 0 to 100); it can be null if you are not interested in
 * monitoring the progress
 * @param filesavedCallback invoked (in EDT) only when the download is
 * finished; if null, no action is taken
 * @throws IOException
 */
public static void downloadUrlSafely(String url, final String fileName, final OnComplete<Integer> percentageCallback, final OnComplete<String> filesavedCallback) throws IOException {
    // Code discussion here: https://stackoverflow.com/a/62137379/1277576
    String partialDownloadsDir = FileSystemStorage.getInstance().getAppHomePath() + FileSystemStorage.getInstance().getFileSystemSeparator() + "partialDownloads";
    if (!FileSystemStorage.getInstance().isDirectory(partialDownloadsDir)) {
        FileSystemStorage.getInstance().mkdir(partialDownloadsDir);
    }
    // do its best to be unique if there are parallel downloads
    final String uniqueId = url.hashCode() + "" + downloadUrlSafelyRandom.nextInt();
    final String partialDownloadPath = partialDownloadsDir + FileSystemStorage.getInstance().getFileSystemSeparator() + uniqueId;
    // as discussed here: https://stackoverflow.com/a/57984257
    final boolean isStorage = fileName.indexOf("/") < 0;
    // total expected download size, with a check partial download support
    final long fileSize = getFileSizeWithoutDownload(url, true);
    // 512 kbyte, size of each small download
    final int splittingSize = 512 * 1024;
    final Wrapper<Long> downloadedTotalBytes = new Wrapper<Long>(0l);
    final OutputStream out;
    if (isStorage) {
        // leave it open to append partial downloads
        out = Storage.getInstance().createOutputStream(fileName);
    } else {
        out = FileSystemStorage.getInstance().openOutputStream(fileName);
    }
    // Codename One thread that supports crash protection and similar Codename One features.
    final EasyThread mergeFilesThread = EasyThread.start("mergeFilesThread");
    final ConnectionRequest cr = new GZConnectionRequest();
    cr.setUrl(url);
    cr.setPost(false);
    if (fileSize > splittingSize) {
        // Which byte should the download start from?
        cr.addRequestHeader("Range", "bytes=0-" + splittingSize);
        cr.setDestinationFile(partialDownloadPath);
    } else {
        Util.cleanup(out);
        if (isStorage) {
            cr.setDestinationStorage(fileName);
        } else {
            cr.setDestinationFile(fileName);
        }
    }
    cr.addResponseListener(new ActionListener<NetworkEvent>() {

        @Override
        public void actionPerformed(NetworkEvent evt) {
            mergeFilesThread.run(new Runnable() {

                @Override
                public void run() {
                    try {
                        // We append the just saved partial download to the fileName, if it exists
                        if (FileSystemStorage.getInstance().exists(partialDownloadPath)) {
                            InputStream in = FileSystemStorage.getInstance().openInputStream(partialDownloadPath);
                            Util.copyNoClose(in, out, 8192);
                            Util.cleanup(in);
                            // before deleting the file, we check and update how much data we have actually downloaded
                            downloadedTotalBytes.set(downloadedTotalBytes.get() + FileSystemStorage.getInstance().getLength(partialDownloadPath));
                            FileSystemStorage.getInstance().delete(partialDownloadPath);
                        }
                        // Is the download finished?
                        if (downloadedTotalBytes.get() > fileSize) {
                            throw new IllegalStateException("More content has been downloaded than the file length, check the code.");
                        }
                        if (fileSize <= 0 || downloadedTotalBytes.get() == fileSize) {
                            // yes, download finished
                            Util.cleanup(out);
                            if (filesavedCallback != null) {
                                CN.callSerially(new Runnable() {

                                    @Override
                                    public void run() {
                                        filesavedCallback.completed(fileName);
                                    }
                                });
                            }
                        } else {
                            // no, it's not finished, we repeat the request after updating the "Range" header
                            cr.addRequestHeader("Range", "bytes=" + downloadedTotalBytes.get() + "-" + (Math.min(downloadedTotalBytes.get() + splittingSize, fileSize)));
                            NetworkManager.getInstance().addToQueue(cr);
                        }
                    } catch (IOException ex) {
                        Log.p("Error in appending splitted file to output file", Log.ERROR);
                        Log.e(ex);
                        Log.sendLogAsync();
                    }
                }
            });
        }
    });
    NetworkManager.getInstance().addToQueue(cr);
    NetworkManager.getInstance().addProgressListener(new ActionListener<NetworkEvent>() {

        @Override
        public void actionPerformed(NetworkEvent evt) {
            if (cr == evt.getConnectionRequest() && fileSize > 0) {
                // the following casting to long is necessary when the file is bigger than 21MB, otherwise the result of the calculation is wrong
                if (percentageCallback != null) {
                    CN.callSerially(new Runnable() {

                        @Override
                        public void run() {
                            percentageCallback.completed((int) ((long) downloadedTotalBytes.get() * 100 / fileSize));
                        }
                    });
                }
            }
        }
    });
}
Also used : Wrapper(com.codename1.util.Wrapper) DataInputStream(java.io.DataInputStream) InputStream(java.io.InputStream) ByteArrayOutputStream(java.io.ByteArrayOutputStream) DataOutputStream(java.io.DataOutputStream) OutputStream(java.io.OutputStream) IOException(java.io.IOException) GZConnectionRequest(com.codename1.io.gzip.GZConnectionRequest) GZConnectionRequest(com.codename1.io.gzip.GZConnectionRequest) EasyThread(com.codename1.util.EasyThread)

Example 2 with EasyThread

use of com.codename1.util.EasyThread in project CodenameOne by codenameone.

the class SynchronizedTest method run.

public void run() {
    EasyThread thread = EasyThread.start("Thread1");
    thread.run(() -> runTest1());
    runTest1();
    Log.p("TEST 1 COMPLETE");
    thread.run(() -> runTest2());
    runTest2();
    Log.p("TEST 2 COMPLETE");
    thread.run(() -> runTest3());
    runTest3();
    Log.p("TEST 3 COMPLETE");
    thread.run(() -> runTest4());
    runTest4();
    Log.p("TEST 4 COMPLETE");
    thread.run(() -> runTest5());
    runTest5();
    Log.p("TEST 5 COMPLETE");
}
Also used : EasyThread(com.codename1.util.EasyThread)

Aggregations

EasyThread (com.codename1.util.EasyThread)2 GZConnectionRequest (com.codename1.io.gzip.GZConnectionRequest)1 Wrapper (com.codename1.util.Wrapper)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 DataInputStream (java.io.DataInputStream)1 DataOutputStream (java.io.DataOutputStream)1 IOException (java.io.IOException)1 InputStream (java.io.InputStream)1 OutputStream (java.io.OutputStream)1