blob: 8f167a5780929b52bb3109bfa23666e2612d4d9a [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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IPath;
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.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.skills.Activator;
import org.eclipse.skills.BrokerTools;
import org.eclipse.skills.model.IQuest;
import org.eclipse.skills.model.IReward;
import org.eclipse.skills.model.ISkill;
import org.eclipse.skills.model.ISkillsFactory;
import org.eclipse.skills.model.ISkillsPackage;
import org.eclipse.skills.model.ITask;
import org.eclipse.skills.model.IUser;
import org.eclipse.skills.model.IUserTask;
import org.eclipse.skills.preferences.IPreferenceConstants;
import org.eclipse.skills.service.storage.WorkspaceDataStorage;
import org.eclipse.skills.ui.notifications.TaskCompletedNotification;
import org.eclipse.skills.ui.notifications.TaskStartedNotification;
import org.eclipse.ui.PlatformUI;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class SkillService implements ISkillService, EventHandler, IPropertyChangeListener {
private static final String USER_STORAGE = "user.skills";
private static ISkillService fInstance = null;
public synchronized static ISkillService getInstance() {
if (fInstance == null)
fInstance = new SkillService();
return fInstance;
}
private static IEventBroker getBroker() {
if (PlatformUI.isWorkbenchRunning())
return PlatformUI.getWorkbench().getService(IEventBroker.class);
return null;
}
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 static boolean isEqual(final ITask runningTask, final ITask openTask) {
return Objects.equals(openTask.getTitle(), runningTask.getTitle()) && EcoreUtil.equals(openTask.getDescription(), runningTask.getDescription());
}
private IUser fUser = null;
private final Collection<ITask> fTasks = null;
private final WorkspaceDataStorage fStorage;
private IQuestProvider fQuestProvider;
private SkillService() {
Activator.getDefault().getPreferenceStore().addPropertyChangeListener(this);
fStorage = new WorkspaceDataStorage();
initializeService();
}
private boolean isServiceEnabledByPreferences() {
return Activator.getDefault().getPreferenceStore().getBoolean(IPreferenceConstants.ACTIVATE_SKILLS);
}
private void initializeService() {
new InitializerJob().schedule();
}
private void registerForEvents() {
final IEventBroker service = getBroker();
if (service != null) {
service.subscribe(ISkillService.EVENT_BASE + "/*", this);
} 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() {
final IEventBroker broker = getBroker();
if (broker != null)
broker.unsubscribe(this);
}
@Override
public IUser getUser() {
if (fUser == null)
loadUser();
return fUser;
}
private File getUserProfileLocation() {
final IPath userStoragePath = Activator.getDefault().getStateLocation().append(USER_STORAGE);
return userStoragePath.toFile();
}
private void loadUser() {
if (getUserProfileLocation().exists()) {
// load stored data
// initialize the model
ISkillsPackage.eINSTANCE.eClass();
final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap();
extensionsMap.put("skills", new XMIResourceFactoryImpl());
final ResourceSet resourceSet = new ResourceSetImpl();
final Resource resource = resourceSet.getResource(URI.createFileURI(getUserProfileLocation().getAbsolutePath()), true);
fUser = (IUser) resource.getContents().get(0);
} else {
fUser = createUser();
storeUser();
}
}
private IUser createUser() {
final IUser user = ISkillsFactory.eINSTANCE.createUser();
user.setName(System.getProperty("user.name"));
final ISkill experience = ISkillsFactory.eINSTANCE.createSkill();
experience.setName("Experience");
user.setExperience(experience);
if (fUser != null) {
user.setName(fUser.getName());
user.setImageLocation(fUser.getImageLocation());
}
return user;
}
private void storeUser() {
final IUser user = getUser();
final Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
final Map<String, Object> extensionsMap = reg.getExtensionToFactoryMap();
extensionsMap.put("skills", new XMIResourceFactoryImpl());
final ResourceSet resourceSet = new ResourceSetImpl();
final Resource resource = resourceSet.createResource(URI.createFileURI(getUserProfileLocation().getAbsolutePath()));
resource.getContents().add(user);
try {
resource.save(Collections.EMPTY_MAP);
} catch (final IOException e) {
// FIXME we could not store the user data
e.printStackTrace();
}
}
/**
* Event handler for broker service events.
*/
@Override
public void handleEvent(Event event) {
if (ISkillService.EVENT_TASK_STARTED.equals(event.getTopic())) {
TaskStartedNotification.display((IUserTask) event.getProperty(IEventBroker.DATA));
}
if (ISkillService.EVENT_TASK_COMPLETED.equals(event.getTopic())) {
final IUserTask userTask = (IUserTask) event.getProperty(IEventBroker.DATA);
for (final IReward reward : userTask.getTask().getRewards())
getUser().consume(reward);
TaskCompletedNotification.display((IUserTask) event.getProperty(IEventBroker.DATA));
}
storeUser();
}
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 (isEqual(runningTask, openTask)) {
openTasks.remove(openTask);
break;
}
}
}
return openTasks;
}
@Override
public void storeResource(String name, byte[] data) throws IOException {
fStorage.storeResource(name, data);
}
@Override
public byte[] loadResource(String name) throws IOException {
return fStorage.loadResource(name);
}
@Override
public boolean hasResource(String name) {
return fStorage.hasResource(name);
}
@Override
public void resetProgress() {
fUser = createUser();
storeUser();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (IPreferenceConstants.ACTIVATE_SKILLS.equals(event.getProperty()))
initializeService();
}
@Override
public void reportTaskReady(ITask task) {
final IUserTask userTask = getUser().addTask(task);
if (task.isAutoActivation())
startTask(userTask);
}
@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;
}
private class InitializerJob extends Job {
public InitializerJob() {
super("Initialize Skills System");
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (isServiceEnabledByPreferences()) {
getUser();
registerForEvents();
startUserTasks();
activateOpenTasks();
} else {
stopUserTasks();
deactivateOpenTasks();
unregisterFromEvents();
}
return Status.OK_STATUS;
}
}
}