/* * Copyright (C) 2015 Actor LLC. <https://actor.im> */ package im.actor.core.modules.file; import java.util.ArrayList; import java.util.HashMap; import im.actor.core.entity.FileReference; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.file.entity.Downloaded; import im.actor.core.modules.ModuleActor; import im.actor.core.util.RandomUtils; import im.actor.core.viewmodel.UploadFileCallback; import im.actor.runtime.Log; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.Props; import im.actor.runtime.actors.messages.PoisonPill; import im.actor.runtime.files.FileSystemReference; public class UploadManager extends ModuleActor { private static final String TAG = "UploadManager"; private static final int SIM_MAX_UPLOADS = 2; private final boolean LOG; private ArrayList<QueueItem> queue = new ArrayList<>(); private HashMap<Long, ArrayList<UploadFileCallback>> callbacks = new HashMap<>(); public UploadManager(ModuleContext context) { super(context); this.LOG = context.getConfiguration().isEnableFilesLogging(); } // Tasks public void startUpload(long rid, String descriptor, String fileName, ActorRef requestActor) { if (LOG) { Log.d(TAG, "Starting upload #" + rid + " with descriptor " + descriptor); } QueueItem queueItem = new QueueItem(rid, descriptor, fileName, requestActor); queueItem.isStopped = false; queue.add(queueItem); checkQueue(); } public void stopUpload(long rid) { if (LOG) { Log.d(TAG, "Stopping upload #" + rid); } QueueItem queueItem = findItem(rid); if (queueItem == null) { if (LOG) { Log.d(TAG, "- Not present in queue"); } } else { if (queueItem.isStarted) { if (LOG) { Log.d(TAG, "- Stopping actor"); } queueItem.taskRef.send(PoisonPill.INSTANCE); queueItem.taskRef = null; queueItem.isStarted = false; } queue.remove(queueItem); ArrayList<UploadFileCallback> clist = callbacks.get(queueItem.rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } } } checkQueue(); } public void bindUpload(long rid, final UploadFileCallback callback) { if (LOG) { Log.d(TAG, "Bind upload #" + rid); } QueueItem queueItem = findItem(rid); if (queueItem == null) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } else { if (queueItem.isStopped) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } else { final float progress = queueItem.progress; im.actor.runtime.Runtime.dispatch(() -> callback.onUploading(progress)); } } ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist == null) { clist = new ArrayList<>(); callbacks.put(rid, clist); } clist.add(callback); } public void unbindUpload(long rid, UploadFileCallback callback) { ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { clist.remove(callback); } } public void requestState(long rid, final UploadFileCallback callback) { QueueItem queueItem = findItem(rid); if (queueItem == null) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } else { if (queueItem.isStopped) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } else { final float progress = queueItem.progress; im.actor.runtime.Runtime.dispatch(() -> callback.onUploading(progress)); } } } public void resumeUpload(long rid) { QueueItem queueItem = findItem(rid); if (queueItem != null) { if (queueItem.isStarted) { return; } if (queueItem.isStopped) { queueItem.isStopped = false; } queueItem.progress = 0; ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onUploading(0)); } } checkQueue(); } } public void pauseUpload(long rid) { QueueItem queueItem = findItem(rid); if (queueItem != null) { if (queueItem.isStarted) { queueItem.taskRef.send(PoisonPill.INSTANCE); queueItem.taskRef = null; queueItem.isStarted = false; } queueItem.isStopped = true; ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } } } } // Queue processing public void onUploadTaskError(long rid) { if (LOG) { Log.d(TAG, "Upload #" + rid + " error"); } QueueItem queueItem = findItem(rid); if (queueItem == null) { if (LOG) { Log.d(TAG, "- Nothing found"); } return; } if (!queueItem.isStarted) { return; } queueItem.taskRef.send(PoisonPill.INSTANCE); queueItem.isStopped = true; queueItem.isStarted = false; ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onNotUploading()); } } queueItem.requestActor.send(new UploadError(rid)); checkQueue(); } public void onUploadTaskProgress(long rid, final float progress) { if (LOG) { Log.d(TAG, "Upload #" + rid + " progress " + progress); } QueueItem queueItem = findItem(rid); if (queueItem == null) { return; } if (!queueItem.isStarted) { return; } queueItem.progress = progress; ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onUploading(progress)); } } } public void onUploadTaskComplete(long rid, FileReference fileReference, FileSystemReference reference) { if (LOG) { Log.d(TAG, "Upload #" + rid + " complete"); } QueueItem queueItem = findItem(rid); if (queueItem == null) { return; } if (!queueItem.isStarted) { return; } queue.remove(queueItem); queueItem.taskRef.send(PoisonPill.INSTANCE); // Saving reference to uploaded file context().getFilesModule().getDownloadedEngine().addOrUpdateItem(new Downloaded(fileReference.getFileId(), fileReference.getFileSize(), reference.getDescriptor())); ArrayList<UploadFileCallback> clist = callbacks.get(rid); if (clist != null) { for (final UploadFileCallback callback : clist) { im.actor.runtime.Runtime.dispatch(() -> callback.onUploaded()); } } queueItem.requestActor.send(new UploadCompleted(rid, fileReference)); checkQueue(); } private void checkQueue() { if (LOG) { Log.d(TAG, "- Checking queue"); } int activeUploads = 0; for (QueueItem queueItem : queue) { if (queueItem.isStarted) { activeUploads++; } } if (activeUploads >= SIM_MAX_UPLOADS) { if (LOG) { Log.d(TAG, "- Already have max number of simultaneous uploads"); } return; } QueueItem pendingQueue = null; for (QueueItem queueItem : queue) { if (!queueItem.isStarted && !queueItem.isStopped) { pendingQueue = queueItem; break; } } if (pendingQueue == null) { if (LOG) { Log.d(TAG, "- No work for uploading"); } return; } if (LOG) { Log.d(TAG, "- Starting upload file #" + pendingQueue.fileDescriptor); } pendingQueue.isStarted = true; final QueueItem finalPendingQueue = pendingQueue; pendingQueue.taskRef = system().actorOf(Props.create(() -> new UploadTask(finalPendingQueue.rid, finalPendingQueue.fileDescriptor, finalPendingQueue.fileName, self(), context())).changeDispatcher("heavy"), "actor/upload/task_" + RandomUtils.nextRid()); } private QueueItem findItem(long rid) { for (QueueItem q : queue) { if (q.rid == rid) { return q; } } return null; } private class QueueItem { private long rid; private String fileDescriptor; private boolean isStopped; private boolean isStarted; private float progress; private ActorRef taskRef; private ActorRef requestActor; private String fileName; private QueueItem(long rid, String fileDescriptor, String fileName, ActorRef requestActor) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.requestActor = requestActor; this.fileName = fileName; } } //region Messages @Override public void onReceive(Object message) { if (message instanceof StartUpload) { StartUpload startUpload = (StartUpload) message; startUpload(startUpload.getRid(), startUpload.getFileDescriptor(), startUpload.getFileName(), sender()); } else if (message instanceof StopUpload) { StopUpload cancelUpload = (StopUpload) message; stopUpload(cancelUpload.getRid()); } else if (message instanceof UploadTaskError) { UploadTaskError uploadTaskError = (UploadTaskError) message; onUploadTaskError(uploadTaskError.getRid()); } else if (message instanceof UploadTaskProgress) { UploadTaskProgress taskProgress = (UploadTaskProgress) message; onUploadTaskProgress(taskProgress.getRid(), taskProgress.getProgress()); } else if (message instanceof UploadTaskComplete) { UploadTaskComplete taskComplete = (UploadTaskComplete) message; onUploadTaskComplete(taskComplete.getRid(), taskComplete.getLocation(), taskComplete.getReference()); } else if (message instanceof BindUpload) { BindUpload bindUpload = (BindUpload) message; bindUpload(bindUpload.getRid(), bindUpload.getCallback()); } else if (message instanceof UnbindUpload) { UnbindUpload unbindUpload = (UnbindUpload) message; unbindUpload(unbindUpload.getRid(), unbindUpload.getCallback()); } else if (message instanceof RequestState) { RequestState requestState = (RequestState) message; requestState(requestState.getRid(), requestState.getCallback()); } else if (message instanceof PauseUpload) { PauseUpload pauseUpload = (PauseUpload) message; pauseUpload(pauseUpload.getRid()); } else if (message instanceof ResumeUpload) { ResumeUpload resumeUpload = (ResumeUpload) message; resumeUpload(resumeUpload.getRid()); } else { super.onReceive(message); } } public static class StartUpload { private long rid; private String fileDescriptor; private String fileName; public StartUpload(long rid, String fileDescriptor, String fileName) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.fileName = fileName; } public long getRid() { return rid; } public String getFileDescriptor() { return fileDescriptor; } public String getFileName() { return fileName; } } public static class BindUpload { private long rid; private UploadFileCallback callback; public BindUpload(long rid, UploadFileCallback callback) { this.rid = rid; this.callback = callback; } public long getRid() { return rid; } public UploadFileCallback getCallback() { return callback; } } public static class UnbindUpload { private long rid; private UploadFileCallback callback; public UnbindUpload(long rid, UploadFileCallback callback) { this.rid = rid; this.callback = callback; } public long getRid() { return rid; } public UploadFileCallback getCallback() { return callback; } } public static class StopUpload { private long rid; public StopUpload(long rid) { this.rid = rid; } public long getRid() { return rid; } } public static class UploadTaskError { private long rid; public UploadTaskError(long rid) { this.rid = rid; } public long getRid() { return rid; } } public static class UploadTaskProgress { private long rid; private float progress; public UploadTaskProgress(long rid, float progress) { this.rid = rid; this.progress = progress; } public long getRid() { return rid; } public float getProgress() { return progress; } } public static class UploadTaskComplete { private long rid; private FileReference location; private FileSystemReference reference; public UploadTaskComplete(long rid, FileReference location, FileSystemReference reference) { this.rid = rid; this.location = location; this.reference = reference; } public long getRid() { return rid; } public FileSystemReference getReference() { return reference; } public FileReference getLocation() { return location; } } public static class UploadCompleted { private long rid; private FileReference fileReference; public UploadCompleted(long rid, FileReference fileReference) { this.rid = rid; this.fileReference = fileReference; } public long getRid() { return rid; } public FileReference getFileReference() { return fileReference; } } public static class UploadError { private long rid; public UploadError(long rid) { this.rid = rid; } public long getRid() { return rid; } } public static class RequestState { private long rid; private UploadFileCallback callback; public RequestState(long rid, UploadFileCallback callback) { this.rid = rid; this.callback = callback; } public long getRid() { return rid; } public UploadFileCallback getCallback() { return callback; } } public static class PauseUpload { private long rid; public PauseUpload(long rid) { this.rid = rid; } public long getRid() { return rid; } } public static class ResumeUpload { private long rid; public ResumeUpload(long rid) { this.rid = rid; } public long getRid() { return rid; } } //endregion }