<commit_before>/*******************************************************************************
 * Copyright (c) 2016-2017 <NAME>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     <NAME> - initial API and implementation
 *******************************************************************************/
package net.resheim.eclipse.timekeeper.db;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;

import org.eclipse.core.resources.ISaveContext;
import org.eclipse.core.resources.ISaveParticipant;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.PersistenceProvider;
import org.eclipse.ui.statushandlers.StatusManager;
import org.flywaydb.core.Flyway;
import org.osgi.framework.BundleContext;

/**
 * Core features for the time keeping application. Handles database and basic
 * time tracking.
 * 
 * @author <NAME>
 */
@SuppressWarnings("restriction")
public class TimekeeperPlugin extends Plugin {

	/* Preferences */
	public static final String DATABASE_URL = "database-url";
	public static final String DATABASE_LOCATION = "database-location";
	public static final String DATABASE_LOCATION_SHARED = "shared";
	public static final String DATABASE_LOCATION_WORKSPACE = "workspace";
	public static final String DATABASE_LOCATION_URL = "url";

	public static final String KEY_VALUELIST_ID = "net.resheim.eclipse.timekeeper"; //$NON-NLS-1$
	
	public static final String BUNDLE_ID = "net.resheim.eclipse.timekeeper.db"; //$NON-NLS-1$

	private static TimekeeperPlugin instance;

	private static EntityManager entityManager = null;
	
	private static final ListenerList<DatabaseChangeListener> listeners = new ListenerList<>();
	
	public void addListener(DatabaseChangeListener listener){
		listeners.add(listener);
	}
	
	public void removeListener(DatabaseChangeListener listener){
		listeners.remove(listener);
	}
	
	private void notifyListeners(){
		for (DatabaseChangeListener databaseChangeListener : listeners) {
			SafeRunner.run(new ISafeRunnable() {				
				@Override
				public void run() throws Exception {
					databaseChangeListener.databaseStateChanged();					
				}

				@Override
				public void handleException(Throwable exception) {
					// ignore
				}
			});
		}
	}
	
	// Database configuration:
	// Since we need a database pretty early this method starts first, before 
	// the preference initializer have had the chance to run. So we ensure 
	// that there always is a database up and running. If the user changes
	// the database URL a restart is required.
	private void connectToDatabase() {
		Job connectDatabaseJob = new Job("Connecting to Timekeeper database") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
				Map<String, Object> props = new HashMap<String, Object>();
				// default, default location
				String jdbc_url = "jdbc:h2:~/.timekeeper/h2db";
				try {
					
					String location = Platform.getPreferencesService().getString(BUNDLE_ID, DATABASE_LOCATION, DATABASE_LOCATION_SHARED,new IScopeContext[] { InstanceScope.INSTANCE });
					switch (location){
						case DATABASE_LOCATION_SHARED:
							jdbc_url = getSharedLocation();
						break;
						case DATABASE_LOCATION_WORKSPACE:
							jdbc_url = getWorkspaceLocation();
						break;
						case DATABASE_LOCATION_URL:
						jdbc_url = getSpecifiedLocation();
						break;
					}					
					// baseline the database
			        Flyway flyway = new Flyway();<commit_msg>Fix #107: Sometimes trouble saving database <commit_after>/*******************************************************************************
 * Copyright (c) 2016-2017 <NAME>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     <NAME> - initial API and implementation
 *******************************************************************************/
package net.resheim.eclipse.timekeeper.db;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;

import org.eclipse.core.resources.ISaveContext;
import org.eclipse.core.resources.ISaveParticipant;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.PersistenceProvider;
import org.eclipse.ui.statushandlers.StatusManager;
import org.flywaydb.core.Flyway;
import org.osgi.framework.BundleContext;

/**
 * Core features for the time keeping application. Handles database and basic
 * time tracking.
 * 
 * @author <NAME>
 */
@SuppressWarnings("restriction")
public class TimekeeperPlugin extends Plugin {

	/* Preferences */
	public static final String DATABASE_URL = "database-url";
	public static final String DATABASE_LOCATION = "database-location";
	public static final String DATABASE_LOCATION_SHARED = "shared";
	public static final String DATABASE_LOCATION_WORKSPACE = "workspace";
	public static final String DATABASE_LOCATION_URL = "url";

	public static final String KEY_VALUELIST_ID = "net.resheim.eclipse.timekeeper"; //$NON-NLS-1$
	
	public static final String BUNDLE_ID = "net.resheim.eclipse.timekeeper.db"; //$NON-NLS-1$

	private static TimekeeperPlugin instance;

	private static EntityManager entityManager = null;
	
	private static final ListenerList<DatabaseChangeListener> listeners = new ListenerList<>();
	
	public void addListener(DatabaseChangeListener listener){
		listeners.add(listener);
	}
	
	public void removeListener(DatabaseChangeListener listener){
		listeners.remove(listener);
	}
	
	private void notifyListeners(){
		for (DatabaseChangeListener databaseChangeListener : listeners) {
			SafeRunner.run(new ISafeRunnable() {				
				@Override
				public void run() throws Exception {
					databaseChangeListener.databaseStateChanged();					
				}

				@Override
				public void handleException(Throwable exception) {
					// ignore
				}
			});
		}
	}
	
	// Database configuration:
	// Since we need a database pretty early this method starts first, before 
	// the preference initializer have had the chance to run. So we ensure 
	// that there always is a database up and running. If the user changes
	// the database URL a restart is required.
	private void connectToDatabase() {
		Job connectDatabaseJob = new Job("Connecting to Timekeeper database") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
				Map<String, Object> props = new HashMap<String, Object>();
				// default, default location
				String jdbc_url = "jdbc:h2:~/.timekeeper/h2db";
				try {
					
					String location = Platform.getPreferencesService().getString(BUNDLE_ID, DATABASE_LOCATION, DATABASE_LOCATION_SHARED,new IScopeContext[] { InstanceScope.INSTANCE });
					switch (location){
						case DATABASE_LOCATION_SHARED:
							jdbc_url = getSharedLocation();
							// Fix https://github.com/turesheim/eclipse-timekeeper/issues/107
							System.setProperty("h2.bindAddress", "localhost");
						break;
						case DATABASE_LOCATION_WORKSPACE:
							jdbc_url = getWorkspaceLocation();
						break;
						case DATABASE_LOCATION_URL:
						jdbc_url = getSpecifiedLocation();
						break;
					}					
					// baseline the database
			        Flyway flyway = new Flyway();
{'fraction_non_alphanumeric': 0.12310532452390206, 'fraction_numerical': 0.0037893509521958802, 'mean_word_length': 4.731069042316259, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6207618', 'n_tokens_mistral': 3212, 'n_tokens_neox': 2847, 'n_words': 709}