initial commit

This commit is contained in:
Daniel-Schroeter 2022-12-22 12:01:13 +01:00
parent c89449765b
commit c6aceb6010
97 changed files with 7670 additions and 0 deletions

17
.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
# Eclipse Core
**/.classpath
**/.project
**/.settings/
# IntelliJ IDEA
**/*.iml
**/.idea
# VS Code
.vscode
# Maven Build
**/target
# Node
node_modules/

10
README.md Normal file
View file

@ -0,0 +1,10 @@
# MoodleSync
Due to the digitalization in education and the resulting increasing amount of e-learning resources,
many universities and other educational institutions use browser based learning platforms for sharing
and managing those. An often used learning platform is Moodle. It offers lecturers the possibility
to publish lecture notes, recordings and other e-learning materials amongst their students. Because
of the fact that the process to upload and manage data via the browser view of Moodle is very time
consuming, the objective of this project was to develop a desktop application used for file
synchronization between a local directory and the learning platform Moodle. Futhermore a Moodle plugin was developed.
Works with following plugin for Moodle: https://github.com/lectureStudio/moodle-local_sync_service.

113
moodle-sync-cli/pom.xml Normal file
View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-cli</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jackson.version>2.13.3</jackson.version>
<log4j.version>2.17.2</log4j.version>
<resteasy.version>4.7.5.Final</resteasy.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<classifier>${envClassifier}</classifier>
<archive>
<manifest>
<mainClass>moodle.sync.cli.CommandLineInterface</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<classpathLayoutType>custom</classpathLayoutType>
<customClasspathLayout>${artifact.artifactId}.${artifact.extension}</customClasspathLayout>
</manifest>
<manifestEntries>
<Class-Path>lib/javafx-controls-win.jar lib/javafx-graphics-win.jar lib/javafx-base-win.jar lib/javafx-fxml-win.jar lib/javafx-swing-win.jar lib/javafx-media-win.jar</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>resources</targetPath>
<filtering>false</filtering>
<excludes>
<exclude>log4j2.xml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>log4j2.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.lecturestudio.core</groupId>
<artifactId>lect-core</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client-microprofile</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-binding-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>${resteasy.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,224 @@
package moodle.sync.cli;
import static java.util.Objects.nonNull;
import moodle.sync.cli.inject.ApplicationModule;
import moodle.sync.core.model.json.Course;
import moodle.sync.core.model.json.MoodleUpload;
import moodle.sync.core.model.json.Section;
import moodle.sync.core.web.service.MoodleService;
import moodle.sync.core.web.service.MoodleUploadTemp;
import org.apache.commons.cli.*;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.inject.GuiceInjector;
import org.lecturestudio.core.inject.Injector;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Properties;
import javax.inject.Inject;
public class CommandLineInterface {
private final MoodleService moodleService;
private final Dictionary dictionary;
private String url;
private String token;
private int userId;
public static void main(String[] args) {
Injector injector = new GuiceInjector(new ApplicationModule());
CommandLineInterface cliTool = injector.getInstance(CommandLineInterface.class);
cliTool.execute(args);
}
@Inject
public CommandLineInterface(MoodleService moodleService, Dictionary dictionary) {
this.moodleService = moodleService;
this.dictionary = dictionary;
}
private void execute(String[] args) {
Options options = generateDefaultOptions();
try {
CommandLine cmd = parseCommandLine(options, args);
// Check for required parameters
checkParameters(cmd);
// Login to moodle
String loginConfigPath = cmd.getOptionValue("l");
login(loginConfigPath);
// Get the dedicated course
String courseId = cmd.getOptionValue("course");
Course course = getCourse(courseId);
// Get the dedicated section
String sectionId = cmd.getOptionValue("section");
Section section = getSection(course, sectionId);
// Get file path to upload
Path file = getFilePath(cmd.getOptionValue("path"));
// Execute file upload
upload(course, section, file);
} catch (Exception e) {
if (nonNull(e.getMessage())) {
printError(e.getMessage());
}
printHelp();
}
}
private Options generateDefaultOptions() {
Options options = new Options();
options.addOption("c", "course", true, dictionary.get("cli.option.course"));
options.addOption("s", "section", true, dictionary.get("cli.option.section"));
options.addOption("p", "path", true, dictionary.get("cli.option.path"));
options.addOption("l", "login", true, dictionary.get("cli.option.login"));
return options;
}
private CommandLine parseCommandLine(Options options, String[] args) {
CommandLineParser parser = new DefaultParser();
try {
return parser.parse(options, args);
} catch (Exception e) {
// If parameter specified but no value given
throw new IllegalArgumentException(e.getMessage());
}
}
private void checkParameters(CommandLine cmd) {
if (!cmd.hasOption("c") || !cmd.hasOption("s") || !cmd.hasOption("p")) {
throw new IllegalArgumentException("cli.args.incomplete");
}
}
private Course getCourse(String courseId) {
Course course;
try {
List<Course> courses = moodleService.getEnrolledCourses(token, userId);
course = courses.stream()
.filter(c -> c.getId().toString().equals(courseId))
.findFirst().orElse(null);
} catch (Exception e) {
throw new IllegalArgumentException("cli.moodle.external.service.error");
}
if (course == null) {
throw new IllegalArgumentException("cli.moodle.course.invalid");
}
return course;
}
private Section getSection(Course course, String sectionId) {
try {
return moodleService.getCourseContentSection(token, course.getId(),
Integer.parseInt(sectionId)).get(0);
} catch (Exception e) {
throw new IllegalArgumentException("cli.moodle.section.invalid");
}
}
private Path getFilePath(String filePath) {
Path path = Path.of(filePath);
if (!Files.exists(path)) {
throw new IllegalArgumentException("cli.path.invalid");
}
return path;
}
private void login(String configPath) throws IllegalAccessException {
if (nonNull(configPath) && !configPath.isEmpty() && !configPath.isBlank()) {
// Parse config file to get credentials
Properties moodleProps = new Properties();
try {
moodleProps.load(new FileInputStream(Path.of(configPath).toFile()));
} catch (IOException e) {
throw new IllegalArgumentException("cli.login.properties.invalid");
}
url = moodleProps.getProperty("url");
token = moodleProps.getProperty("token");
} else {
// Read credentials from environment variables
url = System.getenv("MOODLE_SYNC_URL");
token = System.getenv("MOODLE_SYNC_TOKEN");
}
if (url == null || token == null) {
throw new IllegalArgumentException("cli.moodle.login.invalid");
}
moodleService.setApiUrl(url);
// Check if user exists
try {
userId = moodleService.getUserId(token);
} catch (Exception e) {
throw new IllegalAccessException("cli.moodle.connection.failed");
}
}
private void upload(Course course, Section section, Path file) throws IOException {
MoodleUploadTemp uploader = new MoodleUploadTemp();
MoodleUpload upload;
String fileName = file.getFileName().toString();
try {
upload = uploader.upload(fileName, file.toString(), url, token);
moodleService.setResource(token, course.getId(), section.getSection(),
upload.getItemid(), null, true, fileName, -1);
} catch (Exception e) {
throw new IOException("cli.moodle.upload.error", e);
}
}
private void printHelp() {
String prefix = dictionary.get("cli.usage");
String header = dictionary.get("cli.help.header");
String footer = dictionary.get("cli.help.footer");
HelpFormatter formatter = new HelpFormatter();
PrintWriter writer = new PrintWriter(new PrintStream(System.out, true,
StandardCharsets.UTF_16));
formatter.setSyntaxPrefix(prefix);
formatter.printHelp(writer, 80, "moodle-sync-cli", header,
generateDefaultOptions(), 3, 5, footer, true);
writer.flush();
}
private void printError(String error) {
System.err.println(dictionary.contains(error) ? dictionary.get(error) : error);
}
}

View file

@ -0,0 +1,57 @@
package moodle.sync.cli.inject;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import moodle.sync.core.web.service.MoodleService;
import org.lecturestudio.core.app.LocaleProvider;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.beans.StringProperty;
import org.lecturestudio.core.util.AggregateBundle;
import javax.inject.Singleton;
import java.util.Locale;
import java.util.ResourceBundle;
public class ApplicationModule extends AbstractModule {
@Provides
@Singleton
ResourceBundle createResourceBundle() throws Exception {
LocaleProvider localeProvider = new LocaleProvider();
Locale locale = localeProvider.getBestSupported(Locale.getDefault());
return new AggregateBundle(locale, "resources.i18n.core", "resources.i18n.dict", "resources.i18n.cli");
}
@Provides
@Singleton
AggregateBundle createAggregateBundle(ResourceBundle resourceBundle) {
return (AggregateBundle) resourceBundle;
}
@Provides
@Singleton
Dictionary provideDictionary(ResourceBundle resourceBundle) {
return new Dictionary() {
@Override
public String get(String key) throws NullPointerException {
return resourceBundle.getString(key);
}
@Override
public boolean contains(String key) {
return resourceBundle.containsKey(key);
}
};
}
@Provides
@Singleton
MoodleService createMoodleService(){
return new MoodleService(new StringProperty("http://localhost/"));
}
}

View file

@ -0,0 +1,20 @@
package moodle.sync.cli.log;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.log.Log4jXMLConfigurationFactory;
import org.lecturestudio.core.model.VersionInfo;
@Plugin(name = "Log4jConfigurationFactory", category = "ConfigurationFactory")
@Order(10)
public class Log4jConfigurationFactory extends Log4jXMLConfigurationFactory {
public Log4jConfigurationFactory() {
AppDataLocator dataLocator = new AppDataLocator("MoodleSyncCli");
System.setProperty("logAppVersion", VersionInfo.getAppVersion());
System.setProperty("logFilePath", dataLocator.getAppDataPath());
}
}

View file

@ -0,0 +1,17 @@
cli.usage = Verwendung:
cli.args.incomplete = \u00Dcbergebene Parameter nicht vollst\u00E4ndig
cli.moodle.login.invalid= URl und/oder Zugriffstoken nicht angegeben
cli.moodle.connection.failed = Verbindungsaufbau zur Moodle-Plattform fehlgeschlagen
cli.moodle.external.service.error = Nutzung des Externen Services fehlgeschlagen. Bitte Berechtigungen \u00FCberpr\u00FCfen
cli.moodle.course.invalid = Kurs nicht gefunden, bitte id \u00FCberpr\u00FCfen
cli.moodle.section.invalid = Sektion nicht gefunden, bitte Sektions-Nummer \u00FCberpr\u00FCfen
cli.moodle.upload.error = Hochladen und ver\u00F6ffentlichten der Datei fehlgeschlagen
cli.path.invalid = Dateipfad zur hochzuladenden Datei ung\u00FCltig
cli.option.course = ID des Kurses (pflicht)
cli.option.section = ID der Sektion (pflicht)
cli.option.path = Pfad zur hochzuladenden Datei (pflicht)
cli.option.login = Pfad zur Konfigurationsdatei mit Login-Daten
cli.login.properties.missing = Konfigurationsdatei nicht gefunden
cli.login.properties.invalid = Konfigurationsdatei fehlerbehaftet
cli.help.header = Folgende Parameter sind zu \u00FCbergeben
cli.help.footer = \nWenn Sie einen Beitrag leisten oder ein Problem melden m\u00F6chten, besuchen Sie GitHub: https://github.com/lectureStudio/MoodleSync

View file

@ -0,0 +1,17 @@
cli.usage = Usage:
cli.args.incomplete = Params not complete
cli.moodle.login.invalid = URL and/or access token are missing
cli.moodle.connection.failed = Connecting to moodle failed
cli.moodle.external.service.error = Using the external service failed, please check your permissions
cli.moodle.course.invalid = Course not found, please check id
cli.moodle.section.invalid = Section not found, please check section id
cli.moodle.upload.error = Error uploading and publishing the file
cli.path.invalid = Path to file you want to upload is invalid
cli.option.course = ID of the course (mandatory)
cli.option.section = ID of the section (mandatory)
cli.option.path = Path to the file to upload (mandatory)
cli.option.login = Path to configuration file with login information
cli.login.properties.missing = Configuration file not found
cli.login.properties.invalid = Configuration file is faulty
cli.help.header = The following parameters are required:
cli.help.footer = \nIf you would like to contribute or report an issue, go to GitHub: https://github.com/lectureStudio/MoodleSync

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="filename">moodle-sync</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p %c{1}:%L - %m%n" />
</Console>
<RollingFile
name="FILE"
fileName="${sys:logFilePath}/${filename}-${sys:logAppVersion}.log"
filePattern="${sys:logFilePath}/${filename}-${sys:logAppVersion}-%d{MM-dd-yyyy}-%i.log"
immediateFlush="true">
<PatternLayout pattern="%d %-5p %c{1}:%L - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy>
<Delete basePath="${sys:logFilePath}" maxDepth="1">
<IfFileName glob="${filename}-*.log"/>
<IfAccumulatedFileCount exceeds="3"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="moodle.sync.core.web.json" level="debug" />
<Logger name="org.lecturestudio" level="error" />
<Logger name="org.jboss" level="error" />
<Root level="info">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="FILE" />
</Root>
</Loggers>
</Configuration>

148
moodle-sync-core/pom.xml Normal file
View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.lecturestudio.core</groupId>
<artifactId>lect-core</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.rest.client</groupId>
<artifactId>microprofile-rest-client-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client-microprofile</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-binding-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>${resteasy.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-core</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>18.0.1</javafx.version>
<jackson.version>2.13.3</jackson.version>
<log4j.version>2.17.2</log4j.version>
<resteasy.version>4.7.5.Final</resteasy.version>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>resources</targetPath>
<filtering>false</filtering>
<excludes>
<exclude>log4j2.xml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>log4j2.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>

View file

@ -0,0 +1,24 @@
package moodle.sync.core.config;
import java.util.Locale;
/**
* This Class offers the possibility to generate a default configuration.
*/
public class DefaultConfiguration extends MoodleSyncConfiguration {
public DefaultConfiguration() {
setApplicationName("MoodleSync");
setLocale(Locale.getDefault());
setCheckNewVersion(true);
setUIControlSize(9);
setStartMaximized(false);
setAdvancedUIMode(false);
setSyncRootPath(System.getProperty("user.dir"));
setFormatsFileserver("");
setFormatsMoodle("pdf,png,pptx,docx");
setPortFileserver("21");
setMoodleUrl("https://localhost");
}
}

View file

@ -0,0 +1,228 @@
package moodle.sync.core.config;
import moodle.sync.core.model.json.Course;
import org.lecturestudio.core.beans.BooleanProperty;
import moodle.sync.core.model.json.Section;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.beans.StringProperty;
import java.util.Locale;
/**
* This class represents a configuration containing several settings.
*/
public class MoodleSyncConfiguration extends Configuration {
//The path where the synchronized files are stored at.
private final StringProperty syncRootPath = new StringProperty();
//The previously selected Course.
private final ObjectProperty<Course> recentCourse = new ObjectProperty<>();
//The users Moodle-token.
private final StringProperty moodleToken = new StringProperty();
//The previously selected Section.
private final ObjectProperty<Section> recentSection = new ObjectProperty<>();
//The Url of the Moodle-Platform.
private final StringProperty moodleUrl = new StringProperty();
//If a file belongs to this format, it should be synchronized with the Moodle-plattform.
private final StringProperty formatsMoodle = new StringProperty();
//If a file belongs to this format, it should be synchronized with the fileserver.
private final StringProperty formatsFileserver = new StringProperty();
//The Url of the fileserver.
private final StringProperty ftpserver = new StringProperty();
//The users fileserver-username.
private final StringProperty ftpuser = new StringProperty();
//The users fileserver-password.
private final StringProperty ftppassword = new StringProperty();
//The choosen port for the fileserver-communication.
private final StringProperty ftpport = new StringProperty();
//Whether files of unknown fileformat should be displayed.
private final BooleanProperty showUnknownFormats = new BooleanProperty();
//Language
private final ObjectProperty<Locale> locale = new ObjectProperty();
//Delete file property - still in work
private final BooleanProperty executeDeletion = new BooleanProperty();
public String getSyncRootPath() {
return syncRootPath.get();
}
public void setSyncRootPath(String path) {
this.syncRootPath.set(path);
}
public StringProperty syncRootPathProperty() {
return syncRootPath;
}
public Course getRecentCourse() {
return recentCourse.get();
}
public void setRecentCourse(Course course) {
this.recentCourse.set(course);
}
public ObjectProperty<Course> recentCourseProperty() {
return recentCourse;
}
public String getMoodleToken() {
return moodleToken.get();
}
public void setMoodleToken(String token) {
this.moodleToken.set(token);
}
public StringProperty moodleTokenProperty() {
return moodleToken;
}
public Section getRecentSection() {
return recentSection.get();
}
public void setRecentSection(Section section) {
this.recentSection.set(section);
}
public ObjectProperty<Section> recentSectionProperty() {
return recentSection;
}
public String getMoodleUrl() {
return moodleUrl.get();
}
public void setMoodleUrl(String url) {
this.moodleUrl.set(url);
}
public StringProperty moodleUrlProperty() {
return moodleUrl;
}
public String getFormatsMoodle() {
return formatsMoodle.get();
}
public void setFormatsMoodle(String formats) {
this.formatsMoodle.set(formats);
}
public StringProperty formatsMoodleProperty() {
return formatsMoodle;
}
public String getFormatsFileserver() {
return formatsFileserver.get();
}
public void setFormatsFileserver(String formats) {
this.formatsFileserver.set(formats);
}
public StringProperty formatsFileserverProperty() {
return formatsFileserver;
}
public String getFileserver() {
return ftpserver.get();
}
public void setFileserver(String fileserver) {
this.ftpserver.set(fileserver);
}
public StringProperty FileserverProperty() {
return ftpserver;
}
public String getUserFileserver() {
return ftpuser.get();
}
public void setUserFileserver(String user) {
this.ftpuser.set(user);
}
public StringProperty userFileserverProperty() {
return ftpuser;
}
public String getPasswordFileserver() {
return ftppassword.get();
}
public void setPasswordFileserver(String formats) {
this.ftppassword.set(formats);
}
public StringProperty passwordFileserverProperty() {
return ftppassword;
}
public String getPortFileserver() {
return ftpport.get();
}
public void setPortFileserver(String port) {
this.ftpport.set(port);
}
public StringProperty portFileserverProperty() {
return ftpport;
}
public Boolean getShowUnknownFormats() {
return showUnknownFormats.get();
}
public void setShowUnknownFormats(Boolean unknownFormats) {
this.showUnknownFormats.set(unknownFormats);
}
public BooleanProperty showUnknownFormatsProperty() {
return showUnknownFormats;
}
public Locale getLocale() {
return (Locale)this.locale.get();
}
public void setLocale(Locale locale) {
this.locale.set(locale);
}
public ObjectProperty<Locale> localeProperty() {
return this.locale;
}
public Boolean getExecuteDeletion() {
return executeDeletion.get();
}
public void setExecuteDeletion(Boolean executeDeletion) {
this.executeDeletion.set(executeDeletion);
}
public BooleanProperty executeDeletionProperty() {
return executeDeletion;
}
}

View file

@ -0,0 +1,34 @@
package moodle.sync.core.context;
import java.io.File;
import javax.inject.Inject;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.app.configuration.ConfigurationService;
import org.lecturestudio.core.app.configuration.JsonConfigurationService;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.bus.EventBus;
public class MoodleSyncContext extends ApplicationContext {
private final File configFile;
@Inject
public MoodleSyncContext(AppDataLocator dataLocator, File configFile,
Configuration config, Dictionary dict, EventBus eventBus,
EventBus audioBus) {
super(dataLocator, config, dict, eventBus, audioBus);
this.configFile = configFile;
}
@Override
public void saveConfiguration() throws Exception {
ConfigurationService<Configuration> configService = new JsonConfigurationService<>();
configService.save(configFile, getConfiguration());
}
}

View file

@ -0,0 +1,23 @@
package moodle.sync.core.fileserver;
import java.util.List;
/**
* Interface declaring all needed methods for fileserver support.
*
* @author Daniel Schröter
*/
public interface FileServerClient {
//Retrieve list of FileServerFiles from dedicated directory
List<FileServerFile> getFiles(String pathname) throws Exception;
// String uploadFile(syncTableElement item, String pathname);
//Disconnect from fileserver
void disconnect();
//Connect to fileserver
void connect();
}

View file

@ -0,0 +1,115 @@
package moodle.sync.core.fileserver;
import moodle.sync.core.config.MoodleSyncConfiguration;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Class implementing the FileServerClient-interface using the ftp-protocol.
*
* @author Daniel Schröter
*/
public class FileServerClientFTP implements FileServerClient {
//Used FTPClient for communication.
private final FTPClient ftpClient;
//Configuration providing information about url etc.
private final MoodleSyncConfiguration config;
public FileServerClientFTP(MoodleSyncConfiguration config) {
ftpClient = new FTPClient();
ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
this.config = config;
}
/**
* Establishes the connection with a fileserver.
*/
@Override
public void connect() {
try {
ftpClient.connect(config.getFileserver(), Integer.parseInt(config.getPortFileserver()));
int reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect();
throw new IOException("Exception in connecting to FTP Server");
}
ftpClient.login(config.getUserFileserver(), config.getPasswordFileserver());
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
}
catch (IOException e) {
e.printStackTrace();
}
}
/**
* Terminates the connection with a fileserver.
*/
@Override
public void disconnect() {
try {
ftpClient.disconnect();
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Method to gather information about uploaded files.
*
* @param pathname Path to the directory at the ftpserver.
* @return a list containing elements of FileServerFile.
*/
@Override
public List<FileServerFile> getFiles(String pathname) throws Exception {
List<FileServerFile> files = new ArrayList<>();
try {
FTPFile[] ftpFiles = ftpClient.listFiles(pathname);
for (FTPFile item : ftpFiles) {
files.add(new FileServerFile(item.getName(), item.getTimestamp().getTimeInMillis()));
}
}
catch (Exception e) {
throw new Exception();
}
return files;
}
/**
* Method to upload a file to a ftpserver.
*
* @param item UploadElement, containing the local path to the file.
* @param pathname Dedicated directory at the ftpserver.
* @return the url of the uploaded file.
*/
// @Override
// public String uploadFile(syncTableElement item, String pathname) {
// //Evtl noch pathname einbringen
// String url = null;
// try {
// InputStream file = Files.newInputStream(Paths.get(item.getExistingFile()));
// ftpClient.storeFile("/" /*+ config.getRecentSection().getName() + "/" */ + item.getExistingFileName(), file);
// //ToDo add functionality Url
// url = config.getFileserver() + "/" /*+ config.getRecentSection().getName() + "/" */ + item.getExistingFileName();
// file.close();
// } catch (Exception e) {
// e.printStackTrace();
// }
// return url;
// }
}

View file

@ -0,0 +1,24 @@
package moodle.sync.core.fileserver;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
/**
* Class representing a file uploaded to a fileserver.
*
* @author Daniel Schröter
*/
public class FileServerFile {
private String filename;
private Long lastTimeModified;
}

View file

@ -0,0 +1,21 @@
package moodle.sync.core.model.json;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
/**
* Class representing a course-modules content.
*
* @author Daniel Schröter
*/
public class Content {
private String filename;
private Long timemodified;
}

View file

@ -0,0 +1,42 @@
package moodle.sync.core.model.json;
import lombok.*;
import java.util.Objects;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
/**
* Class representing a Moodle-Course.
*
* @author Daniel Schröter
*/
public class Course {
private Integer id;
private String shortname;
private String displayname;
private String idnumber ;
private Integer visible;
private Integer enddate;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Course course = (Course) o;
return Objects.equals(id, course.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return shortname;
}
}

View file

@ -0,0 +1,82 @@
package moodle.sync.core.model.json;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.json.JsonObject;
import javax.json.bind.serializer.DeserializationContext;
import javax.json.bind.serializer.JsonbDeserializer;
import javax.json.stream.JsonParser;
import java.lang.reflect.Type;
/**
* Class which helps deserialize and debug a course
*/
public class CourseDeserializer implements JsonbDeserializer<Course> {
private static final Logger LOG = LogManager.getLogger(CourseDeserializer.class);
@Override
public Course deserialize(JsonParser parser,
DeserializationContext deserializationContext, Type type) {
JsonObject jsonObj = parser.getObject();
System.out.println("-----------");
System.out.println(jsonObj.toString());
LOG.debug(jsonObj.toString());
Integer id = null;
String shortname = null;
String displayname = null;
String idnumber = null;
Integer visible = null;
Integer enddate = null;
if (jsonObj.containsKey("id")) {
id = jsonObj.getInt("id");
}
if (jsonObj.containsKey("shortname")) {
shortname = jsonObj.getString("shortname");
}
if (jsonObj.containsKey("displayname")) {
displayname = jsonObj.getString("displayname");
}
if (jsonObj.containsKey("idnumber")) {
idnumber = jsonObj.getString("idnumber");
}
if (jsonObj.containsKey("visible")) {
visible = jsonObj.getInt("visible");
}
if (jsonObj.containsKey("enddate")) {
try{
String temp = jsonObj.getJsonNumber("enddate").toString();
System.out.println("Enddate: " + temp);
LOG.debug("Enddate: " + temp);
enddate = Integer.parseInt(temp);
}catch (Throwable e){
enddate = 0;
System.out.println("Failed to parse enddate");
LOG.error("Failed to parse enddate");
}
} else{
enddate = 0;
}
Course course = new Course();
course.setId(id);
course.setShortname(shortname);
course.setDisplayname(displayname);
course.setIdnumber(idnumber);
course.setVisible(visible);
course.setEnddate(enddate);
System.out.println("-----------");
System.out.println(course);
return course;
}
}

View file

@ -0,0 +1,38 @@
package moodle.sync.core.model.json;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyVisibilityStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
@Provider
public class JsonConfigProvider implements ContextResolver<Jsonb> {
@Override
public Jsonb getContext(Class<?> aClass) {
return JsonbBuilder.create(createConfig());
}
public static JsonbConfig createConfig() {
return new JsonbConfig()
.withNullValues(true)
.withDeserializers(new CourseDeserializer())
.withPropertyVisibilityStrategy(new PropertyVisibilityStrategy() {
@Override
public boolean isVisible(Method method) {
return false;
}
@Override
public boolean isVisible(Field field) {
return true;
}
});
}
}

View file

@ -0,0 +1,33 @@
package moodle.sync.core.model.json;
import lombok.*;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
/**
* Class representing a course-module.
*
* @author Daniel Schröter
*/
public class Module {
private Integer id;
private String url;
private String name;
private Integer instance;
private Integer contextid;
private Integer visible;
private String modname;
private String availability;
private List<Content> contents;
public Module(List<Content> contents){
this.contents = contents;
}
}

View file

@ -0,0 +1,38 @@
package moodle.sync.core.model.json;
import com.fasterxml.jackson.annotation.JsonRawValue;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ModuleAvailability {
@JsonRawValue
private TimeDateCondition[] c;
@JsonRawValue
private Boolean[] showc;
public TimeDateCondition getTimeDateCondition(){
return c != null ? c[0] : null;
}
public Boolean getConditionVisibility(){
return showc != null ? showc[0] : null;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public static class TimeDateCondition {
String type;
String d; //Duration
Long t;
}
}

View file

@ -0,0 +1,24 @@
package moodle.sync.core.model.json;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
/**
* Class representing the response-object of a file upload.
*
* @author Daniel Schröter
*/
public class MoodleUpload {
private String filename;
private Long itemid;
}

View file

@ -0,0 +1,50 @@
package moodle.sync.core.model.json;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
/**
* Class representing a course-section.
*
* @author Daniel Schröter
*/
public class Section {
private Integer id;
private String name;
private Integer visible;
private String summary;
private Integer summaryformat;
private Integer section;
private Integer hiddenbynumsections;
private Boolean uservisible;
private List<Module> modules;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Section section = (Section) o;
return Objects.equals(id, section.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return name;
}
}

View file

@ -0,0 +1,20 @@
package moodle.sync.core.model.json;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
/**
* Class representing the necessary parameters of the http-response to the api-call: getSiteInfo.
*
* @author Daniel Schröter
*/
public class SiteInfo {
private int userid;
}

View file

@ -0,0 +1,21 @@
package moodle.sync.core.util.FileWatcherService;
import java.io.File;
import java.util.EventObject;
/**
*
* Class representing an event regarding a local directory.
*
*/
public class FileEvent extends EventObject {
public FileEvent(File file) {
super(file);
}
public File getFile() {
return (File) getSource();
}
}

View file

@ -0,0 +1,22 @@
package moodle.sync.core.util.FileWatcherService;
import java.util.EventListener;
/**
* Interface declaring the possible changes regarding a local directory.
*/
public interface FileListener extends EventListener {
default void onCreated(FileEvent event) {
System.out.println("Created" + event.getFile().toString());
event.getFile();
}
default void onModified(FileEvent event) {
System.out.println("Changed");
}
default void onDeleted(FileEvent event) {
System.out.println("Deleted");
}
}

View file

@ -0,0 +1,116 @@
package moodle.sync.core.util.FileWatcherService;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Class which monitors a directory for changes.
*/
public class FileWatcher implements Runnable {
protected List<FileListener> listeners = new ArrayList<>();
protected final File folder;
protected static final List<WatchService> watchServices = new ArrayList<>();
public FileWatcher(File folder) {
this.folder = folder;
}
public void watch() {
if (folder.exists()) {
Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();
}
}
public void close() throws IOException {
for (int i = 0; i < watchServices.size(); i++) {
watchServices.get(i).close();
}
}
@Override
public void run() {
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
Path path = Paths.get(folder.getAbsolutePath());
path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
watchServices.add(watchService);
boolean poll = true;
while (poll) {
poll = pollEvents(watchService);
}
} catch (IOException | InterruptedException | ClosedWatchServiceException e) {
Thread.currentThread().interrupt();
}
}
protected boolean pollEvents(WatchService watchService) throws InterruptedException {
WatchKey key = watchService.take();
Path path = (Path) key.watchable();
for (WatchEvent<?> event : key.pollEvents()) {
notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile());
}
return key.reset();
}
protected void notifyListeners(WatchEvent.Kind<?> kind, File file) {
FileEvent event = new FileEvent(file);
if (kind == ENTRY_CREATE) {
for (FileListener listener : listeners) {
listener.onCreated(event);
}
if (file.isDirectory()) {
new FileWatcher(file).setListeners(listeners).watch();
}
} else if (kind == ENTRY_MODIFY) {
for (FileListener listener : listeners) {
listener.onModified(event);
}
} else if (kind == ENTRY_DELETE) {
for (FileListener listener : listeners) {
listener.onDeleted(event);
}
}
}
public FileWatcher addListener(FileListener listener) {
listeners.add(listener);
return this;
}
public FileWatcher removeListener(FileListener listener) {
listeners.remove(listener);
return this;
}
public List<FileListener> getListeners() {
return listeners;
}
public FileWatcher setListeners(List<FileListener> listeners) {
this.listeners = listeners;
return this;
}
public static List<WatchService> getWatchServices() {
return Collections.unmodifiableList(watchServices);
}
}

View file

@ -0,0 +1,27 @@
package moodle.sync.core.util;
/**
* Enumeration containing several entities describing actions to do with UploadData.
*
* @author Daniel Schröter
*/
public enum MoodleAction {
MoodleUpload("Upload file to moodle"),
MoodleSynchronize("Update file on moodle"),
FTPUpload("Upload file to fileserver"),
FTPSynchronize("Update file on fileserver"),
FTPLink("Link file to moodle"),
NotLocalFile("File not locally saved"),
ExistingFile("File is up to date"),
DatatypeNotKnown("Data-Format noch specified"),
ExistingSection("Exisiting section"),
UploadSection("Create a new section");
public final String message;
MoodleAction(String message) {
this.message = message;
}
}

View file

@ -0,0 +1,202 @@
package moodle.sync.core.web.client;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import moodle.sync.core.model.json.Course;
import moodle.sync.core.model.json.JsonConfigProvider;
import moodle.sync.core.model.json.Section;
import moodle.sync.core.model.json.SiteInfo;
import moodle.sync.core.web.filter.LoggingFilter;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.annotation.RegisterProviders;
import java.util.List;
/**
* Interface used to define https-calls executed and implemented by classes of the package MicroProfile Rest Client.
*
* @author Daniel Schröter
*/
@Path("/webservice/rest/server.php")
@RegisterProviders({@RegisterProvider(LoggingFilter.class), @RegisterProvider(JsonConfigProvider.class),})
public interface MoodleClient {
/**
* Obtain a users subscribed Moodle-courses.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param userid The users id.
* @return a list of moodle-courses.
*/
@POST
@Path("")
@Produces(MediaType.APPLICATION_JSON)
List<Course> getCourses(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function,
@QueryParam("userid") int userid);
/**
* Method used to provide a webservice info, including information about the user.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @return a object containing the userid.
*/
@GET
@Path("")
@Produces(MediaType.APPLICATION_JSON)
SiteInfo getSiteInfo(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function);
/**
* Receive a moodle-courses content.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @return a list of course-sections containing course-modules.
*/
@GET
@Path("")
@Produces(MediaType.APPLICATION_JSON)
List<Section> getCourseContent(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function,
@QueryParam("courseid") int courseid);
/**
* Method used to move a course-module to a specific position.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param cmid The course-modules id.
* @param sectionid The course-sections id.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist
* null, the course-module will be moved to the bottom of the course-section.
*/
@POST
@Path("")
void setMoveModule(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function,
@QueryParam("cmid") int cmid, @QueryParam("sectionid") int sectionid,
@QueryParam("beforemod") Integer beforemod);
/**
* Create a course-module of type "url".
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @param sectionnum The moodle-sections number in the moodle-course.
* @param urlname The displayname of the course-module.
* @param url The course-modules content.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist
* null, the course-module will be moved to the bottom of the course-section.
*/
@POST
@Path("")
void setUrl(@QueryParam("moodlewsrestformat") String moodlewsrestformat, @QueryParam("wstoken") String token,
@QueryParam("wsfunction") String function, @QueryParam("courseid") int courseid,
@QueryParam("sectionnum") int sectionnum, @QueryParam("urlname") String urlname,
@QueryParam("url") String url, @QueryParam("time") Long time, @QueryParam("visible") boolean visible,
@QueryParam("beforemod") Integer beforemod);
/**
* Create a course-module of type "resource".
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @param sectionnum The moodle-sections number in the moodle-course.
* @param itemid The id of the prior uploaded file, which should be presented by the course-module.
* @param displayname The displayname of the course-module.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist
* null, the course-module will be moved to the bottom of the course-section.
*/
@POST
@Path("")
void setResource(@QueryParam("moodlewsrestformat") String moodlewsrestformat, @QueryParam("wstoken") String token,
@QueryParam("wsfunction") String function, @QueryParam("courseid") int courseid,
@QueryParam("sectionnum") int sectionnum, @QueryParam("itemid") long itemid, @QueryParam("time") Long time,
@QueryParam("visible") boolean visible, @QueryParam("displayname") String displayname,
@QueryParam("beforemod") Integer beforemod);
/**
* Create a course-module of type "folder".
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @param sectionnum The moodle-sections number in the moodle-course.
* @param itemid The id of the prior uploaded files, which should be presented by the course-module.
* @param displayname The displayname of the course-module.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist
* null, the course-module will be moved to the bottom of the course-section.
*/
@POST
@Path("")
void setFolder(@QueryParam("moodlewsrestformat") String moodlewsrestformat, @QueryParam("wstoken") String token,
@QueryParam("wsfunction") String function, @QueryParam("courseid") int courseid,
@QueryParam("sectionnum") int sectionnum, @QueryParam("itemid") long itemid,
@QueryParam("displayname") String displayname, @QueryParam("beforemod") Integer beforemod);
/**
* Obtains the course-content of a specific course-section.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @param s Needed parameter for creating a JSON-Object.
* @param sectionid The course-sections id.
* @return a list containg one section.
*/
@POST
@Path("")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
List<Section> getCourseContentSection(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function,
@QueryParam("courseid") int courseid, @QueryParam("options[0][name]") String s,
@QueryParam("options[0][value]") int sectionid);
/**
* Method used to remove a course-module.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param cmid The course-modules id.
*/
@POST
@Path("")
void removeResource(@QueryParam("moodlewsrestformat") String moodlewsrestformat,
@QueryParam("wstoken") String token, @QueryParam("wsfunction") String function,
@QueryParam("cmids[0]") int cmid);
/**
* Method used to create a new course-section.
*
* @param moodlewsrestformat Used dataformat.
* @param token The Moodle-token.
* @param function The called Web Service API function.
* @param courseid The Moodle-courses id.
* @param sectionname The name of the section.
* @param sectionnum The moodle-sections number in the moodle-course.
*/
@GET
@Path("")
void setSection(@QueryParam("moodlewsrestformat") String moodlewsrestformat, @QueryParam("wstoken") String token,
@QueryParam("wsfunction") String function, @QueryParam("courseid") int courseid,
@QueryParam("sectionname") String sectionname, @QueryParam("sectionnum") int sectionnum);
}

View file

@ -0,0 +1,57 @@
package moodle.sync.core.web.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
/**
* Class which implements an Logging Filter for easy error analysis
*/
@Provider
public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter {
@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
StringBuilder builder = new StringBuilder("----- <Response> ------------\n");
builder.append(" Status: ").append(responseContext.getStatus()).append("\n");
if (MediaType.APPLICATION_JSON_TYPE.equals(responseContext.getMediaType())) {
byte[] content = responseContext.getEntityStream().readAllBytes();
String body = new String(content, StandardCharsets.UTF_8);
builder.append(" Body: " + body).append("\n");
if(body.contains("exception")){
throw new IOException("Fehlende Berechtigung");
}
responseContext.setEntityStream(new ByteArrayInputStream(content));
}
builder.append("----- </Response> ----------\n");
System.out.println(builder.toString());
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
StringBuilder builder = new StringBuilder("----- <Request> ------------\n");
builder.append(" Method: ").append(requestContext.getMethod()).append("\n");
builder.append(" URI: ").append(requestContext.getUri()).append("\n");
for(var header : requestContext.getHeaders().entrySet()){
builder.append(" Header: ").append(header.getKey()).append("\n");
builder.append(" Headervalue: ").append(header.getValue()).append("\n");
}
builder.append(" URI: ").append(requestContext.getEntity()).append("\n");
builder.append("----- </Request> ----------\n");
System.out.println(builder.toString());
}
}

View file

@ -0,0 +1,254 @@
package moodle.sync.core.web.service;
import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import moodle.sync.core.model.json.Course;
import moodle.sync.core.model.json.Section;
import moodle.sync.core.model.json.SiteInfo;
import moodle.sync.core.web.client.MoodleClient;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.lecturestudio.core.beans.StringProperty;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* Class which contains methods to instantiate the interface MoodleClient and to call methods defined in this interface.
*
* @author Daniel Schröter
*/
public class MoodleService {
private MoodleClient moodleClient;
/**
* Creates a new MoodleService.
*
* @param apiUrl The url of the Moodle-platform.
*/
public MoodleService(StringProperty apiUrl) {
setApiUrl(apiUrl.get());
}
/**
* Method which instantiates a MoodleClient.
*
* @param apiUrl The url of the Moodle-platform.
*/
public void setApiUrl(String apiUrl) {
//Parameter checks.
if (apiUrl == null || apiUrl.isEmpty() || apiUrl.isBlank()) {
return;
}
RestClientBuilder builder = RestClientBuilder.newBuilder();
builder.baseUri(URI.create(apiUrl));
//Usage of https.
if (apiUrl.startsWith("https")) {
builder.sslContext(createSSLContext());
builder.hostnameVerifier((hostname, sslSession) -> hostname.equalsIgnoreCase(sslSession.getPeerHost()));
}
//MoodleClient is instantiated by classes of the MicroProfile Rest Client.
moodleClient = builder.build(MoodleClient.class);
}
/**
* Method used to get a users userid.
*
* @param token The Moodle-token.
* @return the userid as an int.
*/
public int getUserId(String token) {
SiteInfo info = moodleClient.getSiteInfo("json", token, "core_webservice_get_site_info");
return info.getUserid();
}
/**
* Method used to provide a users subscribed Moodle-courses.
*
* @param token The Moodle-token.
* @param userid A user's id.
* @return list of Moodle-Courses.
*/
public List<Course> getEnrolledCourses(String token, int userid) {
return moodleClient.getCourses("json", token, "core_enrol_get_users_courses", userid);
}
/**
* Obtain a specific Moodle-courses content.
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @return a list of course-sections, which contains the course-modules.
*/
public List<Section> getCourseContent(String token, int courseid) {
return moodleClient.getCourseContent("json", token, "core_course_get_contents", courseid);
}
/**
* Obtain the content of a specific Moodle-section.
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param sectionid The course-sections id.
* @return the course-section, which contains the course-modules.
*/
public List<Section> getCourseContentSection(String token, int courseid, int sectionid) {
return moodleClient.getCourseContentSection("json", token, "core_course_get_contents", courseid, "sectionid",
sectionid);
}
/**
* Move a course-module to a specific position in a Moodle-course.
*
* @param token The Moodle-token.
* @param cmid The course-modules id.
* @param sectionid The course-sections id.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist null,
* the course-module will be moved to the bottom of the course-section.
*/
public void setMoveModule(String token, int cmid, int sectionid, int beforemod) {
if (beforemod == -1) {
moodleClient.setMoveModule("json", token, "local_course_move_module_to_specific_position", cmid,
sectionid, null);
}
else {
moodleClient.setMoveModule("json", token, "local_course_move_module_to_specific_position", cmid,
sectionid, beforemod);
}
}
/**
* Create a course-module of the type "url".
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param section The moodle-sections number in the moodle-course.
* @param urlname The displayname of the course-module.
* @param url The course-modules content.
*/
public void setUrl(String token, int courseid, int section, String urlname, String url, Long time,
Boolean visible, int beforemod) {
if (beforemod == -1) {
moodleClient.setUrl("json", token, "local_course_add_new_course_module_url", courseid, section, urlname,
url, time, visible, null);
}
else {
moodleClient.setUrl("json", token, "local_course_add_new_course_module_url", courseid, section, urlname,
url, time, visible, beforemod);
}
}
/**
* Create a course-module of the type "resource" at a specific position inside a course-section.
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param section The moodle-sections number in the moodle-course.
* @param name The displayname of the course-module.
* @param itemid The id of the prior uploaded file, which should be presented by the course-module.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist null,
* the course-module will be moved to the bottom of the course-section.
*/
public void setResource(String token, int courseid, int section, Long itemid, Long time, Boolean visible,
String name, int beforemod) {
if (beforemod == -1) {
moodleClient.setResource("json", token, "local_course_add_new_course_module_resource", courseid, section,
itemid, time, visible, name, null);
}
else {
moodleClient.setResource("json", token, "local_course_add_new_course_module_resource", courseid, section,
itemid, time, visible, name, beforemod);
}
}
/**
* Method used for deleting an exisiting course-module
*
* @param token The Moodle-token.
* @param cmid The course-modules id.
*/
public void removeResource(String token, int cmid) {
moodleClient.removeResource("json", token, "core_course_delete_modules", cmid);
}
/**
* Create a course-module of the type "folder".
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param section The moodle-sections number in the moodle-course.
* @param itemid The id of the prior uploaded files, which should be presented by the course-module.
* @param name The displayname of the course-module.
*/
public void setFolder(String token, int courseid, int section, Long itemid, String name) {
moodleClient.setFolder("json", token, "local_course_add_new_course_module_directory", courseid, section,
itemid, name, null);
}
/**
* Create a course-module of the type "resource" at a specific position inside a course-section.
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param section The moodle-sections number in the moodle-course.
* @param itemid The id of the prior uploaded files, which should be presented by the course-module.
* @param name The displayname of the course-module.
* @param beforemod The course-module id of the course-module at the supposed position. If beforemod ist null,
* the course-module will be moved to the bottom of the course-section.
*/
public void setFolder(String token, int courseid, int section, Long itemid, String name, int beforemod) {
moodleClient.setFolder("json", token, "local_course_add_new_course_module_directory", courseid, section,
itemid, name, beforemod);
}
/**
* Method used to create a new course section.
*
* @param token The Moodle-token.
* @param courseid A Moodle-courses id.
* @param sectionname The name of the new section.
* @param sectionnum The moodle-sections number in the moodle-course.
*/
public void setSection(String token, int courseid, String sectionname, int sectionnum) {
moodleClient.setSection("json", token, "local_course_add_new_section", courseid, sectionname, sectionnum);
}
/**
* Method user for generating a needed SSLContext for https-communication
*
* @return SSLContext
*/
private static SSLContext createSSLContext() {
SSLContext sslContext;
try {
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new TrustManager[]{tm}, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
return sslContext;
}
}

View file

@ -0,0 +1,105 @@
package moodle.sync.core.web.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.NoArgsConstructor;
import moodle.sync.core.model.json.MoodleUpload;
import okhttp3.*;
import javax.net.ssl.*;
import java.io.File;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* This class implements an alternative way to execute http-requests with the help of the OkHttpClient and is used for file upload.
*
* @author Daniel Schöter
*/
@NoArgsConstructor
public class MoodleUploadTemp {
/**
* With the help of this method, http-requests to upload a file to Moodle are constructed and executed.
*
* @param name name of the file to upload
* @param pathname path of the file
* @param moodleUrl url of the Moodle platform
* @param token Moodle-token
* @return MoodleUpload: Object which contains the filename and an itemid which is needed to identify the file
*/
public MoodleUpload upload(String name, String pathname, String moodleUrl, String token) {
try {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//Usage of https
if (moodleUrl.startsWith("https")) {
X509TrustManager trustManager;
SSLSocketFactory sslSocketFactory;
try {
trustManager = createTrustManager();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
builder.sslSocketFactory(sslSocketFactory, trustManager);
builder.hostnameVerifier((hostname, sslSession) -> hostname
.equalsIgnoreCase(sslSession.getPeerHost()));
}
//Execution of the http-request
OkHttpClient client = builder.build();
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
.addFormDataPart(name, pathname,
RequestBody.create(MediaType.parse("application/octet-stream"),
new File(pathname)))
.build();
Request request = new Request.Builder()
.url(moodleUrl + "/webservice/upload.php?token=" + token)
.method("POST", body)
.build();
ResponseBody response = client.newCall(request).execute().body();
String bodystring = response.string();
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("--------------------------------------" + bodystring);
List<MoodleUpload> entity = objectMapper.readValue(bodystring, new TypeReference<List<MoodleUpload>>() {
});
System.out.println(entity);
return entity.get(0);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Method user for generating a needed X509TrustManager for https-communication
*
* @return X509TrustManager
*/
private static X509TrustManager createTrustManager() {
X509TrustManager tm;
try {
tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
} catch (Exception e) {
throw new RuntimeException(e);
}
return tm;
}
}

Binary file not shown.

237
moodle-sync-fx/pom.xml Normal file
View file

@ -0,0 +1,237 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-fx</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>18.0.1</javafx.version>
<jackson.version>2.13.3</jackson.version>
<log4j.version>2.17.2</log4j.version>
<resteasy.version>4.7.5.Final</resteasy.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<classifier>${envClassifier}</classifier>
<archive>
<manifest>
<mainClass>moodle.sync.javafx.SyncApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<classpathLayoutType>custom</classpathLayoutType>
<customClasspathLayout>${artifact.artifactId}.${artifact.extension}</customClasspathLayout>
</manifest>
<manifestEntries>
<Class-Path>lib/javafx-controls-win.jar lib/javafx-graphics-win.jar lib/javafx-base-win.jar lib/javafx-fxml-win.jar lib/javafx-swing-win.jar lib/javafx-media-win.jar</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>resources</targetPath>
<filtering>false</filtering>
<excludes>
<exclude>log4j2.xml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>log4j2.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>17.0.2</version>
</dependency>
<dependency>
<groupId>no.tornado</groupId>
<artifactId>tornadofx-controls</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>org.lecturestudio.core</groupId>
<artifactId>lect-core</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.lecturestudio.javafx</groupId>
<artifactId>lect-javafx</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.lecturestudio.web.api</groupId>
<artifactId>lect-web-api</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.1.2</version>
</dependency>
<dependency>
<groupId>com.dlsc.gemsfx</groupId>
<artifactId>gemsfx</artifactId>
<version>1.47.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Used to compare app/release versions -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.8.5</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client-microprofile</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-binding-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>${resteasy.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,32 @@
package moodle.sync.input;
import org.lecturestudio.core.input.KeyCode;
import org.lecturestudio.core.input.KeyEvent;
public enum Shortcut {
APP_CLOSE(KeyCode.Q, KeyEvent.CTRL_MASK),
CLOSE_VIEW(KeyCode.ESCAPE);
private final KeyEvent keyEvent;
Shortcut(KeyCode code) {
this.keyEvent = new KeyEvent(code.getCode());
}
Shortcut(KeyCode code, int modifiers) {
this.keyEvent = new KeyEvent(code.getCode(), modifiers);
}
public KeyEvent getKeyEvent() {
return keyEvent;
}
public boolean match(KeyEvent event) {
return keyEvent.equals(event);
}
}

View file

@ -0,0 +1,22 @@
package moodle.sync.javafx;
import org.lecturestudio.core.app.ApplicationFactory;
import org.lecturestudio.javafx.app.JavaFxApplication;
public class SyncApplication extends JavaFxApplication {
/**
* The entry point of the application. This method calls the static
* {@link #launch(String[])} method to fire up the application.
*
* @param args the main method's arguments.
*/
public static void main(String[] args) {
SyncApplication.launch(args);
}
@Override
public ApplicationFactory createApplicationFactory() {
return new SyncFxFactory();
}
}

View file

@ -0,0 +1,30 @@
package moodle.sync.javafx;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.ApplicationFactory;
import org.lecturestudio.core.inject.GuiceInjector;
import org.lecturestudio.core.inject.Injector;
import moodle.sync.javafx.inject.ApplicationModule;
import moodle.sync.javafx.inject.ViewModule;
import moodle.sync.presenter.MainPresenter;
public class SyncFxFactory implements ApplicationFactory {
private final Injector injector;
public SyncFxFactory() {
injector = new GuiceInjector(new ApplicationModule(), new ViewModule());
}
@Override
public ApplicationContext getApplicationContext() {
return injector.getInstance(ApplicationContext.class);
}
@Override
public org.lecturestudio.core.presenter.MainPresenter<?> getStartPresenter() {
return injector.getInstance(MainPresenter.class);
}
}

View file

@ -0,0 +1,23 @@
package moodle.sync.javafx.custom;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class implementing a Checkbox as the content of a TableCell.
*
* @author Daniel Schröter
*/
public class AvailabilityCellFactory implements Callback<TableColumn<syncTableElement, Boolean>, TableCell<syncTableElement, Boolean>> {
@Override
public TableCell<syncTableElement, Boolean> call(TableColumn<syncTableElement, Boolean> p) {
AvailabilityCheckBoxCell <syncTableElement, Boolean> cell = new AvailabilityCheckBoxCell<syncTableElement, Boolean>();
cell.setAlignment(Pos.CENTER);
cell.setStyle("-fx-alignment: CENTER;");
return cell;
}
}

View file

@ -0,0 +1,32 @@
package moodle.sync.javafx.custom;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used for determining the state of a CheckBox inside the "sync-page"-table.
*
* @author Daniel Schröter
*/
public class AvailabilityCellValueFactory implements Callback<TableColumn.CellDataFeatures<syncTableElement,Boolean>, ObservableValue<Boolean>> {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<syncTableElement, Boolean> param)
{
syncTableElement elem = param.getValue();
//selectedProperty should be used to determine the state.
param.getValue().visibleProperty();
SimpleBooleanProperty booleanProp= (SimpleBooleanProperty) elem.visibleProperty();
booleanProp.addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
elem.setVisible(newValue);
}
});
return booleanProp;
}
}

View file

@ -0,0 +1,79 @@
package moodle.sync.javafx.custom;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.util.StringConverter;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used to display the selctedProperty-value inside a CheckBoxTreeTableCell.
*
* @author Daniel Schröter
*/
public class AvailabilityCheckBoxCell<U, B> extends CheckBoxTableCell<syncTableElement, Boolean> {
private CheckBox checkBox;
private boolean showLabel;
private ObservableValue<Boolean> booleanProperty;
@Override
public void updateItem(Boolean item, boolean empty) {
this.checkBox = new CheckBox();
this.checkBox.setAlignment(Pos.CENTER);
setAlignment(Pos.CENTER);
setGraphic(checkBox);
super.updateItem(item, empty);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty) booleanProperty);
booleanProperty = null;
}
if (empty) {
checkBox.setAlignment(Pos.CENTER);
setText(null);
setGraphic(null);
} else if (getTableRow() != null) {
if (getTableRow().getItem() != null && (!getTableRow().getItem().isSelectable() ||
getTableRow().getItem().getAction() == MoodleAction.UploadSection)) {
checkBox.setAlignment(Pos.CENTER);
setDisable(false);
setGraphic(null);
}
} else {
StringConverter<Boolean> c = getConverter();
if (showLabel) {
checkBox.setAlignment(Pos.CENTER);
setText(c.toString(item));
}
setGraphic(checkBox);
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty) booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(getTableView().
editableProperty().and(getTableColumn().editableProperty()).and(editableProperty())));
}
checkBox.setAlignment(Pos.CENTER);
setAlignment(Pos.CENTER);
}
private ObservableValue<?> getSelectedProperty() {
return getSelectedStateCallback() != null ? getSelectedStateCallback().call(getIndex()) :
getTableColumn().getCellObservableValue(getIndex());
}
}

