package comeon.core; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Singleton; import comeon.core.events.*; import comeon.core.extmetadata.DuplicateKeyException; import comeon.core.extmetadata.ExternalMetadataSource; import comeon.mediawiki.FailedLogoutException; import comeon.mediawiki.MediaWiki; import comeon.mediawiki.MediaWikiFactory; import comeon.model.Media; import comeon.model.Media.State; import comeon.model.Template; import comeon.model.Wiki; import comeon.model.processors.PreProcessor; import comeon.ui.actions.MediaAddedEvent; import comeon.ui.actions.MediaRemovedEvent; import comeon.wikis.ActiveWikiChangeEvent; import comeon.wikis.Wikis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; @Singleton public final class CoreImpl implements Core { private static final Logger LOGGER = LoggerFactory.getLogger(CoreImpl.class); private final Set<Media> media; private final ExecutorService pool; private final Wikis wikis; private final EventBus bus; private final MediaWikiFactory mediaWikiFactory; private final Set<PreProcessor> preProcessors; private final Queue<Future<UploadReport>> currentTasks; private final UploaderReporter reporter; private MediaWiki activeMediaWiki; @Inject private CoreImpl(final Wikis wikis, final ExecutorService pool, final EventBus bus, final MediaWikiFactory mediaWikiFactory, final Set<PreProcessor> preProcessors, final UploaderReporter reporter) { this.media = new HashSet<>(); this.currentTasks = new ConcurrentLinkedQueue<>(); this.pool = pool; this.bus = bus; this.wikis = wikis; this.mediaWikiFactory = mediaWikiFactory; this.preProcessors = preProcessors; this.reporter = reporter; final Wiki activeWiki = wikis.getActiveWiki(); if (activeWiki == null) { throw new IllegalStateException("There must be one active wiki."); } else { this.activeMediaWiki = mediaWikiFactory.build(activeWiki); } } @Override public void addMedia(final File[] files, final Template defautTemplate, final ExternalMetadataSource<?> externalMetadataSource) throws IOException, DuplicateKeyException { externalMetadataSource.loadMetadata(); final MediaUploadBatch batch = new MediaUploadBatch(files, defautTemplate, preProcessors, externalMetadataSource); final Set<Media> newMedia = batch.readFiles(wikis.getActiveWiki().getUser()).getMedia(); this.media.addAll(newMedia); bus.post(new MediaAddedEvent(batch)); } @Override public void removeMedia(final Media media) { this.media.remove(media); bus.post(new MediaRemovedEvent(media)); } @Override public void removeAllMedia() { final List<Media> removedMedia = new ArrayList<>(this.media); this.media.clear(); bus.post(new MediaRemovedEvent(removedMedia)); } @Override public Set<Media> getMedia() { return Collections.unmodifiableSet(media); } private boolean shouldUpload(final Media media) { return !State.UploadedSuccessfully.equals(media.getState()); } @Override public int countMediaToBeUploaded() { return (int) media.parallelStream().filter(this::shouldUpload).count(); } private class UploadTask implements Callable<UploadReport> { private final Logger taskLogger = LoggerFactory.getLogger(UploadTask.class); private final Media media; public UploadTask(final Media media) { this.media = media; } @Override public UploadReport call() throws Exception { try { taskLogger.debug("Starting upload of {}", media.getFileName()); final ProgressListenerAdapter progressListener = new ProgressListenerAdapter(); bus.post(new MediaTransferStartingEvent(media, progressListener)); activeMediaWiki.upload(media, progressListener); media.setState(State.UploadedSuccessfully); bus.post(new MediaTransferDoneEvent(media)); taskLogger.debug("Finished upload of {}", media.getFileName()); return new UploadReport(media); } catch (final Throwable e) { taskLogger.warn("Failed upload of {}", media.getFileName(), e); media.setState(State.FailedUpload); bus.post(new MediaTransferFailedEvent(media, e)); return new UploadReport(media, e); } } @Override public boolean equals(final Object obj) { final boolean isEqual; if (obj == null) { isEqual = false; } else if (obj instanceof UploadTask) { final UploadTask task = (UploadTask) obj; isEqual = task.media.equals(this.media); } else { isEqual = super.equals(obj); } return isEqual; } } @Override public void uploadMedia() { final List<Media> mediaToBeUploaded = media.parallelStream().filter(this::shouldUpload).collect(Collectors.toList()); LOGGER.info("Uploading {} media to {}.", mediaToBeUploaded.size(), activeMediaWiki.getName()); bus.post(new UploadStartingEvent(mediaToBeUploaded)); final List<Future<UploadReport>> tasks = mediaToBeUploaded.stream().map(UploadTask::new).map(pool::submit).collect(Collectors.toList()); currentTasks.addAll(tasks); final List<UploadReport> reports = new ArrayList<>(tasks.size()); try { for (final Future<UploadReport> task : tasks) { try { reports.add(task.get()); } catch (final CancellationException e) { LOGGER.debug("Task was cancelled", e); } catch (final ExecutionException e) { LOGGER.warn("Task execution failed", e); } finally { currentTasks.remove(task); } } } catch (final InterruptedException e) { Thread.interrupted(); LOGGER.warn("We were interrupted while waiting for uploads to complete", e); } finally { try { activeMediaWiki.logout(); } catch (final FailedLogoutException e) { LOGGER.warn("Couldn't close Mediawiki session properly", e); } bus.post(new UploadDoneEvent(reports, reporter.findLoggingFileLocation().orElse(null))); LOGGER.info("Upload done."); } } @Override public void abort() { final List<Future<UploadReport>> cancelledTasks = currentTasks.parallelStream().filter(task -> task.cancel(true)).collect(Collectors.toList()); currentTasks.removeAll(cancelledTasks); } @Subscribe public void handleActiveWikiChangeEvent(final ActiveWikiChangeEvent event) { if (this.activeMediaWiki.isLoggedIn()) { try { this.activeMediaWiki.logout(); } catch (final FailedLogoutException e) { LOGGER.warn("Failed implicit logout", e); } } this.activeMediaWiki = mediaWikiFactory.build(wikis.getActiveWiki()); for (final Media media : this.media) { media.renderTemplate(wikis.getActiveWiki().getUser()); } } }