de.chaosfisch.google.youtube.upload.Uploader.java Source code

Java tutorial

Introduction

Here is the source code for de.chaosfisch.google.youtube.upload.Uploader.java

Source

/*
 * Copyright (c) 2014 Dennis Fischer.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0+
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 *
 * Contributors: Dennis Fischer
 */

package de.chaosfisch.google.youtube.upload;

import com.google.common.collect.Maps;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.RateLimiter;
import com.google.inject.Inject;
import de.chaosfisch.google.youtube.upload.events.UploadEvent;
import de.chaosfisch.google.youtube.upload.events.UploadFinishedEvent;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.*;

public class Uploader {

    private static final int ENQUEUE_WAIT_TIME = 10000;
    private static final int DEFAULT_MAX_UPLOADS = 1;
    private static final int ONE_KILOBYTE = 1024;
    private static final Logger logger = LoggerFactory.getLogger(Uploader.class);
    public static final String STOP_ON_ERROR = "stopOnError";

    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
    private final ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
    private final RateLimiter rateLimitter = RateLimiter.create(Double.MAX_VALUE);
    private final CompletionService<Upload> jobCompletionService = new ExecutorCompletionService<>(executorService);
    private final HashMap<Upload, Future<Upload>> futures = Maps.newHashMapWithExpectedSize(10);

    private final EventBus eventBus;
    private final IUploadJobFactory uploadJobFactory;
    private final Configuration configuration;
    private int runningUploads;
    private IUploadService uploadService;
    private UploadFinishProcessor consumer;
    private ScheduledFuture<?> task;

    private int maxUploads = DEFAULT_MAX_UPLOADS;

    @Inject
    public Uploader(final EventBus eventBus, final IUploadJobFactory uploadJobFactory,
            final Configuration configuration) {
        this.eventBus = eventBus;
        this.uploadJobFactory = uploadJobFactory;
        this.configuration = configuration;
    }

    private void createConsumer() {
        if (null != consumer && consumer.isAlive()) {
            return;
        }
        consumer = new UploadFinishProcessor();
        consumer.setDaemon(true);
        consumer.start();
    }

    public void setMaxUploads(final int maxUploads) {
        this.maxUploads = maxUploads;
        if (canAddJob()) {
            enqueueUpload();
        }
    }

    private boolean canAddJob() {
        return uploadService.getRunning() && maxUploads > runningUploads;
    }

    void shutdown(final boolean force) {
        uploadService.setRunning(false);

        if (force) {
            for (final Map.Entry<Upload, Future<Upload>> job : futures.entrySet()) {
                job.getValue().cancel(true);
                futures.remove(job.getKey());
            }
        }
    }

    public void run() {
        if (uploadService.getRunning()) {
            return;
        }
        uploadService.setRunning(true);

        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (canAddJob() && hasJobs()) {
                    enqueueUpload();

                    try {
                        Thread.sleep(ENQUEUE_WAIT_TIME);
                    } catch (final InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }, "Enqueue-Thread");
        thread.setDaemon(true);
        thread.start();
    }

    private boolean hasJobs() {
        return 0 < uploadService.countUnprocessed();
    }

    public void shutdown() {
        shutdown(false);
    }

    public void abort(final Upload upload) {
        futures.get(upload).cancel(true);
    }

    private void enqueueUpload() {
        if (canAddJob()) {
            final Upload polled = uploadService.fetchNextUpload();
            if (null != polled) {
                if (null == polled.getAccount()) {
                    polled.getStatus().setLocked(true);
                    uploadService.update(polled);
                } else {
                    createConsumer();
                    polled.getStatus().setRunning(true);
                    uploadService.update(polled);
                    futures.put(polled, jobCompletionService.submit(uploadJobFactory.create(polled, rateLimitter)));
                    runningUploads++;
                }
            }
        }
    }

    public void setUploadService(final IUploadService uploadService) {
        this.uploadService = uploadService;
    }

    public void setMaxSpeed(final int maxSpeed) {
        rateLimitter.setRate(0 == maxSpeed ? Double.MAX_VALUE : maxSpeed * ONE_KILOBYTE);
    }

    private class UploadFinishProcessor extends Thread {
        public UploadFinishProcessor() {
            super("Upload Finish Processor-Thread");
        }

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                final Upload upload = getUpload();
                futures.remove(upload);
                if (null != upload) {
                    logger.info("Running uploads: {}", runningUploads);

                    if (upload.isPauseOnFinish()) {
                        uploadService.setRunning(false);
                    }

                    if (upload.getStatus().isFailed() && configuration.getBoolean(STOP_ON_ERROR, false)) {
                        uploadService.stopUploading();
                    }
                }
                final long leftUploads = uploadService.countUnprocessed();
                logger.info("Left uploads: {}", leftUploads);
                enqueueUpload();

                if ((!uploadService.getRunning() || 0 == leftUploads) && 0 == runningUploads) {
                    uploadService.setRunning(false);
                    logger.info("All uploads finished");
                    eventBus.post(new UploadFinishedEvent());
                }
            }
        }

        private Upload getUpload() {
            try {
                final Future<Upload> uploadJobFuture = jobCompletionService.take();
                final Upload upload = uploadJobFuture.get();
                logger.info("Upload finished: {}; {}", upload.getMetadata().getTitle(), upload.getVideoid());
                return upload;
            } catch (ExecutionException | CancellationException | InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            } finally {
                runningUploads--;
            }
        }
    }

    public void runStarttimeChecker() {
        logger.debug("Running starttime checker");
        final long delay = uploadService.getStarttimeDelay();
        logger.debug("Delay to upload is {}", delay);
        final TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                if (0 < uploadService.countReadyStarttime()) {
                    Uploader.this.run();
                }
                runStarttimeChecker();
            }
        };
        if (-1 != delay && (0 == runningUploads || canAddJob())) {
            task = timer.schedule(timerTask, delay, TimeUnit.MILLISECONDS);
        }
    }

    @Subscribe
    public void onUploadEvent(final UploadEvent uploadEvent) {
        if (null != task && !task.isCancelled()) {
            task.cancel(false);
        }
        runStarttimeChecker();
    }

    public void stopStarttimeChecker() {
        timer.shutdownNow();
        executorService.shutdownNow();
    }
}