View file

@ -0,0 +1,24 @@
package moodle.sync.javafx.custom;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.TimeDateElement;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used for determining the date and time of a upload inside the "sync-page"-table.
*
* @author Daniel Schröter
*/
public class AvailableDateTimeTableCellFactory implements Callback<TableColumn<syncTableElement, TimeDateElement>, TableCell<syncTableElement, TimeDateElement>> {
@Override
public TableCell<syncTableElement, TimeDateElement> call(TableColumn<syncTableElement, TimeDateElement> p) {
LocalDateTimeCell<syncTableElement, TimeDateElement> cell = new LocalDateTimeCell<syncTableElement, TimeDateElement>();
cell.setAlignment(Pos.CENTER);
cell.setStyle("-fx-alignment: CENTER;");
return cell;
}
}

View file

@ -0,0 +1,23 @@
package moodle.sync.javafx.custom;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class implementing a Checkbox as the content of a TableCell.
*
* @author Daniel Schröter
*/
public class CheckBoxTableCellFactory implements Callback<TableColumn<syncTableElement, Boolean>, TableCell<syncTableElement, Boolean>> {
@Override
public TableCell<syncTableElement, Boolean> call(TableColumn<syncTableElement, Boolean> p) {
UploadCheckBoxCell<syncTableElement, Boolean> cell = new UploadCheckBoxCell<syncTableElement, Boolean>();
cell.setAlignment(Pos.CENTER);
cell.setStyle("-fx-alignment: CENTER;");
return cell;
}
}

