blob: 403911863ca6296e4da0ee22c4fcd445b77b54c4 [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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
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.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.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
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.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.service.storage.WorkspaceDataStorage;
import org.eclipse.ui.PlatformUI;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class SkillService 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 static String capitalizeFirstLetter(String word) {
if ((word != null) && (!word.isEmpty()))
return word.substring(0, 1).toUpperCase() + word.substring(1);
return word;
}
private IUser fUser = null;
private IDataStorage fStorage = null;
private IQuestProvider fQuestProvider = null;
private UINotificationService fNotificationService = null;
/* package */ SkillService() {
}
@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
fUser = null;
fStorage = 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);
getNotificationService().dispose();
}
@Override
public void handleEvent(Event event) {
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);
}
storeUser();
}
@Override
public IUser getUser() {
if (fUser == null)
loadUser();
return fUser;
}
private void loadUser() {
if (getStorage().hasResource(USER_PROFILE)) {
try {
final InputStream userData = new ByteArrayInputStream(getStorage().loadResource(USER_PROFILE));
// 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.createResource(URI.createURI("user.skills"));
resource.load(userData, new HashMap<>());
fUser = (IUser) resource.getContents().get(0);
} catch (final IOException e) {
// user profile is damaged
fUser = createUser();
storeUser();
}
} else {
fUser = createUser();
storeUser();
}
}
private IUser createUser() {
final IUser user = ISkillsFactory.eINSTANCE.createUser();
user.setName(capitalizeFirstLetter(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 Resource resource = new XMIResourceImpl();
resource.getContents().add(user);
try {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
resource.save(out, extensionsMap);
getStorage().storeResource(USER_PROFILE, out.toByteArray());
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, "Could not store user profile", e);
}
}
private void createNotificationService() {
fNotificationService = new UINotificationService();
}
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 resetProgress() {
fUser = createUser();
storeUser();
}
@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;
}
@Override
public void setStorage(IDataStorage storage) {
fStorage = storage;
}
private IDataStorage getStorage() {
if (fStorage == null)
fStorage = new WorkspaceDataStorage();
return fStorage;
}
}