blob: 633a0e77589747807a373a027e42e58f59fb1266 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Christian Pontesegger and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.skills.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.skills.Activator;
import org.eclipse.skills.BrokerTools;
import org.eclipse.skills.Logger;
import org.eclipse.skills.model.IQuest;
import org.eclipse.skills.model.IReward;
import org.eclipse.skills.model.ITask;
import org.eclipse.skills.model.IUser;
import org.eclipse.skills.model.IUserTask;
import org.eclipse.skills.service.questprovider.ExtensionPointQuestProvider;
import org.eclipse.skills.service.questprovider.IQuestProvider;
import org.eclipse.skills.service.storage.DataStorageProxy;
import org.eclipse.skills.service.storage.WorkspaceDataStorage;
import org.eclipse.ui.PlatformUI;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class SkillService extends DataStorageProxy implements ISkillService, EventHandler {
private static ISkillService fInstance = null;
public synchronized static ISkillService getInstance() {
if (fInstance == null)
fInstance = new SkillService();
return fInstance;
}
private static Collection<ITask> extractTasksRecursively(Collection<ITask> tasks) {
final Collection<ITask> result = new ArrayList<>(tasks);
for (final ITask task : tasks)
result.addAll(extractTasksRecursively(task.getTasks()));
return result;
}
private UserStorage fUserStorage = null;
private IQuestProvider fQuestProvider = null;
private UINotificationService fNotificationService = null;
/* package */ SkillService() {
super(new WorkspaceDataStorage());
}
@Override
public void activateService() {
getUser();
registerForEvents();
startUserTasks();
activateOpenTasks();
BrokerTools.post(ISkillService.EVENT_USER_UPDATE, getUser());
}
@Override
public void deactivateService() {
stopUserTasks();
deactivateOpenTasks();
unregisterFromEvents();
BrokerTools.post(ISkillService.EVENT_USER_UPDATE, getUser());
// unload resources
fUserStorage = null;
fQuestProvider = null;
fNotificationService = null;
}
private void registerForEvents() {
final IEventBroker broker = BrokerTools.getBroker();
if (broker != null) {
broker.subscribe(ISkillService.EVENT_BASE + "/*", this);
createNotificationService();
} else if (PlatformUI.isWorkbenchRunning()) {
// probably the broker service will startup later
final Job job = new Job("Register for Skill events") {
@Override
protected IStatus run(IProgressMonitor monitor) {
registerForEvents();
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule(10 * 1000);
}
}
private void unregisterFromEvents() {
BrokerTools.unsubscribe(this);
if (hasNotificationService())
getNotificationService().dispose();
}
@Override
public void handleEvent(Event event) {
if (ISkillService.EVENT_TASK_COMPLETED.equals(event.getTopic())) {
final Object data = event.getProperty(IEventBroker.DATA);
if (data instanceof IUserTask)
notifyTaskCompleted((IUserTask) data);
else
Logger.warning(Activator.PLUGIN_ID, "Task completed event from broker with invalid data type detected",
new IllegalArgumentException("Unknown event type: " + data));
} else if (ISkillService.EVENT_TASK_READY.equals(event.getTopic())) {
final Object data = event.getProperty(IEventBroker.DATA);
if (data instanceof ITask)
notifyTaskReady((ITask) data);
else
Logger.warning(Activator.PLUGIN_ID, "Task ready event from broker with invalid data type detected",
new IllegalArgumentException("Unknown event type: " + data));
}
}
@Override
public IUserTask notifyTaskReady(ITask task) {
final IUserTask userTask = getUser().addTask(task);
if (task.isAutoActivation())
startTask(userTask);
storeUser();
return userTask;
}
@Override
public void notifyTaskCompleted(IUserTask userTask) {
for (final IReward reward : userTask.getTask().getRewards())
getUser().consume(reward);
updateUserTitle();
storeUser();
}
private void updateUserTitle() {
getUser().setTitle(getUserTitle());
}
private String getUserTitle() {
final UserTitleGenerator userTitleGenerator = new UserTitleGenerator(getUser(), getQuestProvider());
return userTitleGenerator.createUserTitle();
}
@Override
public IUser getUser() {
return getUserStorage().getUser();
}
private void storeUser() {
try {
getUserStorage().storeUser(getUser());
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, "Could not store user profile", e);
}
}
@Override
public void resetProgress() {
getUserStorage().resetProgress();
storeUser();
}
private UserStorage getUserStorage() {
if (fUserStorage == null)
fUserStorage = new UserStorage(this);
return fUserStorage;
}
private void createNotificationService() {
fNotificationService = new UINotificationService();
}
private boolean hasNotificationService() {
return fNotificationService != null;
}
private UINotificationService getNotificationService() {
if (fNotificationService == null)
createNotificationService();
return fNotificationService;
}
private void startUserTasks() {
for (final IUserTask task : getUser().getUsertasks()) {
if (task.isStarted() && !task.isCompleted())
task.activate();
}
}
private void stopUserTasks() {
for (final IUserTask task : getUser().getUsertasks()) {
if (!task.isCompleted())
task.getTask().getGoal().deactivate();
}
}
private void activateOpenTasks() {
for (final ITask task : getOpenTasks()) {
task.getRequirement().activate();
}
}
private void deactivateOpenTasks() {
for (final ITask task : getOpenTasks())
task.getRequirement().deactivate();
}
@Override
public Collection<ITask> getOpenTasks() {
final Collection<ITask> openTasks = getAllAvailableTasks();
final List<ITask> runningTasks = getUser().getUsertasks().stream().map(t -> t.getTask()).collect(Collectors.toList());
// TODO is there a better way to remove the running tasks? openTasks.removeAll() does not work
for (final ITask runningTask : runningTasks) {
for (final ITask openTask : openTasks) {
if (Objects.equals(runningTask, openTask)) {
openTasks.remove(openTask);
break;
}
}
}
return openTasks;
}
@Override
public void storeResource(String name, byte[] data) throws IOException {
getStorage().storeResource(name, data);
}
@Override
public byte[] loadResource(String name) throws IOException {
return getStorage().loadResource(name);
}
@Override
public boolean hasResource(String name) {
return getStorage().hasResource(name);
}
@Override
public void startTask(final IUserTask userTask) {
userTask.setStarted(new Date());
BrokerTools.post(ISkillService.EVENT_TASK_STARTED, userTask);
userTask.activate();
}
@Override
public void setQuestProvider(IQuestProvider questProvider) {
fQuestProvider = questProvider;
}
@Override
public IQuestProvider getQuestProvider() {
if (fQuestProvider != null)
return fQuestProvider;
return new ExtensionPointQuestProvider();
}
@Override
public Collection<ITask> getAllAvailableTasks() {
final Collection<ITask> tasks = new HashSet<>();
for (final IQuest quest : getQuestProvider().getQuests())
tasks.addAll(extractTasksRecursively(quest.getTasks()));
return tasks;
}
}