View file

@ -0,0 +1,21 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import moodle.sync.core.model.json.Course;
/**
* Class implementing Courses as the content of a ComboBox.
*
* @author Daniel Schröter
*/
public class CourseCellFactory implements Callback<ListView<Course>, ListCell<Course>> {
@Override
public ListCell<Course> call(ListView<Course> param) {
return new CourseListCell();
}
}

View file

@ -0,0 +1,29 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.ListCell;
import moodle.sync.core.model.json.Course;
import static java.util.Objects.isNull;
/**
* Class used to display the name of a Course inside a ListCell.
*
* @author Daniel Schröter
*/
public class CourseListCell extends ListCell<Course> {
@Override
protected void updateItem(Course item, boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (isNull(item) || empty) {
setText("");
} else {
setText(item.getShortname());
}
}
}

View file

@ -0,0 +1,120 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.util.Callback;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class executing the drag and drop process within the sync-table
*/
public class DragAndDropRowFactory implements Callback<TableView<syncTableElement>, TableRow<syncTableElement>> {
private static final DataFormat SERIALIZED_MIME_TYPE = new DataFormat("application/x-java-serialized-object");
@Override
public TableRow<syncTableElement> call(TableView<syncTableElement> tableView) {
final TableRow<syncTableElement> row;
row = new TableRow<>();
row.setOnDragDetected(event -> {
/* drag was detected, start drag-and-drop gesture*/
if (!row.isEmpty()) {
Integer index = row.getIndex();
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, index);
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
/* data is dragged over the target */
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
if (row.getIndex() != ((Integer) db.getContent(SERIALIZED_MIME_TYPE)).intValue()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int draggedIndex = (Integer) db.getContent(SERIALIZED_MIME_TYPE);
syncTableElement draggedElement = tableView.getItems().remove(draggedIndex);
int dropIndex;
if (row.isEmpty()) {
dropIndex = tableView.getItems().size();
} else {
dropIndex = row.getIndex();
}
if (draggedElement.getAction() == MoodleAction.UploadSection) {
if (tableView.getItems().get(dropIndex).getAction() == MoodleAction.ExistingSection) {
draggedElement.setSection(tableView.getItems().get(dropIndex).getSection());
draggedElement.setBeforemod(tableView.getItems().get(dropIndex).getCmid());
tableView.getItems().add(dropIndex, draggedElement);
event.setDropCompleted(true);
tableView.getSelectionModel().select(dropIndex);
tableView.refresh();
} else {
tableView.getItems().add(draggedIndex, draggedElement);
event.setDropCompleted(true);
tableView.getSelectionModel().select(draggedIndex);
tableView.refresh();
}
} else if (draggedElement.getAction() == MoodleAction.ExistingSection) {
tableView.getItems().add(draggedIndex, draggedElement);
event.setDropCompleted(true);
tableView.getSelectionModel().select(draggedIndex);
tableView.refresh();
} else {
if (tableView.getItems().get(dropIndex).getAction() == MoodleAction.ExistingSection) {
draggedElement.setBeforemod(-1);
} else {
if (dropIndex != draggedElement.getOldPos()) {
draggedElement.setBeforemod(tableView.getItems().get(dropIndex).getCmid());
}
}
if (dropIndex != draggedElement.getOldPos() || draggedElement.getAction() == MoodleAction.MoodleUpload || draggedElement.getAction() == MoodleAction.FTPUpload) {
draggedElement.setSectionId(tableView.getItems().get(dropIndex - 1).getSectionId());
draggedElement.setSelectable(true);
} else {
draggedElement.setSelectable(false);
}
tableView.getItems().add(dropIndex, draggedElement);
event.setDropCompleted(true);
tableView.refresh();
tableView.getSelectionModel().select(dropIndex);
}
event.consume();
}
});
return row;
}
}

View file

@ -0,0 +1,20 @@
package moodle.sync.javafx.custom;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class implementing a text field with different styles as the content of a TableCell.
*/
public class HighlightSectionCellFactory implements Callback<TableColumn<syncTableElement, String>, TableCell<syncTableElement, String>> {
@Override
public TableCell<syncTableElement, String> call(TableColumn<syncTableElement, String> p) {
UploadHighlightTableCell<syncTableElement, String> cell = new UploadHighlightTableCell<syncTableElement, String>();
cell.setAlignment(Pos.CENTER);
return cell;
}
}

View file

@ -0,0 +1,56 @@
package moodle.sync.javafx.custom;
import com.dlsc.gemsfx.TimePicker;
import javafx.geometry.Pos;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TableCell;
import javafx.scene.layout.HBox;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.TimeDateElement;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used to display a time/date setter inside a TableCell.
*/
public class LocalDateTimeCell<S, U> extends TableCell<syncTableElement, TimeDateElement> {
private DatePicker datePicker;
private TimePicker timePicker;
@Override
public void updateItem(TimeDateElement item, boolean empty) {
this.datePicker = new DatePicker();
this.timePicker = new TimePicker();
setAlignment(Pos.CENTER);
datePicker.setMaxWidth(100);
timePicker.setMaxWidth(100);
HBox hbox = new HBox(datePicker, timePicker);
hbox.setAlignment(Pos.CENTER);
hbox.setSpacing(10);
setGraphic(hbox);
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (getTableRow() != null && getTableRow().getItem() != null) {
datePicker.valueProperty().unbindBidirectional(getTableRow().getItem().availabilityDateTimeProperty().get().LocalDateProperty());
timePicker.timeProperty().unbindBidirectional(getTableRow().getItem().availabilityDateTimeProperty().get().LocalTimeProperty());
if (getTableRow().getItem() != null && (!getTableRow().getItem().isSelectable() ||
getTableRow().getItem().getAction() == MoodleAction.UploadSection)) {
setDisable(false);
setGraphic(null);
}
else {
if (getTableRow().getItem() != null && getTableRow().getItem().isSelectable()) {
datePicker.valueProperty().bindBidirectional(getTableRow().getItem().availabilityDateTimeProperty().get().LocalDateProperty());
timePicker.timeProperty().bindBidirectional(getTableRow().getItem().availabilityDateTimeProperty().get().LocalTimeProperty());
}
}
}
}
}

View file

@ -0,0 +1,20 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import moodle.sync.core.model.json.Section;
/**
* Class implementing Sections as the content of a ComboBox.
*
* @author Daniel Schröter
*/
public class SectionCellFactory implements Callback<ListView<Section>, SectionListCell> {
@Override
public SectionListCell call(ListView<Section> param) {
return new SectionListCell();
}
}

View file

@ -0,0 +1,29 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.ListCell;
import moodle.sync.core.model.json.Section;
import static java.util.Objects.isNull;
/**
* Class used to display the name of a Section inside a ListCell.
*
* @author Daniel Schröter
*/
public class SectionListCell extends ListCell<Section> {
@Override
protected void updateItem(Section item, boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (isNull(item) || empty) {
setText("");
} else {
setText(item.getName());
}
}
}

View file

@ -0,0 +1,20 @@
package moodle.sync.javafx.custom;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used to change the background color of a TableCell.
*/
public class StatusCellFactory implements Callback<TableColumn<syncTableElement, String>, TableCell<syncTableElement, String>> {
@Override
public TableCell<syncTableElement, String> call(TableColumn<syncTableElement, String> p){
StatusTableCell<syncTableElement, String> cell = new StatusTableCell<>();
cell.setAlignment(Pos.CENTER);
return cell;
}
}

View file

@ -0,0 +1,55 @@
package moodle.sync.javafx.custom;
import javafx.scene.control.TableCell;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.syncTableElement;
import org.lecturestudio.javafx.control.SvgIcon;
/**
* Class used to change the background color of a TableCell.
*/
public class StatusTableCell <U, B> extends TableCell<syncTableElement, String> {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (empty || item == null || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
setStyle("-fx-background-color: TRANSPARENT");
} else {
if(getTableRow().getItem().getAction() == MoodleAction.MoodleSynchronize){
setText(item);
setStyle("-fx-background-color: PALEGREEN");
}
else if(getTableRow().getItem().getAction() == MoodleAction.MoodleUpload){
setText(item);
setStyle("-fx-background-color: SKYBLUE");
}
else if(getTableRow().getItem().getAction() == MoodleAction.FTPUpload){
SvgIcon icon = new SvgIcon();
icon.getStyleClass().add("ftp-icon");
setGraphic(icon);
setText(item);
setStyle("-fx-background-color: ORANGE");
}
else if(getTableRow().getItem().getAction() == MoodleAction.UploadSection){
setText(item);
setStyle("-fx-font-weight: bold");
}
else if(getTableRow().getItem().getAction() == MoodleAction.DatatypeNotKnown){
setText(item);
setStyle("-fx-font-weight: bold");
}
else{
setText(item);
setStyle("-fx-background-color: TRANSPARENT");
}
}
}
}

View file

@ -0,0 +1,83 @@
package moodle.sync.javafx.custom;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.util.StringConverter;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used to display the selctedProperty-value inside a CheckBoxTreeTableCell.
*
* @author Daniel Schröter
*/
public class UploadCheckBoxCell<U, B> extends CheckBoxTableCell<syncTableElement, Boolean> {
private CheckBox checkBox;
private boolean showLabel;
private ObservableValue<Boolean> booleanProperty;
@Override
public void updateItem(Boolean item, boolean empty) {
this.checkBox = new CheckBox();
this.checkBox.setAlignment(Pos.CENTER);
setAlignment(Pos.CENTER);
setGraphic(checkBox);
super.updateItem(item, empty);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty) booleanProperty);
booleanProperty = null;
}
if (empty) {
checkBox.setAlignment(Pos.CENTER);
setText(null);
setGraphic(null);
} else if (getTableRow() != null) {
if(getTableRow().getItem() != null && !getTableRow().getItem().isSelectable()) {
checkBox.setAlignment(Pos.CENTER);
setDisable(false);
setGraphic(null);
}
} else {
StringConverter<Boolean> c = getConverter();
if (showLabel) {
checkBox.setAlignment(Pos.CENTER);
setText(c.toString(item));
}
setGraphic(checkBox);
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty) booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
}
checkBox.setAlignment(Pos.CENTER);
setAlignment(Pos.CENTER);
}
private ObservableValue<?> getSelectedProperty() {
return getSelectedStateCallback() != null ?
getSelectedStateCallback().call(getIndex()) :
getTableColumn().getCellObservableValue(getIndex());
}
}

View file

@ -0,0 +1,36 @@
package moodle.sync.javafx.custom;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import moodle.sync.javafx.model.syncTableElement;
/**
* Class used for determining the state of a CheckBox inside the "sync-page"-table.
*
* @author Daniel Schröter
*/
public class UploadElementCellValueFactory implements Callback<TableColumn.CellDataFeatures<syncTableElement,Boolean>, ObservableValue<Boolean>> {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<syncTableElement, Boolean> param)
{
syncTableElement elem = param.getValue();
//selectedProperty should be used to determine the state.
param.getValue().selectedProperty();
SimpleBooleanProperty booleanProp= (SimpleBooleanProperty) elem.selectedProperty();
booleanProp.addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
elem.setSelected(newValue);
}
});
return booleanProp;
}
}

View file

@ -0,0 +1,121 @@
package moodle.sync.javafx.custom;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.syncTableElement;
import org.controlsfx.control.PopOver;
import org.lecturestudio.javafx.control.SvgIcon;
/**
* Class used to display the Name of a Section/ Module including different styles/background colors inside a cell.
*/
public class UploadHighlightTableCell <U, B> extends TableCell<syncTableElement, String> {
private Listener listener = new Listener();
private PopOver popOver;
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(getTableRow().getItem() != null) getTableRow().getItem().selectedProperty().removeListener(listener);
if(popOver != null){
popOver = null;
this.setOnMouseEntered(mouseEvent -> {
});
this.setOnMouseExited(mouseEvent -> {
});
}
setGraphic(null);
setVisible(true);
getTableRow().getStyleClass().remove("headerstyle");
if (empty || item == null || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
setEditable(false);
} else if(getTableRow().getItem().getAction() == MoodleAction.ExistingSection ){
if(!(getTableRow().getItem().getModuleType().replaceAll("\\<.*?>", "")).isEmpty()){
Label textArea = new Label();
textArea.setText(getTableRow().getItem().getModuleType().replaceAll("\\<.*?>", ""));
textArea.setWrapText(true);
textArea.setMaxWidth(200);
textArea.setStyle("-fx-font-weight: normal");
textArea.getStyleClass().add("popUpTextArea");
VBox vBox = new VBox(textArea);
vBox.setPadding(new Insets(5));
popOver = new PopOver(vBox);
popOver.setArrowLocation(PopOver.ArrowLocation.LEFT_CENTER);
this.setOnMouseEntered(mouseEvent -> {
//Show PopOver when mouse enters label
popOver.show(this);
});
this.setOnMouseExited(mouseEvent -> {
//Hide PopOver when mouse exits label
popOver.hide();
});
}
setText(getTableRow().getItem().getModuleName());
setStyle("-fx-font-weight: bold");
getTableRow().getStyleClass().add("headerstyle");
}
else if(getTableRow().getItem().getAction() == MoodleAction.MoodleUpload ||
getTableRow().getItem().getAction() == MoodleAction.FTPUpload ||
getTableRow().getItem().getAction() == MoodleAction.UploadSection ||
getTableRow().getItem().getAction() == MoodleAction.DatatypeNotKnown) {
if((getTableRow().getItem().getAction() == MoodleAction.MoodleUpload && getTableRow().getItem() != null) ||
(getTableRow().getItem().getAction() == MoodleAction.FTPUpload && getTableRow().getItem() != null)) {
if(!getTableRow().getItem().selectedProperty().get()){
setText(null);
}
getTableRow().getItem().selectedProperty().addListener(listener);
}
else {
setText(null);
}
}
else{
setEditable(false);
SvgIcon icon = new SvgIcon();
setStyle("-fx-font-weight: normal");
switch (getTableRow().getItem().getModuleType()) {
case "section" -> setStyle("-fx-font-weight: bold");
case "resource" -> icon.getStyleClass().add("file-icon");
case "forum" -> icon.getStyleClass().add("forum-icon");
case "folder" -> icon.getStyleClass().add("folder-icon");
case "label" -> icon.getStyleClass().add("label-icon");
case "quiz" -> icon.getStyleClass().add("quiz-icon");
case "assign" -> icon.getStyleClass().add("assignment-icon");
case "chat" -> icon.getStyleClass().add("chat-icon");
case "feedback" -> icon.getStyleClass().add("feedback-icon");
case "url" -> icon.getStyleClass().add("url-icon");
case "survey" -> icon.getStyleClass().add("survey-icon");
default -> icon.getStyleClass().add("other-icon");
}
setGraphic(icon);
setText(item.replaceAll("\\u00a0\\n|&nbsp;\\r\\n", ""));
}
}
public class Listener implements ChangeListener {
@Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
setEditable(getTableRow().getItem().selectedProperty().get());
if(getTableRow().getItem().selectedProperty().get()) setText(getTableRow().getItem().getExistingFileName());
setVisible(getTableRow().getItem().selectedProperty().get());
}
}
}

View file

@ -0,0 +1,161 @@
package moodle.sync.javafx.inject;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.name.Names;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import javax.inject.Singleton;
import moodle.sync.core.web.service.MoodleService;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.LocaleProvider;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.app.configuration.ConfigurationService;
import org.lecturestudio.core.app.configuration.JsonConfigurationService;
import org.lecturestudio.core.app.dictionary.Dictionary;
import org.lecturestudio.core.audio.bus.AudioBus;
import org.lecturestudio.core.bus.ApplicationBus;
import org.lecturestudio.core.bus.EventBus;
import org.lecturestudio.core.util.AggregateBundle;
import org.lecturestudio.core.util.DirUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import moodle.sync.core.config.DefaultConfiguration;
import moodle.sync.core.config.MoodleSyncConfiguration;
import moodle.sync.core.context.MoodleSyncContext;
public class ApplicationModule extends AbstractModule {
private final static Logger LOG = LogManager.getLogger(ApplicationModule.class);
private static final AppDataLocator LOCATOR = new AppDataLocator("MoodleSync");
private static final File CONFIG_FILE = new File(LOCATOR.toAppDataPath("config.json"));
@Override
protected void configure() {
bind(ApplicationContext.class).to(MoodleSyncContext.class);
Properties streamProps = new Properties();
try {
streamProps.load(getClass().getClassLoader()
.getResourceAsStream("resources/moodle-api.properties"));
Names.bindProperties(binder(), streamProps);
}
catch (IOException e) {
LOG.error("Load stream properties failed", e);
}
}
@Provides
@Singleton
ResourceBundle createResourceBundle(Configuration config) throws Exception {
LocaleProvider localeProvider = new LocaleProvider();
Locale locale = localeProvider.getBestSupported(config.getLocale());
return new AggregateBundle(locale, "resources.i18n.core", "resources.i18n.dict");
}
@Provides
@Singleton
AggregateBundle createAggregateBundle(ResourceBundle resourceBundle) {
return (AggregateBundle) resourceBundle;
}
@Provides
@Singleton
Dictionary provideDictionary(ResourceBundle resourceBundle) {
return new Dictionary() {
@Override
public String get(String key) throws NullPointerException {
return resourceBundle.getString(key);
}
@Override
public boolean contains(String key) {
return resourceBundle.containsKey(key);
}
};
}
@Provides
@Singleton
MoodleService createMoodleService(Configuration config){
MoodleSyncConfiguration syncConfig = (MoodleSyncConfiguration) config;
return new MoodleService(syncConfig.moodleUrlProperty());
}
@Provides
@Singleton
MoodleSyncContext createApplicationContext(Configuration config,
Dictionary dict) throws Exception {
EventBus eventBus = ApplicationBus.get();
EventBus audioBus = AudioBus.get();
return new MoodleSyncContext(LOCATOR, CONFIG_FILE, config, dict,
eventBus, audioBus);
}
@Provides
@Singleton
ConfigurationService<MoodleSyncConfiguration> provideConfigurationService() {
ConfigurationService<MoodleSyncConfiguration> configService = null;
try {
configService = new JsonConfigurationService<>();
}
catch (Exception e) {
LOG.error("Create configuration service failed.", e);
}
return configService;
}
@Provides
@Singleton
Configuration provideConfiguration(
ConfigurationService<MoodleSyncConfiguration> configService) {
MoodleSyncConfiguration configuration = null;
try {
DirUtils.createIfNotExists(Paths.get(LOCATOR.getAppDataPath()));
if (!CONFIG_FILE.exists()) {
// Create configuration with default values.
configuration = new DefaultConfiguration();
configService.save(CONFIG_FILE, configuration);
}
else {
configuration = configService.load(CONFIG_FILE,
MoodleSyncConfiguration.class);
}
// Set system default locale.
LocaleProvider localeProvider = new LocaleProvider();
configuration.setLocale(localeProvider.getBestSupported(
configuration.getLocale()));
}
catch (Exception e) {
LOG.error("Create configuration failed", e);
}
return configuration;
}
}

View file

@ -0,0 +1,56 @@
package moodle.sync.javafx.inject;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import javax.inject.Provider;
import javafx.util.BuilderFactory;
import moodle.sync.javafx.view.*;
import moodle.sync.view.*;
import org.lecturestudio.core.inject.DIViewContextFactory;
import org.lecturestudio.core.util.AggregateBundle;
import org.lecturestudio.core.view.*;
import org.lecturestudio.javafx.guice.FxmlViewLoader;
import org.lecturestudio.javafx.guice.FxmlViewMatcher;
import org.lecturestudio.javafx.view.*;
import org.lecturestudio.javafx.view.builder.DIBuilderFactory;
public class ViewModule extends AbstractModule {
@Override
protected void configure() {
bind(BuilderFactory.class).to(DIBuilderFactory.class);
bind(ViewContextFactory.class).to(DIViewContextFactory.class);
bind(DirectoryChooserView.class).to(FxDirectoryChooserView.class);
bind(FileChooserView.class).to(FxFileChooserView.class);
bind(NewVersionView.class).to(FxNewVersionView.class);
bind(NotificationView.class).to(FxNotificationView.class);
bind(NotificationPopupView.class).to(FxNotificationPopupView.class);
bind(NotificationPopupManager.class).to(FxNotificationPopupManager.class);
bind(ProgressView.class).to(FxProgressView.class);
bind(ProgressDialogView.class).to(FxProgressDialogView.class);
bind(MainView.class).to(FxMainView.class);
bind(StartView.class).to(FxStartView.class);
bind(SettingsView.class).to(FxSettingsView.class);
Provider<AggregateBundle> resourceProvider = getProvider(AggregateBundle.class);
Provider<BuilderFactory> builderProvider = getProvider(BuilderFactory.class);
bindListener(new FxmlViewMatcher(), new TypeListener() {
@Override
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.register(FxmlViewLoader.getInstance(resourceProvider, builderProvider));
}
});
}
}

View file

@ -0,0 +1,21 @@
package moodle.sync.javafx.log;
import org.lecturestudio.core.app.AppDataLocator;
import org.lecturestudio.core.log.Log4jXMLConfigurationFactory;
import org.lecturestudio.core.model.VersionInfo;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.plugins.Plugin;
@Plugin(name = "Log4jConfigurationFactory", category = "ConfigurationFactory")
@Order(10)
public class Log4jConfigurationFactory extends Log4jXMLConfigurationFactory {
public Log4jConfigurationFactory() {
AppDataLocator dataLocator = new AppDataLocator("MoodleSync");
System.setProperty("logAppVersion", VersionInfo.getAppVersion());
System.setProperty("logFilePath", dataLocator.getAppDataPath());
}
}

View file

@ -0,0 +1,18 @@
package moodle.sync.javafx.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
import moodle.sync.javafx.model.syncTableElement;
import java.nio.file.Path;
import java.util.List;
@Getter
@AllArgsConstructor
public class ReturnValue {
private List<Path> fileList;
private syncTableElement element;
}

View file

@ -0,0 +1,46 @@
package moodle.sync.javafx.model;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.time.LocalDate;
import java.time.LocalTime;
import static java.util.Objects.isNull;
public class TimeDateElement {
private ObjectProperty<LocalDate> localDate;
private ObjectProperty<LocalTime> localTime;
public TimeDateElement(LocalDate localDate, LocalTime localTime) {
this.localDate = new SimpleObjectProperty(localDate);
this.localTime = new SimpleObjectProperty(localTime);
}
public void setLocalDate(LocalDate localDate) {
this.localDate.set(localDate);
}
public void setLocalTime(LocalTime localTime) {
this.localTime.set(localTime);
}
public LocalDate getLocalDate() {
return localDate.get();
}
public LocalTime getLocalTime() {
if(isNull(localTime.get())){
return LocalTime.of(0, 0);
}
return localTime.get();
}
public ObjectProperty<LocalDate> LocalDateProperty() {
return localDate;
}
public ObjectProperty<LocalTime> LocalTimeProperty() {
return localTime;
}
}

View file

@ -0,0 +1,523 @@
package moodle.sync.javafx.model;
import javafx.beans.property.*;
import moodle.sync.core.util.MoodleAction;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class syncTableElement {
private StringProperty moduleName;
private IntegerProperty cmid;
private IntegerProperty section;
private IntegerProperty sectionId;
private IntegerProperty oldPos;
private StringProperty moduleType;
private StringProperty existingFile;
private StringProperty existingFileName;
private BooleanProperty selectable;
private BooleanProperty selected;
private MoodleAction action;
private IntegerProperty beforemod;
private BooleanProperty visible;
private ObjectProperty<TimeDateElement> availabilityDateTime;
private BooleanProperty delete = new SimpleBooleanProperty(false);
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos, String moduleType, Path existingFile, Boolean selectable, Boolean selected, MoodleAction action, Boolean visible){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = new SimpleStringProperty(existingFile.toString());
this.existingFileName = new SimpleStringProperty(existingFile.getFileName().toString());
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(-1);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(new TimeDateElement(null, null));
}
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos, String moduleType, Path existingFile, Boolean selectable, Boolean selected, MoodleAction action, Boolean visible, Integer beforemod){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = new SimpleStringProperty(existingFile.toString());
this.existingFileName = new SimpleStringProperty(existingFile.getFileName().toString());
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(beforemod);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(new TimeDateElement(null, null));
}
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos,String moduleType, Path existingFile, Boolean selectable, Boolean selected, MoodleAction action, Boolean visible, TimeDateElement availabilityDateTime){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = new SimpleStringProperty(existingFile.toString());
this.existingFileName = new SimpleStringProperty(existingFile.getFileName().toString());
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(-1);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(availabilityDateTime);
}
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos, String moduleType, Path existingFile, Boolean selectable, Boolean selected, MoodleAction action, Boolean visible, TimeDateElement availabilityDateTime, Integer beforemod){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = new SimpleStringProperty(existingFile.toString());
this.existingFileName = new SimpleStringProperty(existingFile.getFileName().toString());
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(beforemod);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(availabilityDateTime);
}
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos, String moduleType, Boolean selectable, Boolean selected, MoodleAction action, Boolean visible){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = null;
this.existingFileName = null;
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(-1);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(new TimeDateElement(null, null));
}
public syncTableElement(String moduleName, Integer cmid, Integer section, Integer sectionId, Integer oldPos, String moduleType, Path existingFile, Boolean selectable, Boolean selected, MoodleAction action, Integer beforemod, Boolean visible){
this.moduleName = new SimpleStringProperty(moduleName);
this.cmid = new SimpleIntegerProperty(cmid);
this.section = new SimpleIntegerProperty(section);
this.sectionId = new SimpleIntegerProperty(sectionId);
this.oldPos = new SimpleIntegerProperty(oldPos);
this.moduleType = new SimpleStringProperty(moduleType);
this.existingFile = new SimpleStringProperty(existingFile.toString());
this.existingFileName = new SimpleStringProperty(existingFile.getFileName().toString());
this.selectable = new SimpleBooleanProperty(selectable);
this.selected = new SimpleBooleanProperty(selected);
this.action = action;
this.beforemod = new SimpleIntegerProperty(beforemod);
this.visible = new SimpleBooleanProperty(visible);
this.availabilityDateTime = new SimpleObjectProperty(new TimeDateElement(null, null));
}
public long getUnixTimeStamp(){
LocalDateTime time = null;
try{
time = availabilityDateTime.get().getLocalTime().atDate(availabilityDateTime.get().getLocalDate());
} catch (Exception e) {
return 1659776301;
}
return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()/1000L;
}
public MoodleAction getAction(){ return action;}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public StringProperty moduleNameProperty() {
return moduleName;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public String getModuleName() {
return this.moduleName.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setModuleName(String value) {
this.moduleName.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public IntegerProperty cmidProperty() {
return cmid;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public Integer getCmid() {
return this.cmid.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setCmid(Integer value) { this.cmid.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public IntegerProperty sectionProperty() {
return section;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public Integer getSection() {
return this.section.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setSection(Integer value) { this.section.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public IntegerProperty sectionIdProperty() {
return sectionId;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public Integer getSectionId() {
return this.sectionId.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setSectionId(Integer value) { this.sectionId.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public IntegerProperty oldPosProperty() {
return oldPos;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public Integer getOldPos() {
return this.oldPos.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setOldPos(Integer value) { this.oldPos.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public StringProperty existingFileProperty() {
return existingFile;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public String getExistingFile() {
return this.existingFile.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setExistingFile(String value) {
this.existingFile.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public StringProperty existingFileNameProperty() {
return existingFileName;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public String getExistingFileName() {
return this.existingFileName.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setExistingFileName(String value) {
this.existingFileName.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public StringProperty moduleTypeProperty() {
return moduleType;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public String getModuleType() {
return this.moduleType.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setModuleType(String value) {
this.moduleType.set(value);
}
/**
* Providing the selectedProperty.
*
* @return the selectedProperty.
*/
public BooleanProperty selectableProperty() {
return selectable;
}
/**
* Proving whether the element is selected or not.
*
* @return True if the element is selected.
*/
public boolean isSelectable() {
return this.selectable.get();
}
/**
* Sets the selectedProperty.
*
* @param value if the object is selected.
*/
public void setSelectable(boolean value) {
this.selectable.set(value);
}
/**
* Providing the selectedProperty.
*
* @return the selectedProperty.
*/
public BooleanProperty selectedProperty() {
return selected;
}
/**
* Proving whether the element is selected or not.
*
* @return True if the element is selected.
*/
public boolean isSelected() {
return this.selected.get();
}
/**
* Sets the selectedProperty.
*
* @param value if the object is selected.
*/
public void setSelected(boolean value) {
this.selected.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public IntegerProperty beforemodProperty() {
return beforemod;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public Integer getBeforemod() {
return this.beforemod.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setBeforemod(Integer value) { this.beforemod.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public BooleanProperty visibleProperty() {
return visible;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public boolean getVisible() {
return this.visible.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setVisible(boolean value) { this.visible.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public ObjectProperty<TimeDateElement> availabilityDateTimeProperty() {
return availabilityDateTime;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public TimeDateElement getTimeDateElement() {
return availabilityDateTime.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setTimeDateElement(TimeDateElement value) { this.availabilityDateTime.set(value);
}
/**
* Providing the messageProperty.
*
* @return the messageProprty.
*/
public BooleanProperty deleteProperty() {
return delete;
}
/**
* Providing the files message as a String.
*
* @return the files message as a String.
*/
public boolean getDelete() {
return this.delete.get();
}
/**
* Sets a new message.
*
* @param value the new message.
*/
public void setDelete(boolean value) { this.delete.set(value);
}
}

View file

@ -0,0 +1,285 @@
package moodle.sync.javafx.view;
import static java.util.Objects.nonNull;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Locale;
import java.util.function.Predicate;
import javax.inject.Inject;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.Theme;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.View;
import org.lecturestudio.core.view.ViewLayer;
import org.lecturestudio.javafx.beans.converter.KeyEventConverter;
import org.lecturestudio.javafx.util.FxUtils;
import org.lecturestudio.javafx.view.FxmlView;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import moodle.sync.view.MainView;
/**
* Class implementing the functions of the "main-window".
*/
@FxmlView(name = "main-window")
public class FxMainView extends StackPane implements MainView {
private final ApplicationContext context;
private final Deque<Node> viewStack;
private Predicate<org.lecturestudio.core.input.KeyEvent> keyAction;
private Action shownAction;
private Action closeAction;
@FXML
private BorderPane contentPane;
@Inject
public FxMainView(ApplicationContext context) {
super();
this.context = context;
this.viewStack = new ArrayDeque<>();
}
@Override
public Rectangle2D getBounds() {
Window window = getScene().getWindow();
return new Rectangle2D(window.getX(), window.getY(), window.getWidth(),
window.getHeight());
}
@Override
public void close() {
// Fire close request in order to shut down appropriately.
Window window = getScene().getWindow();
window.fireEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSE_REQUEST));
}
@Override
public void hide() {
FxUtils.invoke(() -> {
Window window = getScene().getWindow();
window.hide();
});
}
@Override
public void removeView(View view, ViewLayer layer) {
if (layer == ViewLayer.Window) {
return;
}
checkNodeView(view);
Node nodeView = (Node) view;
removeNode(nodeView);
}
@Override
public void showView(View view, ViewLayer layer) {
if (layer == ViewLayer.Window) {
return;
}
checkNodeView(view);
Node nodeView = (Node) view;
switch (layer) {
case Content:
showNode(nodeView, true);
break;
case Dialog:
case Notification:
showNodeOnTop(nodeView);
break;
}
}
@Override
public void setFullscreen(boolean fullscreen) {
FxUtils.invoke(() -> {
Stage stage = (Stage) getScene().getWindow();
stage.setFullScreen(fullscreen);
});
}
@Override
public void setOnKeyEvent(Predicate<org.lecturestudio.core.input.KeyEvent> action) {
this.keyAction = action;
}
@Override
public void setOnShown(Action action) {
this.shownAction = action;
}
@Override
public void setOnClose(Action action) {
this.closeAction = action;
}
private void onKeyEvent(KeyEvent event) {
if (event.getEventType() == KeyEvent.KEY_PRESSED) {
org.lecturestudio.core.input.KeyEvent keyEvent = KeyEventConverter.INSTANCE.from(event);
if (nonNull(keyAction) && keyAction.test(keyEvent)) {
event.consume();
}
}
}
private void removeNode(Node nodeView) {
FxUtils.invoke(() -> {
boolean removed = getChildren().remove(nodeView);
if (!removed) {
showNode(nodeView, false);
}
});
}
private void showNode(Node nodeView, boolean show) {
Node currentView = contentPane.getCenter();
boolean isSame = currentView == nodeView;
if (show) {
if (!isSame) {
viewStack.push(nodeView);
FxUtils.invoke(() -> {
contentPane.setCenter(nodeView);
});
}
}
else if (isSame) {
Node lastView = viewStack.pop();
if (!viewStack.isEmpty()) {
lastView = viewStack.pop();
}
showNode(lastView, true);
}
}
private void showNodeOnTop(Node nodeView) {
FxUtils.invoke(() -> {
getChildren().add(nodeView);
});
}
@FXML
private void initialize() {
Configuration config = context.getConfiguration();
// Set application wide font size.
setStyle(String.format(Locale.US, "-fx-font-size: %.2fpt;", config.getUIControlSize()));
addEventFilter(KeyEvent.KEY_PRESSED, this::onKeyEvent);
config.themeProperty().addListener((observable, oldTheme, newTheme) -> {
// Load new theme.
loadTheme(newTheme);
// Unload old theme.
unloadTheme(oldTheme);
});
// Init view-stack with default node.
viewStack.push(contentPane.getCenter());
sceneProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends Scene> observableValue,
Scene oldScene, Scene newScene) {
if (nonNull(newScene)) {
sceneProperty().removeListener(this);
onSceneSet(newScene);
}
}
});
}
private void loadTheme(Theme theme) {
if (nonNull(theme) && nonNull(theme.getFile())) {
String themeUrl = getClass().getResource(theme.getFile()).toExternalForm();
getScene().getStylesheets().add(themeUrl);
}
}
private void unloadTheme(Theme theme) {
if (nonNull(theme.getFile())) {
String themeUrl = getClass().getResource(theme.getFile()).toExternalForm();
getScene().getStylesheets().remove(themeUrl);
}
}
private void onSceneSet(Scene scene) {
scene.windowProperty().addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends Window> observable,
Window oldWindow, Window newWindow) {
if (nonNull(newWindow)) {
scene.windowProperty().removeListener(this);
onStageSet((Stage) newWindow);
}
}
});
}
private void onStageSet(Stage stage) {
// It's imperative to load fxml-defined stylesheets prior to the user-defined theme
// stylesheet, so the theme can override initial styles.
getStylesheets().forEach(file -> {
loadTheme(new Theme("defined", file));
});
// Remove loaded stylesheets to avoid additional loading by the scene itself.
getStylesheets().clear();
loadTheme(context.getConfiguration().getTheme());
stage.setOnShown(event -> {
executeAction(shownAction);
});
stage.setOnCloseRequest(event -> {
// Consume event. Don't close the window yet.
event.consume();
executeAction(closeAction);
});
}
private void checkNodeView(View view) {
if (!Node.class.isAssignableFrom(view.getClass())) {
throw new IllegalArgumentException("View expected to be a JavaFX Node");
}
}
}

View file

@ -0,0 +1,233 @@
package moodle.sync.javafx.view;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.util.converter.IntegerStringConverter;
import moodle.sync.presenter.SettingsPresenter;
import moodle.sync.util.UserInputValidations;
import moodle.sync.view.SettingsView;
import org.controlsfx.control.PopOver;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.beans.StringProperty;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.javafx.beans.LectBooleanProperty;
import org.lecturestudio.javafx.beans.LectObjectProperty;
import org.lecturestudio.javafx.beans.LectStringProperty;
import org.lecturestudio.javafx.util.FxUtils;
import org.lecturestudio.javafx.view.FxView;
import org.lecturestudio.javafx.view.FxmlView;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
/**
* Class implementing the functions of the "settings-page".
*
* @author Daniel Schröter
*/
@FxmlView(name = "main-settings", presenter = SettingsPresenter.class)
public class FxSettingsView extends VBox implements SettingsView, FxView {
@FXML
private Button closesettingsButton;
@FXML
private ComboBox<Locale> languageCombo;
@FXML
private TextField tokenField;
@FXML
private TextField syncRootPath;
@FXML
private TextField ftpField;
@FXML
private TextField ftpUser;
@FXML
private TextField ftpPassword;
@FXML
private TextField moodleField;
@FXML
private TextArea formatsMoodle;
@FXML
private TextField ftpPort;
@FXML
private TextArea formatsFileserver;
@FXML
private Button syncRootPathButton;
@FXML
private CheckBox showUnknownFormats;
public FxSettingsView() {
super();
}
/**
* Exiting the "settings-page".
*
* @param action Users action.
*/
@Override
public void setOnExit(Action action) {
FxUtils.bindAction(closesettingsButton, action);
}
@Override
public void setLocale(ObjectProperty<Locale> locale) {
languageCombo.valueProperty().bindBidirectional(new LectObjectProperty<>(locale));
}
@Override
public void setLocales(List<Locale> locales) {
FxUtils.invoke(() -> languageCombo.getItems().setAll(locales));
}
@Override
public void setMoodleField(StringProperty moodleURL) {
moodleField.textProperty().bindBidirectional(new LectStringProperty(moodleURL));
moodleField.textProperty().addListener(event -> {
moodleField.pseudoClassStateChanged(
PseudoClass.getPseudoClass("error"),
!moodleField.getText().matches("^(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]")
);
});
}
/**
* Input of the Moodle-token.
*
* @param moodleToken User input.
*/
@Override
public void setMoodleToken(StringProperty moodleToken) {
tokenField.textProperty().bindBidirectional(new LectStringProperty(moodleToken));
tokenField.textProperty().addListener(event -> {
tokenField.pseudoClassStateChanged(
PseudoClass.getPseudoClass("error"),
(tokenField.getText().isEmpty())
);
});
}
/**
* Input and inputvalidation of the fileserver url.
*
* @param ftpURL User input.
*/
@Override
public void setFtpField(StringProperty ftpURL) {
ftpField.textProperty().bindBidirectional(new LectStringProperty(ftpURL));
ftpField.textProperty().addListener(event -> {
ftpField.pseudoClassStateChanged(
PseudoClass.getPseudoClass("error"),
(!ftpField.getText().isEmpty() &&
!ftpField.getText().matches("^(((https?|ftp)://)|(ftp\\.))[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"))
);
});
}
/**
* Input and inputvalidation of the used port.
*
* @param ftpport User input.
*/
@Override
public void setFtpPort(StringProperty ftpport) {
ftpPort.setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), 0, UserInputValidations.numberValidationFormatter));
ftpPort.textProperty().bindBidirectional(new LectStringProperty(ftpport));
}
/**
* Input of the fileserver username.
*
* @param ftpuser User input.
*/
@Override
public void setFtpUser(StringProperty ftpuser) {
ftpUser.textProperty().bindBidirectional(new LectStringProperty(ftpuser));
}
/**
* Input of the fileserver password.
*
* @param ftppassword User input.
*/
@Override
public void setFtpPassword(StringProperty ftppassword) {
ftpPassword.textProperty().bindBidirectional(new LectStringProperty(ftppassword));
}
/**
* Input of formats used to upload to the Moodle-Platform.
*
* @param moodleformats User input.
*/
@Override
public void setFormatsMoodle(StringProperty moodleformats) {
formatsMoodle.textProperty().bindBidirectional(new LectStringProperty(moodleformats));
}
/**
* Input of formats used to upload to the fileserver.
*
* @param fileserverformats User input.
*/
@Override
public void setFormatsFileserver(StringProperty fileserverformats) {
formatsFileserver.textProperty().bindBidirectional(new LectStringProperty(fileserverformats));
}
/**
* Input of the Root-Directory.
*
* @param path User input.
*/
@Override
public void setSyncRootPath(StringProperty path) {
syncRootPath.textProperty().bindBidirectional(new LectStringProperty(path));
syncRootPath.textProperty().addListener(event -> {
syncRootPath.pseudoClassStateChanged(
PseudoClass.getPseudoClass("error"),
!syncRootPath.getText().isEmpty() &&
!Files.isDirectory(Paths.get(syncRootPath.getText()))
);
});
}
/**
* Opens the explorer.
*
* @param action User needs to click a button.
*/
@Override
public void setSelectSyncRootPath(Action action) {
FxUtils.bindAction(syncRootPathButton, action);
}
/**
* User can set if files with unknownFormats should be shown in the "sync-table".
*
* @param unknownFormats User Input CheckBox.
*/
@Override
public void setShowUnknownFormats(BooleanProperty unknownFormats) {
showUnknownFormats.selectedProperty().bindBidirectional(new LectBooleanProperty(unknownFormats));
}
}

View file

@ -0,0 +1,163 @@
package moodle.sync.javafx.view;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import moodle.sync.core.model.json.Course;
import moodle.sync.javafx.model.syncTableElement;
import moodle.sync.core.model.json.Section;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.javafx.beans.LectBooleanProperty;
import org.lecturestudio.javafx.beans.LectObjectProperty;
import org.lecturestudio.javafx.util.FxUtils;
import org.lecturestudio.javafx.view.FxView;
import org.lecturestudio.javafx.view.FxmlView;
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
import moodle.sync.presenter.StartPresenter;
import moodle.sync.view.StartView;
import java.util.List;
/**
* Class implementing the functions of the "start-page".
*
* @author Daniel Schröter
*/
@FxmlView(name = "main-start", presenter = StartPresenter.class)
public class FxStartView extends VBox implements StartView, FxView {
@FXML
private Button syncButton;
@FXML
private Button settingsButton;
@FXML
private Button updateButton;
@FXML
private Button folderButton;
@FXML
private CheckBox allSelected;
@FXML
private ComboBox<Course> courseCombo;
@FXML
private ComboBox<Section> sectionCombo;
@FXML
private TableView<syncTableElement> syncTable;
public FxStartView() {
super();
}
@Override
public void setData(ObservableList<syncTableElement> data) {
FxUtils.invoke(() -> {
syncTable.getItems().clear();
syncTable.setItems(data);
});
}
/**
* Update the interface
*
* @param action User presses button.
*/
@Override
public void setOnUpdate(Action action) {
FxUtils.bindAction(updateButton, action);
}
/**
* Start the synchronisation process.
*
* @param action User presses button.
*/
@Override
public void setOnSync(Action action) {
FxUtils.bindAction(syncButton, action);
}
/**
* User opens the "settings-page".
*
* @param action User presses button.
*/
@Override
public void setOnSettings(Action action) {
FxUtils.bindAction(settingsButton, action);
}
@Override
public void setSelectAll(BooleanProperty selectAll) {
allSelected.selectedProperty().bindBidirectional(new LectBooleanProperty(selectAll));
}
@Override
public void setOnFolder(Action action) {
FxUtils.bindAction(folderButton, action);
}
/**
* Method to set the elements of the Course-Combobox.
*
* @param courses Moodle-Courses to display.
*/
@Override
public void setCourses(List<Course> courses) {
FxUtils.invoke(() -> courseCombo.getItems().setAll(courses));
}
/**
* Choosen Moodle-course.
*
* @param course choosen Moodle-course.
*/
@Override
public void setCourse(ObjectProperty<Course> course) {
courseCombo.valueProperty().bindBidirectional(new LectObjectProperty<>(course));
}
/**
* Method to set the elements of the Section-Combobox.
*
* @param sections Course-Sections to display.
*/
@Override
public void setSections(List<Section> sections) {
FxUtils.invoke(() -> sectionCombo.getItems().setAll(sections));
sectionCombo.getSelectionModel().selectFirst();
}
/**
* Choosen course-section.
*
* @param section choosen course-section.
*/
@Override
public void setSection(ObjectProperty<Section> section) {
sectionCombo.valueProperty().bindBidirectional(new LectObjectProperty<>(section));
}
/**
* Method to initiate the display of the sections of a choosen Course.
*
* @param action User chooses Course.
*/
@Override
public void setOnCourseChanged(ConsumerAction<Course> action) {
courseCombo.valueProperty().addListener((observable, oldCourse, newCourse) -> {
executeAction(action, newCourse);
});
}
}

View file

@ -0,0 +1,451 @@
package moodle.sync.presenter;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import com.google.common.eventbus.Subscribe;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Predicate;
import javax.inject.Inject;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.configuration.Configuration;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.bus.event.ViewVisibleEvent;
import org.lecturestudio.core.input.KeyEvent;
import org.lecturestudio.core.model.VersionInfo;
import org.lecturestudio.core.presenter.NewVersionPresenter;
import org.lecturestudio.core.presenter.NotificationPresenter;
import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.core.presenter.command.CloseApplicationCommand;
import org.lecturestudio.core.presenter.command.ClosePresenterCommand;
import org.lecturestudio.core.presenter.command.NewVersionCommand;
import org.lecturestudio.core.presenter.command.ShowPresenterCommand;
import org.lecturestudio.core.util.ObservableHashMap;
import org.lecturestudio.core.util.ObservableMap;
import org.lecturestudio.core.util.ShutdownHandler;
import org.lecturestudio.core.view.NotificationPopupManager;
import org.lecturestudio.core.view.NotificationPopupView;
import org.lecturestudio.core.view.NotificationType;
import org.lecturestudio.core.view.View;
import org.lecturestudio.core.view.ViewContextFactory;
import org.lecturestudio.core.view.ViewHandler;
import org.lecturestudio.core.view.ViewLayer;
import org.lecturestudio.web.api.model.GitHubRelease;
import org.lecturestudio.web.api.service.VersionChecker;
import moodle.sync.input.Shortcut;
import moodle.sync.view.MainView;
/**
* Class defining the logic of the "main-window".
*/
public class MainPresenter extends org.lecturestudio.core.presenter.MainPresenter<MainView> implements ViewHandler {
private final ObservableMap<Class<? extends View>, BooleanProperty> viewMap;
private final Map<KeyEvent, Predicate<KeyEvent>> shortcutMap;
private final List<ShutdownHandler> shutdownHandlers;
private final List<Presenter<?>> contexts;
private final NotificationPopupManager popupManager;
private final ViewContextFactory contextFactory;
/** The waiting notification. */
private NotificationPresenter notificationPresenter;
@Inject
MainPresenter(ApplicationContext context, MainView view,
NotificationPopupManager popupManager,
ViewContextFactory contextFactory) {
super(context, view);
this.popupManager = popupManager;
this.contextFactory = contextFactory;
this.viewMap = new ObservableHashMap<>();
this.shortcutMap = new HashMap<>();
this.contexts = new ArrayList<>();
this.shutdownHandlers = new ArrayList<>();
}
@Override
public void openFile(File file) {
}
@Override
public void setArgs(String[] args) {
}
@Override
public void initialize() {
registerShortcut(Shortcut.CLOSE_VIEW, this::closeView);
addShutdownHandler(new SaveConfigurationHandler(context));
addShutdownHandler(new ShutdownHandler() {
@Override
public boolean execute() {
if (nonNull(closeAction)) {
closeAction.execute();
}
return true;
}
});
Configuration config = context.getConfiguration();
context.setFullscreen(config.getStartFullscreen());
context.fullscreenProperty().addListener((observable, oldValue, newValue) -> {
setFullscreen(newValue);
});
view.setOnClose(this::closeWindow);
view.setOnShown(this::onViewShown);
view.setOnKeyEvent(this::keyEvent);
context.getEventBus().register(this);
if (config.getCheckNewVersion()) {
// Check for a new version.
CompletableFuture.runAsync(() -> {
try {
VersionChecker versionChecker = new VersionChecker();
if (versionChecker.newVersionAvailable()) {
GitHubRelease release = versionChecker.getLatestRelease();
VersionInfo version = new VersionInfo();
version.downloadUrl = versionChecker.getMatchingAssetUrl();
version.htmlUrl = release.getUrl();
version.published = release.getPublishedAt();
version.version = release.getTagName();
context.getEventBus().post(new NewVersionCommand(
NewVersionPresenter.class, version));
}
}
catch (Exception e) {
throw new CompletionException(e);
}
})
.exceptionally(throwable -> {
logException(throwable, "Check for new version failed");
return null;
});
}
}
@Override
public void destroy() {
if (shutdownHandlers.isEmpty()) {
return;
}
Runnable shutdownLoop = () -> {
for (ShutdownHandler handler : shutdownHandlers) {
try {
if (!handler.execute()) {
// Abort shutdown process.
break;
}
}
catch (Exception e) {
logException(e, "Execute shutdown handler failed");
}
}
};
Thread thread = new Thread(shutdownLoop, "ShutdownHandler-Thread");
thread.start();
}
@Subscribe
public void onCommand(final CloseApplicationCommand command) {
closeWindow();
}
@Subscribe
public void onCommand(final ClosePresenterCommand command) {
destroyHandler(command.getPresenterClass());
}
@Subscribe
public <T extends Presenter<?>> void onCommand(final ShowPresenterCommand<T> command) {
T presenter = createPresenter(command.getPresenterClass());
try {
command.execute(presenter);
}
catch (Exception e) {
logException(e, "Execute command failed");
}
display(presenter);
}
@Override
public void addShutdownHandler(ShutdownHandler handler) {
requireNonNull(handler, "ShutdownHandler must not be null.");
if (!shutdownHandlers.contains(handler)) {
shutdownHandlers.add(handler);
}
}
@Override
public void removeShutdownHandler(ShutdownHandler handler) {
requireNonNull(handler, "ShutdownHandler must not be null.");
shutdownHandlers.remove(handler);
}
@Override
public void showView(View childView, ViewLayer layer) {
if (layer == ViewLayer.NotificationPopup) {
popupManager.show(view, (NotificationPopupView) childView);
}
else {
view.showView(childView, layer);
setViewShown(getViewInterface(childView.getClass()));
}
}
@Override
public void display(Presenter<?> presenter) {
requireNonNull(presenter);
Presenter<?> cachedPresenter = findCachedContext(presenter);
try {
if (nonNull(cachedPresenter)) {
View view = cachedPresenter.getView();
if (nonNull(view)) {
BooleanProperty property = getViewVisibleProperty(getViewInterface(view.getClass()));
if (property.get()) {
return;
}
showView(view, cachedPresenter.getViewLayer());
}
}
else {
if (presenter.getClass().equals(NotificationPresenter.class) &&
nonNull(notificationPresenter) &&
!notificationPresenter.equals(presenter)) {
hideWaitingNotification();
}
presenter.initialize();
View view = presenter.getView();
if (nonNull(view)) {
BooleanProperty property = getViewVisibleProperty(getViewInterface(view.getClass()));
if (property.get()) {
return;
}
presenter.setOnClose(() -> destroy(presenter));
showView(view, presenter.getViewLayer());
addContext(presenter);
}
}
}
catch (Exception e) {
handleException(e, "Show view failed", "error", "generic.error");
}
}
@Override
public void destroy(Presenter<?> presenter) {
requireNonNull(presenter);
View childView = presenter.getView();
try {
view.removeView(childView, presenter.getViewLayer());
setViewHidden(getViewInterface(childView.getClass()));
if (!presenter.cache()) {
presenter.destroy();
removeContext(presenter);
}
}
catch (Exception e) {
handleException(e, "Destroy view failed", "error", "generic.error");
}
}
@Override
public void closeWindow() {
destroy();
}
@Override
public void setFullscreen(boolean enable) {
view.setFullscreen(enable);
}
private void addContext(Presenter<?> presenter) {
requireNonNull(context);
if (!contexts.contains(presenter)) {
contexts.add(presenter);
}
}
private void removeContext(Presenter<?> presenter) {
requireNonNull(presenter);
contexts.remove(presenter);
}
private Presenter<?> findCachedContext(Presenter<?> presenter) {
requireNonNull(presenter);
for (Presenter<?> p : contexts) {
if ((presenter.equals(p) || presenter.getClass() == p.getClass()) && p.cache()) {
return p;
}
}
return null;
}
private boolean keyEvent(KeyEvent event) {
Predicate<KeyEvent> action = shortcutMap.get(event);
if (nonNull(action)) {
return action.test(event);
}
return false;
}
private BooleanProperty getViewVisibleProperty(Class<? extends View> viewClass) {
BooleanProperty property = viewMap.get(viewClass);
if (isNull(property)) {
property = new BooleanProperty(false);
property.addListener((observable, oldValue, newValue) -> {
context.getEventBus().post(new ViewVisibleEvent(viewClass, newValue));
});
viewMap.put(viewClass, property);
}
return property;
}
private void destroyHandler(Class<? extends Presenter<?>> presenterClass) {
for (Presenter<?> presenter : contexts) {
if (getViewInterface(presenter.getClass()) == presenterClass) {
destroy(presenter);
break;
}
}
}
private void onViewShown() {
}
private boolean closeView(KeyEvent event) {
if (!contexts.isEmpty()) {
Presenter<?> presenter = contexts.get(contexts.size() - 1);
View view = presenter.getView();
BooleanProperty property = getViewVisibleProperty(getViewInterface(view.getClass()));
if (property.get()) {
presenter.close();
return true;
}
}
return false;
}
private void registerShortcut(Shortcut shortcut, Predicate<KeyEvent> action) {
shortcutMap.put(shortcut.getKeyEvent(), action);
}
private void showWaitingNotification(String title, String message) {
if (context.getDictionary().contains(title)) {
title = context.getDictionary().get(title);
}
if (context.getDictionary().contains(message)) {
message = context.getDictionary().get(message);
}
notificationPresenter = createPresenter(NotificationPresenter.class);
notificationPresenter.setMessage(message);
notificationPresenter.setNotificationType(NotificationType.WAITING);
notificationPresenter.setTitle(title);
display(notificationPresenter);
}
private void hideWaitingNotification() {
if (nonNull(notificationPresenter)) {
destroy(notificationPresenter);
notificationPresenter = null;
}
}
private void setViewHidden(Class<? extends View> viewClass) {
BooleanProperty property = getViewVisibleProperty(viewClass);
property.set(false);
}
private void setViewShown(Class<? extends View> viewClass) {
BooleanProperty property = getViewVisibleProperty(viewClass);
property.set(true);
}
private <T extends Presenter<?>> T createPresenter(Class<T> pClass) {
return contextFactory.getInstance(pClass);
}
@SuppressWarnings("unchecked")
private Class<? extends View> getViewInterface(Class<?> cls) {
while (nonNull(cls)) {
final Class<?>[] interfaces = cls.getInterfaces();
for (final Class<?> i : interfaces) {
if (i == View.class) {
return (Class<? extends View>) cls;
}
return getViewInterface(i);
}
cls = cls.getSuperclass();
}
return null;
}
}

View file

@ -0,0 +1,26 @@
package moodle.sync.presenter;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.util.ShutdownHandler;
public class SaveConfigurationHandler extends ShutdownHandler {
private final ApplicationContext context;
public SaveConfigurationHandler(ApplicationContext context) {
this.context = context;
}
@Override
public boolean execute() {
try {
context.saveConfiguration();
}
catch (Exception e) {
logException(e, "Save configuration failed");
}
return true;
}
}

View file

@ -0,0 +1,93 @@
package moodle.sync.presenter;
import moodle.sync.core.config.DefaultConfiguration;
import moodle.sync.core.config.MoodleSyncConfiguration;
import moodle.sync.view.SettingsView;
import moodle.sync.core.web.service.MoodleService;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.app.LocaleProvider;
import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.core.view.DirectoryChooserView;
import org.lecturestudio.core.view.ViewContextFactory;
import javax.inject.Inject;
import java.io.File;
import static java.util.Objects.nonNull;
/**
* Class defining the logic of the "settings-page".
*
* @author Daniel Schröter
*/
public class SettingsPresenter extends Presenter<SettingsView> {
private final ViewContextFactory viewFactory;
//Used MoodleService for executing Web Service API-Calls.
private final MoodleService moodleService;
@Inject
SettingsPresenter(ApplicationContext context, SettingsView view,
ViewContextFactory viewFactory, MoodleService moodleService) {
super(context, view);
this.moodleService = moodleService;
this.viewFactory = viewFactory;
}
@Override
public void initialize() throws Exception{
MoodleSyncConfiguration config = (MoodleSyncConfiguration) context.getConfiguration();
LocaleProvider localeProvider = new LocaleProvider();
//Initialising all functions of the "settings-page" with the help of the configuration.
view.setOnExit(this::close);
view.setLocales(localeProvider.getLocales());
view.setLocale(config.localeProperty());
view.setMoodleField(config.moodleUrlProperty());
view.setFormatsMoodle(config.formatsMoodleProperty());
view.setFormatsFileserver(config.formatsFileserverProperty());
view.setMoodleToken(config.moodleTokenProperty());
view.setSyncRootPath(config.syncRootPathProperty());
view.setSelectSyncRootPath(this::selectSyncPath);
view.setFtpField(config.FileserverProperty());
view.setFtpPort(config.portFileserverProperty());
view.setFtpUser(config.userFileserverProperty());
view.setFtpPassword(config.passwordFileserverProperty());
view.setShowUnknownFormats(config.showUnknownFormatsProperty());
}
/**
* Function to close the "settings-page".
*/
@Override
public void close() {
MoodleSyncConfiguration config = (MoodleSyncConfiguration) context.getConfiguration();
//Reconstruct the MoodleService with the new settings.
moodleService.setApiUrl(config.getMoodleUrl());
super.close();
}
/**
* Providing the functionality to choose a Root-Directory.
*/
private void selectSyncPath() {
MoodleSyncConfiguration config = (MoodleSyncConfiguration) context.getConfiguration();
String syncPath = config.getSyncRootPath();
//Check whether a default path should be used to prevent unwanted behavior.
if (syncPath == null || syncPath.isEmpty() || syncPath.isBlank()) {
DefaultConfiguration defaultConfiguration = new DefaultConfiguration();
syncPath = defaultConfiguration.getSyncRootPath();
}
File initDirectory = new File(syncPath);
DirectoryChooserView dirChooser = viewFactory.createDirectoryChooserView();
dirChooser.setInitialDirectory(initDirectory);
File selectedFile = dirChooser.show(view);
if (nonNull(selectedFile)) {
config.setSyncRootPath(selectedFile.getAbsolutePath());
}
}
}

View file

@ -0,0 +1,598 @@
package moodle.sync.presenter;
import javax.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import moodle.sync.core.config.DefaultConfiguration;
import moodle.sync.core.config.MoodleSyncConfiguration;
import moodle.sync.core.model.json.*;
import moodle.sync.core.fileserver.FileServerClientFTP;
import moodle.sync.core.fileserver.FileServerFile;
import moodle.sync.core.model.json.Module;
import moodle.sync.util.VerifyDataService;
import moodle.sync.javafx.model.ReturnValue;
import moodle.sync.presenter.command.ShowSettingsCommand;
import moodle.sync.util.FileService;
import moodle.sync.core.util.FileWatcherService.FileEvent;
import moodle.sync.core.util.FileWatcherService.FileListener;
import moodle.sync.core.util.FileWatcherService.FileWatcher;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.javafx.model.TimeDateElement;
import moodle.sync.javafx.model.syncTableElement;
import moodle.sync.util.SetModuleService;
import org.apache.commons.io.FilenameUtils;
import org.lecturestudio.core.app.ApplicationContext;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.presenter.Presenter;
import org.lecturestudio.core.view.NotificationType;
import org.lecturestudio.core.view.ViewContextFactory;
import moodle.sync.view.StartView;
import moodle.sync.core.web.service.MoodleService;
import java.awt.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static java.util.Objects.isNull;
/**
* Class defining the logic of the "start-page".
*
* @author Daniel Schröter
*/
public class StartPresenter extends Presenter<StartView> implements FileListener {
private final ViewContextFactory viewFactory;
//Used MoodleService for executing Web Service API-Calls.
private final MoodleService moodleService;
//Configuration providing the settings.
private final MoodleSyncConfiguration config;
//Providing the content of a course. Used for the section-combobox.
private List<Section> courseContent;
//List representing the actual courseData with the planned/possible changes.
private ObservableList<syncTableElement> courseData;
//FileWatcher for the current course's directory.
private FileWatcher watcher;
//Saves if the fileserver is required.
private boolean fileServerRequired;
//Used fileServerClient implementation.
private FileServerClientFTP fileClient;
//Select all possible changes.
private BooleanProperty selectAll;
//User's moodle token.
private String token;
//The moodle plattforms url.
private String url;
//Selected moodle course.
private Course course;
//Selected moodle section.
private Section section;
@Inject
StartPresenter(ApplicationContext context, StartView view, ViewContextFactory viewFactory,
MoodleService moodleService) {
super(context, view);
this.viewFactory = viewFactory;
this.moodleService = moodleService;
this.config = (MoodleSyncConfiguration) context.getConfiguration();
this.selectAll = new BooleanProperty(false);
}
@Override
public void initialize() {
//Initialising all functions of the "start-page" with the help of the configuration.
String syncPath = config.getSyncRootPath();
//Check whether a default path should be used to prevent unwanted behavior.
if (!VerifyDataService.validateString(syncPath)) {
DefaultConfiguration defaultConfiguration = new DefaultConfiguration();
config.setSyncRootPath(defaultConfiguration.getSyncRootPath());
}
view.setOnUpdate(this::updateCourses);
view.setOnSync(this::onSync);
view.setOnSettings(this::onSettings);
view.setCourse(config.recentCourseProperty());
view.setCourses(courses());
view.setSection(config.recentSectionProperty());
view.setSections(sections());
view.setOnCourseChanged(this::onCourseChanged);
view.setData(setData());
view.setOnFolder(this::openCourseDirectory);
view.setSelectAll(selectAll);
//Display the course-sections after Moodle-course is choosen.
config.recentCourseProperty().addListener((observable, oldCourse, newCourse) -> {
course = config.getRecentCourse(); //Todo hier schauen ob das ausreicht!
config.setRecentSection(null);
view.setData(setData());
});
config.recentSectionProperty().addListener((observable, oldSection, newSection) -> {
section = config.getRecentSection();
view.setData(setData());
});
config.moodleUrlProperty().addListener((observable, oldUrl, newUrl) -> {
config.setRecentCourse(null);
config.setRecentSection(null);
course = null;
section = null;
});
selectAll.addListener((observable, oldUrl, newUrl) -> {
if (newUrl) {
for (syncTableElement elem : courseData) {
if (elem.isSelectable()) {
elem.selectedProperty().setValue(true);
}
}
} else {
for (syncTableElement elem : courseData) {
if (elem.isSelectable()) {
elem.selectedProperty().setValue(false);
}
}
}
});
}
private void onCourseChanged(Course course) {
view.setSections(sections());
}
/**
* Execute an API-call to get users Moodle-courses.
*
* @return list containing users Moodle-courses.
*/
private List<Course> courses() {
url = config.getMoodleUrl();
//Security checks to prevent unwanted behaviour.
//Todo überprüfen neu
if (!VerifyDataService.validateString(url) || !VerifyDataService.validateString(token)) {
return new ArrayList<>();
}
List<Course> courses = List.of();
try {
courses = moodleService.getEnrolledCourses(token, moodleService.getUserId(token));
}
catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.invalidurl.message");
config.setRecentCourse(null);
course = null;
}
//Do not show Moodle-courses which are already over.
if (!courses.isEmpty()) {
courses.removeIf(item -> (item.getEnddate() != 0 && (item.getEnddate() < System.currentTimeMillis()
/1000)));
}
//Sort Courses if Possible
/*if(courses.get(0).getShortname().contains("SoSe") || courses.get(0).getShortname().contains("WiSe")){
if(courses.get(0).getShortname().contains("20")){
}
}*/
return courses;
}
/**
* Execute an API-call to get a choosen Moodle-courses course-sections.
*
* @return list containing course-sections.
*/
private List<Section> sections() {
if (course == null) {
return new ArrayList<>();
}
try {
List<Section> content = moodleService.getCourseContent(token, course.getId());
content.add(0, new Section(-2, this.context.getDictionary().get("start.sync.showall"), 1, "all", -1, -1,
-1, true, null));
courseContent = content;
return content;
}
catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.invalidurl.message");
}
return new ArrayList<>();
}
private void onSettings() {
context.getEventBus().post(new ShowSettingsCommand(this::refreshCourseList));
}
/**
* Starts the sync-process.
*/
private void onSync() {
//Serveral security checks to prevent unwanted behaviour.
if (config.getRecentCourse() == null) {
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.course.message");
return;
}
if (!Files.isDirectory(Paths.get(config.getSyncRootPath()))) {
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.path.message");
return;
}
for (syncTableElement courseData : courseData) {
try {
if (courseData.isSelected()) {
if (courseData.getModuleType().equals("resource")) {
SetModuleService.publishResource(moodleService, courseData, course, url, token);
}
else if (courseData.getAction() == MoodleAction.FTPUpload) {
SetModuleService.publishFileserverResource(moodleService, courseData, course, token);
}
else if (courseData.getAction() != MoodleAction.UploadSection) {
SetModuleService.moveResource(moodleService, courseData, token);
}
}
}
catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title",
MessageFormat.format(context.getDictionary().get("start.sync.error.upload.message"),
courseData.getModuleName()));
}
}
//Adding of new sections at the end of the sync-process to prevent new section-numbers
for (syncTableElement courseData : courseData) {
if (courseData.getAction() == MoodleAction.UploadSection && courseData.isSelected()) {
//Logic for Section-Upload
try {
SetModuleService.createSection(moodleService, courseData, course, token);
} catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.upload" +
".message");
}
}
}
updateCourses();
}
/**
* Method to update the displayed Moodle-Courses.
*/
private void updateCourses() {
view.setData(setData());
}
private void refreshCourseList() {
view.setCourses(courses());
}
private ObservableList<syncTableElement> setData() {
token = config.getMoodleToken();
if (isNull(courseContent) || isNull(course))
return FXCollections.observableArrayList();
try {
if (watcher != null)
watcher.close();
} catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.message");
}
List<Path> sectionList = List.of();
try {
//If no section is selected, or "all" are selected, directories are checked and coursecontent is set.
if (isNull(section) || section.getId() == -2) {
//Check if course-folder exists, otherwise create one.
Path courseDirectory =
Paths.get(config.getSyncRootPath() + "/" + course.getDisplayname());
FileService.directoryManager(courseDirectory);
sectionList = FileService.getPathsInDirectory(courseDirectory);
courseContent = sections();
} //Handling if a specific section is chosen.
else{
courseContent.clear();
courseContent.add(moodleService.getCourseContentSection(token, course.getId(),
section.getId()).get(0));
}
} catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.message");
}
ObservableList<syncTableElement> data = FXCollections.observableArrayList();
for (Section section : courseContent) {
try {
if (section.getId() != -2) {
String sectionName = section.getName();
int sectionNum = section.getSection();
int sectionId = section.getId();
data.add(new syncTableElement(sectionName, sectionId, sectionNum, sectionId, data.size(),
section.getSummary(), false, false, MoodleAction.ExistingSection,
section.getVisible() == 1));
sectionList = FileService.formatSectionFolder(sectionList, section);
//Section-directory is eventually created.
Path execute =
Paths.get(config.getSyncRootPath() + "/" + course.getDisplayname() + "/" + section.getSection() + "_" + sectionName);
FileService.directoryManager(execute);
//Initialize the fileServerRequired variable.
fileServerRequired = false;
List<FileServerFile> files = List.of();
//Watcher is added to choosen course-/ or section-directory.
watcher = new FileWatcher(new File(execute.toString()));
watcher.addListener(this);
watcher.watch();
//Categorize Moodle-, FileServer- and local-files.
try {
List<Path> fileList = FileService.getPathsInDirectory(execute);
for (Module module : section.getModules()) {
if (module.getModname().equals("resource")) {
ReturnValue resource = FileService.findModuleInFiles(fileList, module, sectionNum,
sectionId, data.size());
data.add(resource.getElement());
fileList = resource.getFileList();
}
else if (module.getModname().equals("url") && !config.getFormatsFileserver().isEmpty()) {
boolean found = false;
for (Path file : fileList) {
if (module.getName().equals(file.getFileName().toString())) {
found = true;
//File is found in the moodle-course
//Initialize the fileServer files.
if (!fileServerRequired) {
files = provideFileserverFiles(/*config.getRecentSection().getName()
*/ ""); //ToDo -> If there should be support for different upload-sections.
}
for (FileServerFile fileServerFile : files) {
if (fileServerFile.getFilename().equals(file.getFileName().toString())) {
//File additionally uploaded to fileserver
if (fileServerFile.getLastTimeModified() < Files.getLastModifiedTime(file).toMillis()) {
//File not up-to-date at fileserver
if (module.getAvailability() != null) {
var JsonB = new JsonConfigProvider().getContext(null);
JsonB.fromJson(module.getAvailability(),
ModuleAvailability.class);
LocalDateTime time =
LocalDateTime.ofInstant(Instant.ofEpochMilli(JsonB.fromJson(module.getAvailability().
replaceAll("\\\\", ""), ModuleAvailability.class).getTimeDateCondition().getT() * 1000L),
ZoneId.systemDefault());
data.add(new syncTableElement(module.getName(),
module.getId(), sectionNum, sectionId, data.size(),
module.getModname(), file, true, false,
MoodleAction.FTPSynchronize,
getPriorityVisibility(module.getVisible() == 1,
JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getConditionVisibility()),
new TimeDateElement(time.toLocalDate(), time.toLocalTime()), module.getId()));
} else {
data.add(new syncTableElement(module.getName(),
module.getId(), sectionNum, sectionId, data.size(),
module.getModname(), file, true, false,
MoodleAction.FTPSynchronize, module.getVisible() == 1));
}
fileList.remove(file);
break;
} else {
//File up to date at the fileserver
if (module.getAvailability() != null) {
var JsonB = new JsonConfigProvider().getContext(null);
JsonB.fromJson(module.getAvailability(),
ModuleAvailability.class);
LocalDateTime time =
LocalDateTime.ofInstant(Instant.ofEpochMilli(JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getTimeDateCondition().getT() * 1000L), ZoneId.systemDefault());
data.add(new syncTableElement(module.getName(),
module.getId(), sectionNum, sectionId, data.size(),
module.getModname(), file, false, false,
MoodleAction.ExistingFile,
getPriorityVisibility(module.getVisible() == 1,
JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""), ModuleAvailability.class)
.getConditionVisibility()), new TimeDateElement(time.toLocalDate(), time.toLocalTime())));
} else {
data.add(new syncTableElement(module.getName(),
module.getId(), sectionNum, sectionId, data.size(),
module.getModname(), file, false, false,
MoodleAction.ExistingFile, module.getVisible() == 1));
}
fileList.remove(file);
break;
}
}
}
}
}
if (!found) {
data.add(new syncTableElement(module.getName(), module.getId(), sectionNum,
sectionId, data.size(), module.getModname(), false, false,
MoodleAction.NotLocalFile, module.getVisible() == 1));
}
} else {
//Other modules which are not "url" or "resource".
data.add(new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId
, data.size(), module.getModname(), false, false, MoodleAction.NotLocalFile,
module.getVisible() == 1));
}
}
if (fileList.size() != 0) {
//All files inside here should be uploaded, if DataType is known.
for (Path path : fileList) {
if (contains(config.getFormatsMoodle().split(","),
FilenameUtils.getExtension(path.getFileName().toString()))) {
data.add(new syncTableElement(path.getFileName().toString(), -1, sectionNum,
sectionId, data.size(), "resource", path, true, false,
MoodleAction.MoodleUpload, true));
}
//More Complicated: all Files for the Fileserver-Upload (if new upload oder update)
// are found here:
else if ((contains(config.getFormatsFileserver().split(","),
FilenameUtils.getExtension(path.getFileName().toString()))) && !config.getFormatsFileserver().isBlank()) {
//Local files which are not uploaded to moodle.
if (!fileServerRequired) {
files = provideFileserverFiles(/*config.getRecentSection().getName()*/ "");
//ToDo -> If there should be support for different upload-sections.
}
//Array files containing all files uploaded to the fileserver (name and date).
for (FileServerFile fileServerFile : files) {
if (fileServerFile.getFilename().equals(path.getFileName())) {
if (fileServerFile.getLastTimeModified() < Files.getLastModifiedTime(path).toMillis()) {
//File to up-to-date
data.add(new syncTableElement(path.getFileName().toString(), -1,
sectionNum, sectionId, data.size(), "url", path, true, false,
MoodleAction.FTPUpload, true));
} else {
data.add(new syncTableElement(path.getFileName().toString(), -1,
sectionNum, sectionId, data.size(), "url", path, true, false,
MoodleAction.FTPLink, true));
}
}
}
//File not on moodle nor on fileserver
data.add(new syncTableElement(path.getFileName().toString(), -1, sectionNum,
sectionId, data.size(), "url", path, true, false, MoodleAction.FTPUpload,
true));
}
else {
data.add(new syncTableElement(path.getFileName().toString(), -1, sectionNum,
sectionId, data.size(), "resource", path, false, false,
MoodleAction.DatatypeNotKnown, false));
}
}
}
}
catch (Throwable e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.message");
}
}
}
catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.message");
}
}
if (!sectionList.isEmpty()) {
for (Path elem : sectionList) {
data.add(new syncTableElement(elem.getFileName().toString(), -1, -1, -1, data.size(), "section", elem
, true, false, MoodleAction.UploadSection, true));
}
}
courseData = data;
//Add FileWatcher
watcher = new FileWatcher(new File(config.getSyncRootPath() + "/" + course.getDisplayname()));
watcher.addListener(this);
watcher.watch();
return data;
}
/*private void deleteModule(syncTableElement element){
if(element.getAction() == MoodleAction.ExistingFile || element.getAction() == MoodleAction.NotLocalFile ||
element.getAction() == MoodleAction.MoodleSynchronize || element.getAction() == MoodleAction.FTPSynchronize){
if(element.getDelete()){
//Hier nochmal Nutzerdialog
context.getEventBus().post(new ShowPresenterCommand<>(ConfirmDeleteModulePresenter.class));
}
}
}*/
@Override
public void onCreated(FileEvent event) {
view.setData(setData());
}
@Override
public void onModified(FileEvent event) {
view.setData(setData());
}
@Override
public void onDeleted(FileEvent event) {
view.setData(setData());
}
private Boolean getPriorityVisibility(Boolean visible, Boolean availability) {
if (!visible || !availability) {
return false;
}
return true;
}
private void openCourseDirectory() {
Desktop desktop = Desktop.getDesktop();
try {
File dirToOpen = new File(config.getSyncRootPath() + "/" + course.getDisplayname());
desktop.open(dirToOpen);
} catch (Throwable e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.path.unknown.message");
}
}
public static boolean contains(final String[] arr, final String key) {
return Arrays.asList(arr).contains(key);
}
private List<FileServerFile> provideFileserverFiles(String pathname) throws Exception {
List<FileServerFile> files = List.of();
if (!VerifyDataService.validateString(config.getFileserver()) || !VerifyDataService.validateString(config.getUserFileserver()) ||
!VerifyDataService.validateString(config.getPasswordFileserver())) {
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.fileserver1.message");
} else {
try {
fileClient = new FileServerClientFTP(config);
fileClient.connect();
files = fileClient.getFiles(/*config.getRecentSection().getName()*/ ""); //ToDo -> If there should be
// support for different upload-sections.
fileClient.disconnect();
} catch (Exception e) {
logException(e, "Sync failed");
showNotification(NotificationType.ERROR, "start.sync.error.title", "start.sync.error.fileserver2.message");
}
}
fileServerRequired = true;
return files;
}
}

View file

@ -0,0 +1,21 @@
package moodle.sync.presenter.command;
import moodle.sync.presenter.SettingsPresenter;
import org.lecturestudio.core.presenter.command.ShowPresenterCommand;
import org.lecturestudio.core.view.Action;
public class ShowSettingsCommand extends ShowPresenterCommand<SettingsPresenter> {
private final Action closeAction;
public ShowSettingsCommand(Action closeAction) {
super(SettingsPresenter.class);
this.closeAction = closeAction;
}
@Override
public void execute(SettingsPresenter presenter) {
presenter.setOnClose(closeAction);
}
}

View file

@ -0,0 +1,188 @@
package moodle.sync.util;
import moodle.sync.javafx.model.ReturnValue;
import moodle.sync.javafx.model.TimeDateElement;
import moodle.sync.javafx.model.syncTableElement;
import moodle.sync.core.model.json.JsonConfigProvider;
import moodle.sync.core.model.json.Module;
import moodle.sync.core.model.json.ModuleAvailability;
import moodle.sync.core.model.json.Section;
import moodle.sync.core.util.MoodleAction;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Class implementing several methods in terms of file handling and comparison.
*
* @author Daniel Schröter
*/
public class FileService {
/**
* Secures that a directory given in a given path exists. Therefore the directory could be created.
*
* @param p Path of the directory.
*/
public static void directoryManager(Path p) throws Exception {
Files.createDirectories(p);
}
public static List<Path> formatSectionFolder(List<Path> sectionList, Section section) {
int remove = -1;
for (int i = 0; i < sectionList.size(); i++) {
String[] sectionFolder = sectionList.get(i).getFileName().toString().split("_", 2);
if (sectionFolder[sectionFolder.length - 1].equals(section.getName())) {
File temp = new File(sectionList.get(i).toString());
temp.renameTo(new File((sectionList.get(i).getParent().toString() + "/" + section.getSection() + "_" + section.getName())));
remove = i;
break;
}
}
if (remove != -1) {
sectionList.remove(remove);
}
return sectionList;
}
/**
* Obtaining a list containing all paths inside a directory.
*
* @param p Path of the directory.
* @return list of all paths inside the directory.
*/
public static List<Path> fileServerRequired(Path p) throws IOException {
List<Path> result = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
for (Path entry : stream) {
result.add(entry);
}
} catch (DirectoryIteratorException ex) {
// I/O error encountered during the iteration, the cause is an IOException.
throw ex.getCause();
}
return result;
}
public static List<Path> getPathsInDirectory(Path p) throws IOException {
List<Path> result = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
for (Path entry : stream) {
//When an element is a directory, a recursive-call of this method is made.
result.add(entry);
}
} catch (DirectoryIteratorException ex) {
// I/O error encounted during the iteration, the cause is an IOException
throw ex.getCause();
}
return result;
}
//Returns List with all Moodle-Moduls of a choosen Module-type
/**
* Method to sort the Modules inside a Section by Module-type.
*
* @param type1 Module-type searched for.
* @param section Section containing a list of Modules.
* @return a list of Modules of the searched Module-type.
*/
public static List<Module> getModulesByType(String type1, Section section) {
List<Module> modules = new ArrayList<>();
for (int i = 0; i < section.getModules().size(); i++) {
if (section.getModules().get(i).getModname().equals(type1)) {
modules.add(section.getModules().get(i));
}
}
return modules;
}
public static ReturnValue findModuleInFiles(List<Path> fileList, Module module, int sectionNum, int sectionId,
int position /* Substitute data.size()*/) throws Exception {
syncTableElement element = null;
boolean found = false;
for (int i = 0; i < fileList.size(); i++) {
if (fileList.get(i).getFileName().toString().equals(module.getContents().get(0).getFilename())) {
long onlinemodified = module.getContents().get(0).getTimemodified() * 1000;
long filemodified = Files.getLastModifiedTime(fileList.get(i)).toMillis();
//Check if local file is newer.
if (filemodified > onlinemodified) {
found = true;
if (module.getAvailability() != null) {
var JsonB = new JsonConfigProvider().getContext(null);
JsonB.fromJson(module.getAvailability(), ModuleAvailability.class);
LocalDateTime time =
LocalDateTime.ofInstant(Instant.ofEpochMilli(JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getTimeDateCondition().getT() * 1000L), ZoneId.systemDefault());
element = new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId,
position, module.getModname(), fileList.get(i), true, false,
MoodleAction.MoodleSynchronize, getPriorityVisibility(module.getVisible() == 1,
JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getConditionVisibility()),
new TimeDateElement(time.toLocalDate(), time.toLocalTime()), module.getId());
} else {
element = new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId,
position, module.getModname(), fileList.get(i), true, false,
MoodleAction.MoodleSynchronize, module.getVisible() == 1, module.getId());
}
fileList.remove(i);
break;
} else {
found = true;
if (module.getAvailability() != null) {
var JsonB = new JsonConfigProvider().getContext(null);
JsonB.fromJson(module.getAvailability(), ModuleAvailability.class);
LocalDateTime time =
LocalDateTime.ofInstant(Instant.ofEpochMilli(JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getTimeDateCondition().getT() * 1000L), ZoneId.systemDefault());
element = new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId,
position, module.getModname(), fileList.get(i), false, false,
MoodleAction.ExistingFile, getPriorityVisibility(module.getVisible() == 1,
JsonB.fromJson(module.getAvailability().replaceAll("\\\\", ""),
ModuleAvailability.class).getConditionVisibility()),
new TimeDateElement(time.toLocalDate(), time.toLocalTime()));
}
else {
element = new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId,
position, module.getModname(), fileList.get(i), false, false,
MoodleAction.ExistingFile, module.getVisible() == 1);
}
fileList.remove(i);
break;
}
}
}
if (!found) {
element = new syncTableElement(module.getName(), module.getId(), sectionNum, sectionId, position,
module.getModname(), false, false, MoodleAction.NotLocalFile, module.getVisible() == 1);
}
return new ReturnValue(fileList, element);
}
private static Boolean getPriorityVisibility(Boolean visible, Boolean availability) {
if (!visible || !availability) {
return false;
}
return true;
}
public static boolean contains(final String[] arr, final String key) {
return Arrays.asList(arr).contains(key);
}
}

View file

@ -0,0 +1,85 @@
package moodle.sync.util;
import moodle.sync.core.model.json.Course;
import moodle.sync.core.model.json.MoodleUpload;
import moodle.sync.javafx.model.syncTableElement;
import moodle.sync.core.util.MoodleAction;
import moodle.sync.core.web.service.MoodleService;
import moodle.sync.core.web.service.MoodleUploadTemp;
import java.io.File;
import java.nio.file.Path;
public final class SetModuleService {
public static void publishResource(MoodleService moodleService, syncTableElement courseData, Course course,
String url, String token) throws Exception {
if (courseData.getAction() == MoodleAction.MoodleUpload) {
MoodleUploadTemp uploader = new MoodleUploadTemp();
//Upload of the file to the Moodle-platform.
MoodleUpload upload = uploader.upload(courseData.getExistingFileName(), courseData.getExistingFile(), url
, token);
//Publish it in the Moodle-course.
if (courseData.getUnixTimeStamp() > System.currentTimeMillis() / 1000L) {
//Time in future
moodleService.setResource(token, course.getId(), courseData.getSection(), upload.getItemid(),
courseData.getUnixTimeStamp(), courseData.getVisible(), courseData.getModuleName(),
courseData.getBeforemod());
}
else {
//Time not modified, time should be null
moodleService.setResource(token, course.getId(), courseData.getSection(), upload.getItemid(), null,
courseData.getVisible(), courseData.getModuleName(), courseData.getBeforemod());
}
}
else if (courseData.getAction() == MoodleAction.MoodleSynchronize) {
MoodleUploadTemp uploader = new MoodleUploadTemp();
//Upload of the new file to the Moodle-platform.
MoodleUpload upload = uploader.upload(courseData.getExistingFileName(), courseData.getExistingFile(), url
, token);
//Publish it in the Moodle-course above the old course-module containing the old file.
if (courseData.getUnixTimeStamp() > System.currentTimeMillis() / 1000L) {
moodleService.setResource(token, course.getId(), courseData.getSection(), upload.getItemid(),
courseData.getUnixTimeStamp(), courseData.getVisible(), courseData.getModuleName(),
courseData.getBeforemod());
}
else {
moodleService.setResource(token, course.getId(), courseData.getSection(), upload.getItemid(), null,
courseData.getVisible(), courseData.getModuleName(), courseData.getBeforemod());
}
//Removal of the old course-module.
moodleService.removeResource(token, courseData.getCmid());
} else {
moodleService.setMoveModule(token, courseData.getCmid(), courseData.getSectionId(),
courseData.getBeforemod());
}
}
public static void publishFileserverResource(MoodleService moodleService, syncTableElement courseData,
Course course, String token) throws Exception {
//TODO: konkreter fileserver upload hinzufügen
// url = fileservice.....
String url = "https://wikipedia.org";
if (courseData.getUnixTimeStamp() > System.currentTimeMillis() / 1000L) {
moodleService.setUrl(token, course.getId(), courseData.getSection(), courseData.getModuleName(), url,
courseData.getUnixTimeStamp(), courseData.getVisible(), courseData.getBeforemod());
} else {
moodleService.setUrl(token, course.getId(), courseData.getSection(), courseData.getModuleName(), url,
null, courseData.getVisible(), courseData.getBeforemod());
}
}
public static void moveResource(MoodleService moodleService, syncTableElement courseData, String token) throws Exception {
moodleService.setMoveModule(token, courseData.getCmid(), courseData.getSectionId(), courseData.getBeforemod());
}
public static void createSection(MoodleService moodleService, syncTableElement courseData, Course course,
String token) throws Exception {
moodleService.setSection(token, course.getId(), courseData.getModuleName(), courseData.getSection());
if (!courseData.getExistingFileName().split("_", 2)[0].matches("\\d+")) {
File temp = new File(courseData.getExistingFile());
temp.renameTo(new File(Path.of(courseData.getExistingFile()).getParent().toString() + "/" + courseData.getSection() + "_" + courseData.getExistingFileName()));
}
}
}

View file

@ -0,0 +1,27 @@
package moodle.sync.util;
import javafx.scene.control.TextFormatter;
import java.util.function.UnaryOperator;
/**
* Class implementing a method used for input validation.
*
* @authod Daniel Schröter
*/
public class UserInputValidations {
/**
* Providing operation to check whether an input is a number. If not, the input is not printed.
*
* returns if the input is a number, the number otherwise an empty string.
*/
public static UnaryOperator<TextFormatter.Change> numberValidationFormatter = change -> {
//If change is not a number, change input to an empty string.
if (!change.getControlNewText().matches("\\d+")) {
change.setText("");
}
return change;
};
}

View file

@ -0,0 +1,15 @@
package moodle.sync.util;
/**
* Class used to implement Methods to verify data.
*/
public final class VerifyDataService {
public static boolean validateString(String string){
if(string == null || string.isEmpty() || string.isBlank()) {
return false;
} else {
return true;
}
}
}

View file

@ -0,0 +1,34 @@
package moodle.sync.view;
import java.util.function.Predicate;
import org.lecturestudio.core.geometry.Rectangle2D;
import org.lecturestudio.core.input.KeyEvent;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.View;
import org.lecturestudio.core.view.ViewLayer;
/**
* Interface defining the functions of the "main-window".
*/
public interface MainView extends View {
Rectangle2D getBounds();
void close();
void hide();
void removeView(View view, ViewLayer layer);
void showView(View view, ViewLayer layer);
void setFullscreen(boolean fullscreen);
void setOnKeyEvent(Predicate<KeyEvent> action);
void setOnShown(Action action);
void setOnClose(Action action);
}

View file

@ -0,0 +1,46 @@
package moodle.sync.view;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.beans.StringProperty;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.View;
import java.util.List;
import java.util.Locale;
/**
* Interface defining the functions of the "settings-page".
*
* @author Daniel Schröter
*/
public interface SettingsView extends View {
void setOnExit(Action action);
void setLocale(ObjectProperty<Locale> locale);
void setLocales(List<Locale> locales);
void setMoodleField(StringProperty moodleURL);
void setFormatsMoodle(StringProperty moodleformats);
void setFormatsFileserver(StringProperty fileserverformats);
void setFtpField(StringProperty ftpURL);
void setFtpPort(StringProperty ftpPort);
void setFtpUser(StringProperty ftpUser);
void setFtpPassword(StringProperty ftpPassword);
void setMoodleToken(StringProperty moodleToken);
void setSyncRootPath(StringProperty path);
void setSelectSyncRootPath(Action action);
void setShowUnknownFormats(BooleanProperty unknownFormats);
}

View file

@ -0,0 +1,43 @@
package moodle.sync.view;
import javafx.collections.ObservableList;
import moodle.sync.core.model.json.Course;
import moodle.sync.javafx.model.syncTableElement;
import moodle.sync.core.model.json.Section;
import org.lecturestudio.core.beans.BooleanProperty;
import org.lecturestudio.core.beans.ObjectProperty;
import org.lecturestudio.core.view.Action;
import org.lecturestudio.core.view.ConsumerAction;
import org.lecturestudio.core.view.View;
import java.util.List;
/**
* Interface defining the functions of the "start-page".
*
* @author Daniel Schröter
*/
public interface StartView extends View {
void setOnUpdate(Action action);
void setOnSync(Action action);
void setOnSettings(Action action);
void setOnFolder(Action action);
void setSelectAll(BooleanProperty selectAll);
void setCourses(List<Course> courses);
void setCourse(ObjectProperty<Course> course);
void setSections(List<Section> sections);
void setSection(ObjectProperty<Section> section);
void setOnCourseChanged(ConsumerAction<Course> action);
void setData(ObservableList<syncTableElement> data);
}

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="filename">moodle-sync</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p %c{1}:%L - %m%n" />
</Console>
<RollingFile
name="FILE"
fileName="${sys:logFilePath}/${filename}-${sys:logAppVersion}.log"
filePattern="${sys:logFilePath}/${filename}-${sys:logAppVersion}-%d{MM-dd-yyyy}-%i.log"
immediateFlush="true">
<PatternLayout pattern="%d %-5p %c{1}:%L - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy>
<Delete basePath="${sys:logFilePath}" maxDepth="1">
<IfFileName glob="${filename}-*.log"/>
<IfAccumulatedFileCount exceeds="3"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="moodle.sync.core.web.json" level="debug" />
<Logger name="org.lecturestudio" level="error" />
<Logger name="org.jboss" level="error" />
<Root level="info">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="FILE" />
</Root>
</Loggers>
</Configuration>

View file

@ -0,0 +1,2 @@
moodle.api.url = http://localhost/webservice/rest/
moodle.upload.url = http://localhost/webservice/

View file

@ -0,0 +1,5 @@
.text-input:error {
-fx-border-insets: 0;
-fx-border-width: 2px;
-fx-border-color: red;
}

View file

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!--
In this document, the layout of the "settings-page" is defined
@author Daniel Schröter
-->
<?import org.lecturestudio.javafx.factory.LocaleListCell?>
<?import org.lecturestudio.javafx.factory.LocaleCellFactory?>
<fx:root alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="VBox" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label alignment="TOP_CENTER" text="%settings.settings">
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding></Label>
<GridPane alignment="CENTER" vgap="15.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="150.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="300.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="17.0" prefWidth="156.0" text="%settings.language" />
<Label prefHeight="17.0" prefWidth="156.0" text="%settings.root" GridPane.rowIndex="1" />
<TextField fx:id="syncRootPath" prefHeight="25.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Button fx:id="syncRootPathButton" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" text="%settings.search" GridPane.columnIndex="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" />
</GridPane.margin></Button>
<ComboBox fx:id="languageCombo" prefWidth="150.0" GridPane.columnIndex="1">
<buttonCell>
<LocaleListCell/>
</buttonCell>
<cellFactory>
<LocaleCellFactory/>
</cellFactory>
</ComboBox>
</children>
</GridPane>
<Label prefWidth="612.0" styleClass="text-head" text="%settings.lmslabel">
<padding>
<Insets bottom="5.0" right="5.0" top="10.0" />
</padding>
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
</Label>
<GridPane alignment="CENTER" vgap="15.0">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="150.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="300.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%settings.token" GridPane.rowIndex="1" />
<TextField fx:id="tokenField" prefHeight="25.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="%settings.lms" />
<TextField fx:id="moodleField" prefHeight="25.0" prefWidth="300.0" GridPane.columnIndex="1" />
<Label text="%settings.dataformatsmoodle" GridPane.rowIndex="2" />
<TextArea fx:id="formatsMoodle" GridPane.columnIndex="1" GridPane.rowIndex="2">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" top="12.0" />
</GridPane.margin>
</TextArea>
</children>
</GridPane>
<Label prefWidth="612.0" styleClass="text-head" text="%settings.ftplabel">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" right="5.0" top="10.0" />
</padding>
</Label>
<GridPane alignment="CENTER" vgap="10.0">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="150.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="300.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%settings.ftp" />
<Label text="%settings.ftpusername" GridPane.rowIndex="1" />
<Label text="%settings.ftppassword" GridPane.rowIndex="2" />
<TextField fx:id="ftpUser" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextField fx:id="ftpPassword" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="%settings.dataformatsfilesverer" GridPane.rowIndex="3" />
<TextArea fx:id="formatsFileserver" GridPane.columnIndex="1" GridPane.rowIndex="3">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" top="15.0" />
</GridPane.margin>
</TextArea>
<TextField fx:id="ftpPort" prefHeight="25.0" prefWidth="100.0" GridPane.columnIndex="2">
<GridPane.margin>
<Insets left="5.0" />
</GridPane.margin>
</TextField>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="300.0" GridPane.columnIndex="1">
<children>
<TextField fx:id="ftpField" prefHeight="25.0" prefWidth="240.0" />
<Label alignment="CENTER_RIGHT" contentDisplay="RIGHT" prefHeight="25.0" prefWidth="60.0" text="%settings.port" />
</children>
</HBox>
</children>
</GridPane>
<GridPane prefHeight="45.0" prefWidth="612.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<CheckBox fx:id="showUnknownFormats" mnemonicParsing="false" text="%settings.showUnknownFormats">
<GridPane.margin>
<Insets top="5.0" />
</GridPane.margin>
</CheckBox>
</children>
</GridPane>
<HBox alignment="CENTER" spacing="50.0">
<children>
<Button fx:id="closesettingsButton" mnemonicParsing="false" text="%settings.close">
<HBox.margin>
<Insets top="2.0" />
</HBox.margin></Button>
</children>
<padding>
<Insets top="15.0" />
</padding>
</HBox>
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</fx:root>

View file

@ -0,0 +1,16 @@
settings.search = Durchsuchen
settings.close = Schlie\u00DFen
settings.root = Rootverzeichnis
settings.token = Token
settings.lms = URL
settings.ftp = URL
settings.ftpusername = Benutzername
settings.ftppassword = Passwort
settings.dataformatsmoodle = Dateiformate
settings.dataformatsfilesverer = Dateiformate
settings.settings = Einstellungen
settings.lmslabel = Moodle-Plattform
settings.ftplabel = Fileserver
settings.port = Port
settings.showUnknownFormats = Dateien mit unbekannten Dateiformaten anzeigen
settings.language = Sprache

View file

@ -0,0 +1,16 @@
settings.search = Search Directory
settings.close = Close
settings.root = Root directory
settings.token = Token
settings.lms = URL
settings.ftp = URL
settings.ftpusername = Username
settings.ftppassword = Password
settings.dataformatsmoodle = Fileformats
settings.dataformatsfilesverer = Fileformats
settings.settings = Settings
settings.lmslabel = Moodle-Platform
settings.ftplabel = Fileserver
settings.port = Port
settings.showUnknownFormats = Show Files with unknown Fileformats
settings.language = Language

View file

@ -0,0 +1,93 @@
.main-start .container {
-fx-spacing: 10;
-fx-padding: 30;
}
.folderButton{
-fx-icon-content: "m.5 3 .04.87a1.99 1.99 0 0 0-.342 1.311l.637 7A2 2 0 0 0 2.826 14H9v-1H2.826a1 1 0 0 1-.995-.91l-.637-7A1 1 0 0 1 2.19 4h11.62a1 1 0 0 1 .996 1.09L14.54 8h1.005l.256-2.819A2 2 0 0 0 13.81 3H9.828a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 6.172 1H2.5a2 2 0 0 0-2 2zm5.672-1a1 1 0 0 1 .707.293L7.586 3H2.19c-.24 0-.47.042-.683.12L1.5 2.98a1 1 0 0 1 1-.98h3.672z M13.5 10a.5.5 0 0 1 .5.5V12h1.5a.5.5 0 1 1 0 1H14v1.5a.5.5 0 1 1-1 0V13h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z";
}
.unavailable-icon{
-fx-icon-content: "M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z";
}
.assignment-icon{
-fx-icon-content: "M8 11a.5.5 0 0 0 .5-.5V6.707l1.146 1.147a.5.5 0 0 0 .708-.708l-2-2a.5.5 0 0 0-.708 0l-2 2a.5.5 0 1 0 .708.708L7.5 6.707V10.5a.5.5 0 0 0 .5.5z M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z";
}
.chat-icon{
-fx-icon-content: "M11.176 14.429c-2.665 0-4.826-1.8-4.826-4.018 0-2.22 2.159-4.02 4.824-4.02S16 8.191 16 10.411c0 1.21-.65 2.301-1.666 3.036a.324.324 0 0 0-.12.366l.218.81a.616.616 0 0 1 .029.117.166.166 0 0 1-.162.162.177.177 0 0 1-.092-.03l-1.057-.61a.519.519 0 0 0-.256-.074.509.509 0 0 0-.142.021 5.668 5.668 0 0 1-1.576.22ZM9.064 9.542a.647.647 0 1 0 .557-1 .645.645 0 0 0-.646.647.615.615 0 0 0 .09.353Zm3.232.001a.646.646 0 1 0 .546-1 .645.645 0 0 0-.644.644.627.627 0 0 0 .098.356Z M0 6.826c0 1.455.781 2.765 2.001 3.656a.385.385 0 0 1 .143.439l-.161.6-.1.373a.499.499 0 0 0-.032.14.192.192 0 0 0 .193.193c.039 0 .077-.01.111-.029l1.268-.733a.622.622 0 0 1 .308-.088c.058 0 .116.009.171.025a6.83 6.83 0 0 0 1.625.26 4.45 4.45 0 0 1-.177-1.251c0-2.936 2.785-5.02 5.824-5.02.05 0 .1 0 .15.002C10.587 3.429 8.392 2 5.796 2 2.596 2 0 4.16 0 6.826Zm4.632-1.555a.77.77 0 1 1-1.54 0 .77.77 0 0 1 1.54 0Zm3.875 0a.77.77 0 1 1-1.54 0 .77.77 0 0 1 1.54 0Z";
}
.feedback-icon{
-fx-icon-content: "M13 2.5a1.5 1.5 0 0 1 3 0v11a1.5 1.5 0 0 1-3 0v-.214c-2.162-1.241-4.49-1.843-6.912-2.083l.405 2.712A1 1 0 0 1 5.51 15.1h-.548a1 1 0 0 1-.916-.599l-1.85-3.49a68.14 68.14 0 0 0-.202-.003A2.014 2.014 0 0 1 0 9V7a2.02 2.02 0 0 1 1.992-2.013 74.663 74.663 0 0 0 2.483-.075c3.043-.154 6.148-.849 8.525-2.199V2.5zm1 0v11a.5.5 0 0 0 1 0v-11a.5.5 0 0 0-1 0zm-1 1.35c-2.344 1.205-5.209 1.842-8 2.033v4.233c.18.01.359.022.537.036 2.568.189 5.093.744 7.463 1.993V3.85zm-9 6.215v-4.13a95.09 95.09 0 0 1-1.992.052A1.02 1.02 0 0 0 1 7v2c0 .55.448 1.002 1.006 1.009A60.49 60.49 0 0 1 4 10.065zm-.657.975 1.609 3.037.01.024h.548l-.002-.014-.443-2.966a68.019 68.019 0 0 0-1.722-.082z";
}
.file-icon{
-fx-icon-content: "M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z";
}
.folder-icon{
-fx-icon-content: "M.54 3.87.5 3a2 2 0 0 1 2-2h3.672a2 2 0 0 1 1.414.586l.828.828A2 2 0 0 0 9.828 3h3.982a2 2 0 0 1 1.992 2.181l-.637 7A2 2 0 0 1 13.174 14H2.826a2 2 0 0 1-1.991-1.819l-.637-7a1.99 1.99 0 0 1 .342-1.31zM2.19 4a1 1 0 0 0-.996 1.09l.637 7a1 1 0 0 0 .995.91h10.348a1 1 0 0 0 .995-.91l.637-7A1 1 0 0 0 13.81 4H2.19zm4.69-1.707A1 1 0 0 0 6.172 2H2.5a1 1 0 0 0-1 .981l.006.139C1.72 3.042 1.95 3 2.19 3h5.396l-.707-.707z";
}
.forum-icon{
-fx-icon-content: "M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z M3 3.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 6a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 6zm0 2.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z";
}
.label-icon{
-fx-icon-content: "M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0z M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586 7 7L13.586 9l-7-7H2v4.586z";
}
.quiz-icon{
-fx-icon-content: "M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.235.235 0 0 1 .02-.022z";
}
.survey-icon{
-fx-icon-content: "M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z";
}
.url-icon{
-fx-icon-content: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z";
}
.other-icon{
-fx-icon-content: "M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z";
}
.ftp-icon{
-fx-icon-content: "M4.5 11a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 10.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z M16 11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V9.51c0-.418.105-.83.305-1.197l2.472-4.531A1.5 1.5 0 0 1 4.094 3h7.812a1.5 1.5 0 0 1 1.317.782l2.472 4.53c.2.368.305.78.305 1.198V11zM3.655 4.26 1.592 8.043C1.724 8.014 1.86 8 2 8h12c.14 0 .276.014.408.042L12.345 4.26a.5.5 0 0 0-.439-.26H4.094a.5.5 0 0 0-.44.26zM1 10v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1z";
}
.trash-icon{
-fx-icon-content: "M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5ZM11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H2.506a.58.58 0 0 0-.01 0H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1h-.995a.59.59 0 0 0-.01 0H11Zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5h9.916Zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47ZM8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5Z";
}
.check-box .box {
-fx-background-color: white;
-fx-border-color:grey;
-fx-border-radius:3px;
}
.check-box:selected .mark {
-fx-background-color: white;
}
.check-box:selected .box {
-fx-background-color: #28a3f4;
}
.table-column .label {
-fx-content-display: right ;
}
.table-column {
-fx-cell-size: 40;
}
.headerstyle.table-row-cell {
}
.headerstyle.table-row-cell .table-cell {
-fx-border-width: 0;
}
.popUpTextArea {
-fx-text-fill: black;
}

View file

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!--
In this document, the layout of the "start-page" is defined
@author Daniel Schröter
-->
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import org.lecturestudio.javafx.layout.DynamicResizePolicy?>
<?import org.lecturestudio.javafx.layout.ColumnSizeConstraints?>
<?import org.lecturestudio.javafx.control.SvgIcon?>
<?import moodle.sync.javafx.custom.CourseListCell?>
<?import moodle.sync.javafx.custom.CourseCellFactory?>
<?import moodle.sync.javafx.custom.SectionListCell?>
<?import moodle.sync.javafx.custom.SectionCellFactory?>
<?import moodle.sync.javafx.custom.DragAndDropRowFactory?>
<?import moodle.sync.javafx.custom.HighlightSectionCellFactory?>
<?import moodle.sync.javafx.custom.StatusCellFactory?>
<?import moodle.sync.javafx.custom.CheckBoxTableCellFactory?>
<?import moodle.sync.javafx.custom.UploadElementCellValueFactory?>
<?import moodle.sync.javafx.custom.AvailabilityCellFactory?>
<?import moodle.sync.javafx.custom.AvailabilityCellValueFactory?>
<?import moodle.sync.javafx.custom.AvailableDateTimeTableCellFactory?>
<fx:root alignment="CENTER" type="VBox" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox alignment="CENTER_LEFT" spacing="5.0">
<VBox alignment="CENTER">
<Button fx:id="folderButton" mnemonicParsing="false">
<graphic>
<SvgIcon styleClass="icon, folderButton"/>
</graphic>
</Button>
</VBox>
<VBox>
<Label text ="%start.selectcourse"/>
<ComboBox fx:id="courseCombo">
<buttonCell>
<CourseListCell/>
</buttonCell>
<cellFactory>
<CourseCellFactory/>
</cellFactory>
</ComboBox>
</VBox>
<VBox>
<Label text ="%start.selectsection"/>
<ComboBox fx:id="sectionCombo">
<buttonCell>
<SectionListCell/>
</buttonCell>
<cellFactory>
<SectionCellFactory/>
</cellFactory>
</ComboBox>
</VBox>
<Pane HBox.hgrow="ALWAYS"/>
<Button fx:id="updateButton" mnemonicParsing="false" text="%start.update"/>
<Button fx:id="syncButton" mnemonicParsing="false" text="%start.sync"/>
<Button fx:id="settingsButton" mnemonicParsing="false" text="%start.settings"/>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</padding>
</HBox>
</top>
<center>
<TableView fx:id="syncTable" BorderPane.alignment="CENTER" editable="true" prefHeight="27.0">
<rowFactory>
<DragAndDropRowFactory/>
</rowFactory>
<columnResizePolicy>
<DynamicResizePolicy tableView="$syncTable">
<columnConstraints>
<ColumnSizeConstraints percentWidth="0.495"/>
<ColumnSizeConstraints percentWidth="0.495"/>
<ColumnSizeConstraints prefWidth="90.0"/>
<ColumnSizeConstraints prefWidth="85.0"/>
<ColumnSizeConstraints prefWidth="200.0"/>
</columnConstraints>
</DynamicResizePolicy>
</columnResizePolicy>
<columns>
<TableColumn fx:id="courseViewTableColumn" text="Moodle">
<cellFactory>
<HighlightSectionCellFactory/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="moduleName"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="localViewTableColumn" text="%start.local">
<cellFactory>
<StatusCellFactory/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="existingFileName"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="executeTableColumn" styleClass="CENTER" editable="true" text="%start.execute">
<graphic>
<CheckBox fx:id="allSelected" mnemonicParsing="false" />
</graphic>
<cellFactory>
<CheckBoxTableCellFactory/>
</cellFactory>
<cellValueFactory>
<UploadElementCellValueFactory/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="availabilityTableColumn" styleClass="CENTER" editable="true" text="%start.visible">
<cellFactory>
<AvailabilityCellFactory/>
</cellFactory>
<cellValueFactory>
<AvailabilityCellValueFactory/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="availabilityTimeTableColumn" styleClass="CENTER" editable="true" text="%start.availability">
<cellFactory>
<AvailableDateTimeTableCellFactory/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="availabilityDateTime"/>
</cellValueFactory>
</TableColumn>
<!--- <TableColumn fx:id="deleteColumn" styleClass="Center" editable="false" text="%start.delete">
<cellFactory>
<DeleteButtonCellFactory/>
</cellFactory>
</TableColumn> -->
</columns>
<BorderPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</BorderPane.margin>
</TableView>
</center>
</BorderPane>
</fx:root>

View file

@ -0,0 +1,22 @@
start.exit = Beenden
start.sync = Synchronisieren
start.update = Aktualisieren
start.local = Lokal
start.execute = Ausf\u00FChren
start.visible = Sichtbarkeit
start.availability = Zeitpunkt
start.sync.showall = Alle anzeigen
start.sync.error.title = Fehler
start.sync.error.message = Synchronisierung fehlgeschlagen. Bitte versuchen Sie es erneut.
start.settings = Einstellungen
start.selectcourse = Kurs
start.selectsection = Sektion
start.delete = Löschen
start.sync.error.fileserver1.message = Fileserver Angaben unvollst\u00E4ndig. Bitte in den Einstellungen erg\u00E4nzen.
start.sync.error.fileserver2.message = Verbindungsaufbau zum Fileserver fehlgeschlagen. Bitte Einstellungen \u00FCberpr\u00FCfen.
start.sync.error.course.message = Synchronisierung fehlgeschlagen. Bitte Kurs ausw\u00E4hlen.
start.sync.error.section.message = Synchronisierung fehlgeschlagen. Bitte Kurssektion ausw\u00E4hlen.
start.sync.error.invalidurl.message = Fehler beim Verbindungsaufbau zur Moodle-Plattform. Bitte angegebene URL und Token \u00FCberpr\u00FCfen.
start.sync.error.path.message = Root-Verzeichnis fehlerhaft. Bitte in den Einstellungen korrigieren.
start.sync.error.path.unknown.message = Kein Kurs ausgew\u00E4hlt. Bitte Kurs w\u00E4hlen.
start.sync.error.upload.message = Upload nicht m\u00F6glich. Bitte Dateigr\u00F6\u00DFe, Moodle-Berechtigungen und Internetverbindung \u00FCberpr\u00FCfen.\nBetreffende Datei: {0}

View file

@ -0,0 +1,22 @@
start.exit = Exit
start.sync = Sync
start.update = Update
start.local = Local
start.execute = Execute
start.visible = Visibility
start.sync.showall = Show all
start.availability = Availability
start.sync.error.title = Error
start.sync.error.message = Synchronization failed. Please try again.
start.settings = Settings
start.selectcourse = Course
start.selectsection = Section
start.delete = Delete
start.sync.error.fileserver1.message = Fileserver settings incomplete.
start.sync.error.fileserver2.message = Connecting to fileserver was failed. Please check settings.
start.sync.error.course.message = Synchronization failed. Please choose a course.
start.sync.error.section.message = Synchronization failed. Please choose a section.
start.sync.error.invalidurl.message = Error when connecting to Moodle. Please check URL and Token.
start.sync.error.path.message = Root-Directory faulty. Please correct in settings.
start.sync.error.path.unknown.message = No course chosen. Please choose a course.
start.sync.error.upload.message = Upload to Moodle not possible, check permissions and upload size. File:

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>
<?import moodle.sync.javafx.view.FxStartView?>
<!--
In this document, the layout of the "main-window" is defined
-->
<fx:root type="StackPane" prefWidth="800" prefHeight="600" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<stylesheets>
<String fx:value="/resources/css/base.css" />
</stylesheets>
<BorderPane fx:id="contentPane">
<center>
<FxStartView />
</center>
</BorderPane>
</fx:root>

View file

@ -0,0 +1,17 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 http://maven.apache.org/xsd/assembly-2.1.1.xsd">
<id>bundle</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.parent.build.directory}/${package.bundle.dir}</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View file

@ -0,0 +1,17 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 http://maven.apache.org/xsd/assembly-2.1.1.xsd">
<id>default</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.parent.build.directory}/${package.dir}</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-package-archive</artifactId>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<package.dir>${package.name}-${os.name}</package.dir>
<package.bundle.dir>${package.name}-bundle-${os.name}</package.bundle.dir>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>default</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<finalName>${package.name}-${package.version}-${os.name}</finalName>
</configuration>
</execution>
<execution>
<id>bundle</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly-bundle.xml</descriptor>
</descriptors>
<finalName>${package.name}-bundle-${package.version}-${os.name}</finalName>
</configuration>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>${project.parent.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-package-cli</artifactId>
<packaging>jpackage</packaging>
<properties>
<app.name>moodle-sync-cli</app.name>
<app.name.parent>moodle-sync-bundle-${os.name}</app.name.parent>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-jlink-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<mainClass>moodle.sync.cli.CommandLineInterface</mainClass>
<variables>
<appName>moodle-sync-cli</appName>
<appVersion>${package.version}</appVersion>
<appVendor>TU Darmstadt</appVendor>
<appCopyright>Copyright © 2022 TU Darmstadt</appCopyright>
</variables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<mkdir dir="${project.basedir}/../target/${app.name.parent}" />
<copy todir="${project.basedir}/../target/${app.name.parent}">
<fileset dir="${project.build.directory}/jpackage/${app.name}" />
</copy>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync-cli</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,13 @@
<#-- template to create the options file for the jpackage tool to create the application image -->
<#if osName?upper_case?contains("WIN")>
--win-console
<#elseif osName?upper_case?contains("MAC")>
<#else>
</#if>
--name ${appName}
--app-version "${appVersion}"
--vendor "${appVendor}"
--copyright "${appCopyright}"

View file

@ -0,0 +1,8 @@
<#-- template to create the options file for the jpackage tool to create the installer -->
<#if osName?upper_case?contains("WIN")>
<#elseif osName?upper_case?contains("MAC")>
<#else>
</#if>

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>moodle-sync-package-fx</artifactId>
<packaging>jpackage</packaging>
<properties>
<app.name>moodle-sync-fx</app.name>
<app.name.parent>moodle-sync-bundle-${os.name}</app.name.parent>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-jlink-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<mainClass>moodle.sync.javafx.SyncApplication</mainClass>
<variables>
<appName>moodle-sync-fx</appName>
<appVersion>${package.version}</appVersion>
<appVendor>TU Darmstadt</appVendor>
<appCopyright>Copyright © 2022 TU Darmstadt</appCopyright>
</variables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<mkdir dir="${project.basedir}/../target/${app.name.parent}" />
<copy todir="${project.basedir}/../target/${app.name.parent}">
<fileset dir="${project.build.directory}/jpackage/${app.name}" />
</copy>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync-fx</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,13 @@
<#-- template to create the options file for the jpackage tool to create the application image -->
<#if osName?upper_case?contains("WIN")>
<#elseif osName?upper_case?contains("MAC")>
<#else>
</#if>
--name ${appName}
--app-version "${appVersion}"
--vendor "${appVendor}"
--copyright "${appCopyright}"

View file

@ -0,0 +1,8 @@
<#-- template to create the options file for the jpackage tool to create the installer -->
<#if osName?upper_case?contains("WIN")>
<#elseif osName?upper_case?contains("MAC")>
<#else>
</#if>

331
pom.xml Normal file
View file

@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>moodle.sync</groupId>
<artifactId>moodle-sync</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<description>
Moodle platform file synchronization from a desktop app.
</description>
<url>https://github.com/lectureStudio/MoodleSync</url>
<developers>
<developer>
<id>d.schroeter</id>
<name>Daniel Schröter</name>
</developer>
</developers>
<licenses>
<license>
<name>GNU General Public License, Version 3.0</name>
<url>http://www.gnu.org/licenses/gpl-3.0.txt</url>
<distribution>manual</distribution>
<comments>A free, copyleft license for software and other kinds of works.</comments>
</license>
</licenses>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/lectureStudio/MoodleSync/issues</url>
</issueManagement>
<scm>
<connection>scm:git:git://github.com/lectureStudio/MoodleSync.git</connection>
<developerConnection>scm:git:ssh://git@github.com/lectureStudio/MoodleSync.git</developerConnection>
<url>https://github.com/lectureStudio/MoodleSync/tree/main</url>
</scm>
<modules>
<module>moodle-sync-core</module>
<module>moodle-sync-cli</module>
<module>moodle-sync-fx</module>
<module>moodle-sync-package-cli</module>
<module>moodle-sync-package-fx</module>
<module>moodle-sync-package-archive</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<package.name>moodle-sync</package.name>
<package.version>1.0.${git.commitsCount}</package.version>
<package.dir>${package.name}-${os.name}</package.dir>
<package.bundle.dir>${package.name}-bundle-${os.name}</package.bundle.dir>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
<license.path>${project.basedir}/../</license.path>
</properties>
<build>
<pluginManagement>
<!-- Set versions of common plugins for reproducibility, ordered alphabetically. -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.0</version>
<configuration>
<release>14</release>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-installed</id>
<phase>install</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<type>${project.packaging}</type>
</artifactItem>
</artifactItems>
<stripVersion>true</stripVersion>
<outputDirectory>${project.parent.build.directory}/${package.dir}</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-dependencies</id>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<excludeArtifactIds>junit</excludeArtifactIds>
<stripVersion>true</stripVersion>
<excludeTransitive>false</excludeTransitive>
<excludeTypes>war</excludeTypes>
<excludeScope>provided</excludeScope>
<outputDirectory>${project.parent.build.directory}/${package.dir}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<compress>true</compress>
<manifestEntries>
<Version>${project.version}</Version>
<Package-Version>${package.version}</Package-Version>
<SCM-Revision>${git.revision}</SCM-Revision>
<Build-Date>${maven.build.timestamp}</Build-Date>
</manifestEntries>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<failOnError>false</failOnError>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-license</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/META-INF</outputDirectory>
<resources>
<resource>
<directory>${license.path}</directory>
<includes>
<include>LICENSE</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>ru.concerteza.buildnumber</groupId>
<artifactId>maven-jgit-buildnumber-plugin</artifactId>
<version>1.2.10</version>
</plugin>
<plugin>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-jlink-maven-plugin</artifactId>
<version>17.12.0.0</version>
<extensions>true</extensions>
<configuration>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<compress>0</compress>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>ru.concerteza.buildnumber</groupId>
<artifactId>maven-jgit-buildnumber-plugin</artifactId>
<executions>
<execution>
<id>git-buildnumber</id>
<goals>
<goal>extract-buildnumber</goal>
</goals>
<phase>validate</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<!-- We want to sign the artifact, the POM, and all attached artifacts. -->
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
-->
</plugins>
</build>
</profile>
<profile>
<id>linux-x86_64</id>
<activation>
<os>
<family>unix</family>
<name>Linux</name>
<arch>amd64</arch>
</os>
</activation>
<properties>
<platform.arch>x86_64</platform.arch>
<platform.name>linux</platform.name>
<os.name>linux-${platform.arch}</os.name>
</properties>
</profile>
<profile>
<id>mac-x86_64</id>
<activation>
<os>
<family>mac</family>
<arch>x86_64</arch>
</os>
</activation>
<properties>
<platform.arch>x86_64</platform.arch>
<platform.name>mac</platform.name>
<os.name>macosx-${platform.arch}</os.name>
</properties>
</profile>
<profile>
<id>windows-x86_64</id>
<activation>
<os>
<family>windows</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<platform.arch>x86_64</platform.arch>
<platform.name>windows</platform.name>
<os.name>windows-${platform.arch}</os.name>
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>