diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb29f5dda..6cc580db4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,17 @@ jobs: env: DOCKER_ENABLE_SECURITY: true + - name: Upload Test Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports-jdk-${{ matrix.jdk-version }} + path: | + build/reports/tests/ + build/test-results/ + build/reports/problems/ + retention-days: 3 + docker-compose-tests: # if: github.event_name == 'push' && github.ref == 'refs/heads/main' || # (github.event_name == 'pull_request' && diff --git a/build.gradle b/build.gradle index a2b09d4b5..088e8d813 100644 --- a/build.gradle +++ b/build.gradle @@ -123,11 +123,13 @@ jpackage { windows { launcherAsService = false appVersion = project.version - winConsole = false - winDirChooser = true - winMenu = true - winShortcut = true - winPerUserInstall = true + + winConsole = false + winMenu = true // Creates start menu entry + winShortcut = true // Creates desktop shortcut + winShortcutPrompt = true // Lets user choose whether to create shortcuts + winDirChooser = true // Allows users to choose installation directory + winPerUserInstall = false winMenuGroup = "Stirling Software" winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF" diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index 10eecaaa9..eabe017ce 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -2,6 +2,7 @@ package stirling.software.SPDF; import java.io.IOException; import java.net.ServerSocket; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -24,6 +25,7 @@ import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.config.ConfigInitializer; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.model.ApplicationProperties; @SpringBootApplication @@ -76,15 +78,27 @@ public class SPdfApplication { props.put("spring.main.web-application-type", "servlet"); } app.setAdditionalProfiles("default"); - app.addInitializers(new ConfigInitializer()); + + ConfigInitializer initializer = new ConfigInitializer(); + try { + initializer.ensureConfigExists(); + } catch (IOException | URISyntaxException e) { + log.error("Error initialising configuration", e); + } + Map propertyFiles = new HashMap<>(); // External config files - if (Files.exists(Paths.get("configs/settings.yml"))) { - propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml"); + log.info("Settings file: {}", InstallationPathConfig.getSettingsPath()); + if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) { + propertyFiles.put( + "spring.config.additional-location", + "file:" + InstallationPathConfig.getSettingsPath()); } else { - log.warn("External configuration file 'configs/settings.yml' does not exist."); + log.warn( + "External configuration file '{}' does not exist.", + InstallationPathConfig.getSettingsPath()); } - if (Files.exists(Paths.get("configs/custom_settings.yml"))) { + if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) { String existingLocation = propertyFiles.getOrDefault("spring.config.additional-location", ""); if (!existingLocation.isEmpty()) { @@ -92,9 +106,11 @@ public class SPdfApplication { } propertyFiles.put( "spring.config.additional-location", - existingLocation + "file:configs/custom_settings.yml"); + existingLocation + "file:" + InstallationPathConfig.getCustomSettingsPath()); } else { - log.warn("Custom configuration file 'configs/custom_settings.yml' does not exist."); + log.warn( + "Custom configuration file '{}' does not exist.", + InstallationPathConfig.getCustomSettingsPath()); } Properties finalProps = new Properties(); if (!propertyFiles.isEmpty()) { @@ -110,8 +126,8 @@ public class SPdfApplication { app.run(args); // Ensure directories are created try { - Files.createDirectories(Path.of("customFiles/static/")); - Files.createDirectories(Path.of("customFiles/templates/")); + Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath())); + Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath())); } catch (Exception e) { log.error("Error creating directories: {}", e.getMessage()); } diff --git a/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java b/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java index a5509e1b7..ae5f76fd7 100644 --- a/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java +++ b/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java @@ -40,6 +40,7 @@ import me.friwi.jcefmaven.EnumProgress; import me.friwi.jcefmaven.MavenCefAppHandlerAdapter; import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler; import stirling.software.SPDF.UI.WebBrowser; +import stirling.software.SPDF.config.InstallationPathConfig; @Component @Slf4j @@ -72,7 +73,8 @@ public class DesktopBrowser implements WebBrowser { CefAppBuilder builder = new CefAppBuilder(); configureCefSettings(builder); builder.setProgressHandler(createProgressHandler()); - + builder.setInstallDir( + new File(InstallationPathConfig.getClientWebUIPath())); // Build and initialize CEF cefApp = builder.build(); client = cefApp.createClient(); @@ -99,8 +101,16 @@ public class DesktopBrowser implements WebBrowser { private void configureCefSettings(CefAppBuilder builder) { CefSettings settings = builder.getCefSettings(); - settings.cache_path = new File("jcef-bundle").getAbsolutePath(); - settings.root_cache_path = new File("jcef-bundle").getAbsolutePath(); + String basePath = InstallationPathConfig.getClientWebUIPath(); + log.info("basePath " + basePath); + settings.cache_path = new File(basePath + "cache").getAbsolutePath(); + settings.root_cache_path = new File(basePath + "root_cache").getAbsolutePath(); + // settings.browser_subprocess_path = new File(basePath + + // "subprocess").getAbsolutePath(); + // settings.resources_dir_path = new File(basePath + "resources").getAbsolutePath(); + // settings.locales_dir_path = new File(basePath + "locales").getAbsolutePath(); + settings.log_file = new File(basePath, "debug.log").getAbsolutePath(); + settings.persist_session_cookies = true; settings.windowless_rendering_enabled = false; settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO; @@ -212,6 +222,9 @@ public class DesktopBrowser implements WebBrowser { } private void setupLoadHandler() { + final long initStartTime = System.currentTimeMillis(); + log.info("Setting up load handler at: {}", initStartTime); + client.addLoadHandler( new CefLoadHandlerAdapter() { @Override @@ -220,32 +233,77 @@ public class DesktopBrowser implements WebBrowser { boolean isLoading, boolean canGoBack, boolean canGoForward) { + log.debug( + "Loading state change - isLoading: {}, canGoBack: {}, canGoForward: {}, " + + "browserInitialized: {}, Time elapsed: {}ms", + isLoading, + canGoBack, + canGoForward, + browserInitialized, + System.currentTimeMillis() - initStartTime); + if (!isLoading && !browserInitialized) { + log.info( + "Browser finished loading, preparing to initialize UI components"); browserInitialized = true; SwingUtilities.invokeLater( () -> { - if (loadingWindow != null) { - Timer timer = - new Timer( - 500, - e -> { - loadingWindow.dispose(); - loadingWindow = null; + try { + if (loadingWindow != null) { + log.info("Starting UI initialization sequence"); - frame.dispose(); - frame.setOpacity(1.0f); - frame.setUndecorated(false); - frame.pack(); - frame.setSize(1280, 800); - frame.setLocationRelativeTo(null); - frame.setVisible(true); - frame.requestFocus(); - frame.toFront(); - browser.getUIComponent() - .requestFocus(); - }); - timer.setRepeats(false); - timer.start(); + // Close loading window first + loadingWindow.setVisible(false); + loadingWindow.dispose(); + loadingWindow = null; + log.info("Loading window disposed"); + + // Then setup the main frame + frame.setVisible(false); + frame.dispose(); + frame.setOpacity(1.0f); + frame.setUndecorated(false); + frame.pack(); + frame.setSize(1280, 800); + frame.setLocationRelativeTo(null); + log.debug("Frame reconfigured"); + + // Show the main frame + frame.setVisible(true); + frame.requestFocus(); + frame.toFront(); + log.info("Main frame displayed and focused"); + + // Focus the browser component + Timer focusTimer = + new Timer( + 100, + e -> { + try { + browser.getUIComponent() + .requestFocus(); + log.info( + "Browser component focused"); + } catch (Exception ex) { + log.error( + "Error focusing browser", + ex); + } + }); + focusTimer.setRepeats(false); + focusTimer.start(); + } + } catch (Exception e) { + log.error("Error during UI initialization", e); + // Attempt cleanup on error + if (loadingWindow != null) { + loadingWindow.dispose(); + loadingWindow = null; + } + if (frame != null) { + frame.setVisible(true); + frame.requestFocus(); + } } }); } diff --git a/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java b/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java index ad827dc5d..d6c0d27a0 100644 --- a/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java +++ b/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java @@ -14,9 +14,12 @@ public class LoadingWindow extends JDialog { private final JLabel statusLabel; private final JPanel mainPanel; private final JLabel brandLabel; + private long startTime; public LoadingWindow(Frame parent, String initialUrl) { super(parent, "Initializing Stirling-PDF", true); + startTime = System.currentTimeMillis(); + log.info("Creating LoadingWindow - initialization started at: {}", startTime); // Initialize components mainPanel = new JPanel(); @@ -29,8 +32,8 @@ public class LoadingWindow extends JDialog { gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(5, 5, 5, 5); - gbc.weightx = 1.0; // Add horizontal weight - gbc.weighty = 0.0; // Add vertical weight + gbc.weightx = 1.0; + gbc.weighty = 0.0; // Add icon try { @@ -43,12 +46,14 @@ public class LoadingWindow extends JDialog { iconLabel.setHorizontalAlignment(SwingConstants.CENTER); gbc.gridy = 0; mainPanel.add(iconLabel, gbc); + log.debug("Icon loaded and scaled successfully"); } } } } catch (Exception e) { log.error("Failed to load icon", e); } + // URL Label with explicit size brandLabel = new JLabel(initialUrl); brandLabel.setHorizontalAlignment(SwingConstants.CENTER); @@ -63,6 +68,7 @@ public class LoadingWindow extends JDialog { statusLabel.setPreferredSize(new Dimension(300, 25)); gbc.gridy = 2; mainPanel.add(statusLabel, gbc); + // Progress bar with explicit size progressBar = new JProgressBar(0, 100); progressBar.setStringPainted(true); @@ -82,33 +88,78 @@ public class LoadingWindow extends JDialog { setAlwaysOnTop(true); setProgress(0); setStatus("Starting..."); + + log.info( + "LoadingWindow initialization completed in {}ms", + System.currentTimeMillis() - startTime); } public void setProgress(final int progress) { SwingUtilities.invokeLater( () -> { try { - progressBar.setValue(Math.min(Math.max(progress, 0), 100)); - progressBar.setString(progress + "%"); + int validProgress = Math.min(Math.max(progress, 0), 100); + log.info( + "Setting progress to {}% at {}ms since start", + validProgress, System.currentTimeMillis() - startTime); + + // Log additional details when near 90% + if (validProgress >= 85 && validProgress <= 95) { + log.info( + "Near 90% progress - Current status: {}, Window visible: {}, " + + "Progress bar responding: {}, Memory usage: {}MB", + statusLabel.getText(), + isVisible(), + progressBar.isEnabled(), + Runtime.getRuntime().totalMemory() / (1024 * 1024)); + + // Add thread state logging + Thread currentThread = Thread.currentThread(); + log.debug( + "Current thread state - Name: {}, State: {}, Priority: {}", + currentThread.getName(), + currentThread.getState(), + currentThread.getPriority()); + } + + progressBar.setValue(validProgress); + progressBar.setString(validProgress + "%"); mainPanel.revalidate(); mainPanel.repaint(); } catch (Exception e) { - log.error("Error updating progress", e); + log.error("Error updating progress to " + progress, e); } }); } public void setStatus(final String status) { - log.info(status); + log.info( + "Status update at {}ms - Setting status to: {}", + System.currentTimeMillis() - startTime, + status); + SwingUtilities.invokeLater( () -> { try { - statusLabel.setText(status != null ? status : ""); + String validStatus = status != null ? status : ""; + statusLabel.setText(validStatus); + + // Log UI state when status changes + log.debug( + "UI State - Window visible: {}, Progress: {}%, Status: {}", + isVisible(), progressBar.getValue(), validStatus); + mainPanel.revalidate(); mainPanel.repaint(); } catch (Exception e) { - log.error("Error updating status", e); + log.error("Error updating status to: " + status, e); } }); } + + @Override + public void dispose() { + log.info("LoadingWindow disposing after {}ms", System.currentTimeMillis() - startTime); + super.dispose(); + } } diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 19a4b768f..6597be251 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -136,16 +136,6 @@ public class AppConfig { return false; } - @Bean(name = "watchedFoldersDir") - public String watchedFoldersDir() { - return "./pipeline/watchedFolders/"; - } - - @Bean(name = "finishedFoldersDir") - public String finishedFoldersDir() { - return "./pipeline/finishedFolders/"; - } - @Bean(name = "directoryFilter") public Predicate processOnlyFiles() { return path -> { diff --git a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java index 42dbf7475..b8c101af7 100644 --- a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java +++ b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java @@ -16,27 +16,15 @@ import org.simpleyaml.configuration.comments.CommentType; import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.implementation.SimpleYamlImplementation; import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; import lombok.extern.slf4j.Slf4j; @Slf4j -public class ConfigInitializer - implements ApplicationContextInitializer { - - @Override - public void initialize(ConfigurableApplicationContext applicationContext) { - try { - ensureConfigExists(); - } catch (Exception e) { - throw new RuntimeException("Failed to initialize application configuration", e); - } - } +public class ConfigInitializer { public void ensureConfigExists() throws IOException, URISyntaxException { // Define the path to the external config directory - Path destPath = Paths.get("configs", "settings.yml"); + Path destPath = Paths.get(InstallationPathConfig.getSettingsPath()); // Check if the file already exists if (Files.notExists(destPath)) { @@ -53,10 +41,11 @@ public class ConfigInitializer "Resource file not found: settings.yml.template"); } } + log.info("Created settings file from template"); } else { // Define the path to the config settings file - Path settingsPath = Paths.get("configs", "settings.yml"); + Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath()); // Load the template resource URL settingsTemplateResource = getClass().getClassLoader().getResource("settings.yml.template"); @@ -120,7 +109,7 @@ public class ConfigInitializer } // Create custom settings file if it doesn't exist - Path customSettingsPath = Paths.get("configs", "custom_settings.yml"); + Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath()); if (!Files.exists(customSettingsPath)) { Files.createFile(customSettingsPath); } diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 07ebadb1a..6fbeee5ba 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.config; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -267,20 +268,26 @@ public class EndpointConfiguration { } private void processEnvironmentConfigs() { - List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); - List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - if (!bookAndHtmlFormatsInstalled) { - groupsToRemove.add("Calibre"); - } - if (endpointsToRemove != null) { - for (String endpoint : endpointsToRemove) { - disableEndpoint(endpoint.trim()); - } - } + if (applicationProperties != null && applicationProperties.getEndpoints() != null) { + List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); + List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - if (groupsToRemove != null) { - for (String group : groupsToRemove) { - disableGroup(group.trim()); + if (!bookAndHtmlFormatsInstalled) { + if (groupsToRemove == null) { + groupsToRemove = new ArrayList<>(); + } + groupsToRemove.add("Calibre"); + } + if (endpointsToRemove != null) { + for (String endpoint : endpointsToRemove) { + disableEndpoint(endpoint.trim()); + } + } + + if (groupsToRemove != null) { + for (String group : groupsToRemove) { + disableGroup(group.trim()); + } } } } diff --git a/src/main/java/stirling/software/SPDF/config/FileFallbackTemplateResolver.java b/src/main/java/stirling/software/SPDF/config/FileFallbackTemplateResolver.java index 8b31cc95f..b6315db92 100644 --- a/src/main/java/stirling/software/SPDF/config/FileFallbackTemplateResolver.java +++ b/src/main/java/stirling/software/SPDF/config/FileFallbackTemplateResolver.java @@ -33,7 +33,8 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe String characterEncoding, Map templateResolutionAttributes) { Resource resource = - resourceLoader.getResource("file:./customFiles/templates/" + resourceName); + resourceLoader.getResource( + "file:" + InstallationPathConfig.getTemplatesPath() + resourceName); try { if (resource.exists() && resource.isReadable()) { return new FileTemplateResource(resource.getFile().getPath(), characterEncoding); diff --git a/src/main/java/stirling/software/SPDF/config/InstallationPathConfig.java b/src/main/java/stirling/software/SPDF/config/InstallationPathConfig.java new file mode 100644 index 000000000..e8e1aa1b0 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/InstallationPathConfig.java @@ -0,0 +1,132 @@ +package stirling.software.SPDF.config; + +import java.io.File; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InstallationPathConfig { + private static final String BASE_PATH; + + // Root paths + private static final String LOG_PATH; + private static final String CONFIG_PATH; + private static final String PIPELINE_PATH; + private static final String CUSTOM_FILES_PATH; + private static final String CLIENT_WEBUI_PATH; + + // Config paths + private static final String SETTINGS_PATH; + private static final String CUSTOM_SETTINGS_PATH; + + // Pipeline paths + private static final String PIPELINE_WATCHED_FOLDERS_PATH; + private static final String PIPELINE_FINISHED_FOLDERS_PATH; + + // Custom file paths + private static final String STATIC_PATH; + private static final String TEMPLATES_PATH; + private static final String SIGNATURES_PATH; + + static { + BASE_PATH = initializeBasePath(); + + // Initialize root paths + LOG_PATH = BASE_PATH + "logs" + File.separator; + CONFIG_PATH = BASE_PATH + "configs" + File.separator; + PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator; + CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator; + CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator; + + // Initialize config paths + SETTINGS_PATH = CONFIG_PATH + "settings.yml"; + CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml"; + + // Initialize pipeline paths + PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator; + PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator; + + // Initialize custom file paths + STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator; + TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator; + SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator; + } + + private static String initializeBasePath() { + if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return System.getenv("APPDATA") + File.separator + "Stirling-PDF" + File.separator; + } else if (os.contains("mac")) { + return System.getProperty("user.home") + + File.separator + + "Library" + + File.separator + + "Application Support" + + File.separator + + "Stirling-PDF" + + File.separator; + } else { + return System.getProperty("user.home") + + File.separator + + ".config" + + File.separator + + "Stirling-PDF" + + File.separator; + } + } + return "./"; + } + + public static String getPath() { + return BASE_PATH; + } + + public static String getLogPath() { + return LOG_PATH; + } + + public static String getConfigPath() { + return CONFIG_PATH; + } + + public static String getPipelinePath() { + return PIPELINE_PATH; + } + + public static String getCustomFilesPath() { + return CUSTOM_FILES_PATH; + } + + public static String getClientWebUIPath() { + return CLIENT_WEBUI_PATH; + } + + public static String getSettingsPath() { + return SETTINGS_PATH; + } + + public static String getCustomSettingsPath() { + return CUSTOM_SETTINGS_PATH; + } + + public static String getPipelineWatchedFoldersDir() { + return PIPELINE_WATCHED_FOLDERS_PATH; + } + + public static String getPipelineFinishedFoldersDir() { + return PIPELINE_FINISHED_FOLDERS_PATH; + } + + public static String getStaticPath() { + return STATIC_PATH; + } + + public static String getTemplatesPath() { + return TEMPLATES_PATH; + } + + public static String getSignaturesPath() { + return SIGNATURES_PATH; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/LogbackPropertyLoader.java b/src/main/java/stirling/software/SPDF/config/LogbackPropertyLoader.java new file mode 100644 index 000000000..99de07acb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/LogbackPropertyLoader.java @@ -0,0 +1,10 @@ +package stirling.software.SPDF.config; + +import ch.qos.logback.core.PropertyDefinerBase; + +public class LogbackPropertyLoader extends PropertyDefinerBase { + @Override + public String getPropertyValue() { + return InstallationPathConfig.getLogPath(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index 5cbae1f0f..68d3fbbe4 100644 --- a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -23,7 +23,8 @@ public class WebMvcConfig implements WebMvcConfigurer { public void addResourceHandlers(ResourceHandlerRegistry registry) { // Handler for external static resources registry.addResourceHandler("/**") - .addResourceLocations("file:customFiles/static/", "classpath:/static/"); + .addResourceLocations( + "file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/"); // .setCachePeriod(0); // Optional: disable caching } } diff --git a/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java b/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java index 50bc77f87..e2818e898 100644 --- a/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java +++ b/src/main/java/stirling/software/SPDF/config/YamlPropertySourceFactory.java @@ -16,7 +16,6 @@ public class YamlPropertySourceFactory implements PropertySourceFactory { throws IOException { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(encodedResource.getResource()); - Properties properties = factory.getObject(); return new PropertiesPropertySource( diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java index bfd35c640..6c56a2c57 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java @@ -11,13 +11,11 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.User; -@Component @Slf4j public class CustomSaml2ResponseAuthenticationConverter implements Converter { diff --git a/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java b/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java index ce97c0bc0..4075a1d9d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.utils.GeneralUtils; @@ -33,7 +34,8 @@ public class SettingsController { if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) { return ResponseEntity.status(HttpStatus.ALREADY_REPORTED) .body( - "Setting has already been set, To adjust please edit /config/settings.yml"); + "Setting has already been set, To adjust please edit " + + InstallationPathConfig.getSettingsPath()); } GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false); applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled)); diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index d91ad7e3d..dcef03763 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.api.HandleDataRequest; import stirling.software.SPDF.utils.WebResponseUtils; @@ -35,22 +34,12 @@ import stirling.software.SPDF.utils.WebResponseUtils; @Tag(name = "Pipeline", description = "Pipeline APIs") public class PipelineController { - final String watchedFoldersDir = "./pipeline/watchedFolders/"; - - final String finishedFoldersDir = "./pipeline/finishedFolders/"; - private final PipelineProcessor processor; - private final ApplicationProperties applicationProperties; - private final ObjectMapper objectMapper; - public PipelineController( - PipelineProcessor processor, - ApplicationProperties applicationProperties, - ObjectMapper objectMapper) { + public PipelineController(PipelineProcessor processor, ObjectMapper objectMapper) { this.processor = processor; - this.applicationProperties = applicationProperties; this.objectMapper = objectMapper; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java index 2595c2fec..20330204f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.scheduling.annotation.Scheduled; @@ -25,6 +24,7 @@ import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.utils.FileMonitor; @@ -48,14 +48,12 @@ public class PipelineDirectoryProcessor { public PipelineDirectoryProcessor( ObjectMapper objectMapper, ApiDocService apiDocService, - @Qualifier("watchedFoldersDir") String watchedFoldersDir, - @Qualifier("finishedFoldersDir") String finishedFoldersDir, PipelineProcessor processor, FileMonitor fileMonitor) { this.objectMapper = objectMapper; this.apiDocService = apiDocService; - this.watchedFoldersDir = watchedFoldersDir; - this.finishedFoldersDir = finishedFoldersDir; + this.watchedFoldersDir = InstallationPathConfig.getPipelineWatchedFoldersDir(); + this.finishedFoldersDir = InstallationPathConfig.getPipelineFinishedFoldersDir(); this.processor = processor; this.fileMonitor = fileMonitor; } diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 0043c5e1d..3823e088c 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.model.SignatureFile; import stirling.software.SPDF.service.SignatureService; @@ -34,8 +35,6 @@ import stirling.software.SPDF.service.SignatureService; @Slf4j public class GeneralWebController { - private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/"; - private static final String ALL_USERS_FOLDER = "ALL_USERS"; private final SignatureService signatureService; private final UserServiceInterface userService; private final ResourceLoader resourceLoader; @@ -223,7 +222,9 @@ public class GeneralWebController { // Extract font names from classpath fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2")); // Extract font names from external directory - fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*")); + fontNames.addAll( + getFontNamesFromLocation( + "file:" + InstallationPathConfig.getStaticPath() + "fonts/*")); return fontNames; } diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 3b2d61762..9ff700c99 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -1,5 +1,7 @@ package stirling.software.SPDF.model; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; @@ -13,18 +15,23 @@ import java.util.List; import java.util.stream.Collectors; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; import lombok.Data; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.YamlPropertySourceFactory; import stirling.software.SPDF.model.provider.GithubProvider; import stirling.software.SPDF.model.provider.GoogleProvider; @@ -33,11 +40,37 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException; @Configuration @ConfigurationProperties(prefix = "") -@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) @Data @Order(Ordered.HIGHEST_PRECEDENCE) +@Slf4j public class ApplicationProperties { + @Bean + public PropertySource dynamicYamlPropertySource(ConfigurableEnvironment environment) + throws IOException { + String configPath = InstallationPathConfig.getSettingsPath(); + log.debug("Attempting to load settings from: " + configPath); + + File file = new File(configPath); + if (!file.exists()) { + log.error("Warning: Settings file does not exist at: " + configPath); + } + + Resource resource = new FileSystemResource(configPath); + if (!resource.exists()) { + throw new FileNotFoundException("Settings file not found at: " + configPath); + } + + EncodedResource encodedResource = new EncodedResource(resource); + PropertySource propertySource = + new YamlPropertySourceFactory().createPropertySource(null, encodedResource); + environment.getPropertySources().addFirst(propertySource); + + log.debug("Loaded properties: " + propertySource.getSource()); + + return propertySource; + } + private Legal legal = new Legal(); private Security security = new Security(); private System system = new System(); @@ -153,6 +186,7 @@ public class ApplicationProperties { } public Resource getSpCert() { + if (spCert == null) return null; if (spCert.startsWith("classpath:")) { return new ClassPathResource(spCert.substring("classpath:".length())); } else { @@ -161,6 +195,7 @@ public class ApplicationProperties { } public Resource getidpCert() { + if (idpCert == null) return null; if (idpCert.startsWith("classpath:")) { return new ClassPathResource(idpCert.substring("classpath:".length())); } else { diff --git a/src/main/java/stirling/software/SPDF/service/SignatureService.java b/src/main/java/stirling/software/SPDF/service/SignatureService.java index c939337fe..bd4642e77 100644 --- a/src/main/java/stirling/software/SPDF/service/SignatureService.java +++ b/src/main/java/stirling/software/SPDF/service/SignatureService.java @@ -13,14 +13,19 @@ import org.springframework.stereotype.Service; import org.thymeleaf.util.StringUtils; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.model.SignatureFile; @Service @Slf4j public class SignatureService { - private static final String SIGNATURE_BASE_PATH = "customFiles/signatures/"; - private static final String ALL_USERS_FOLDER = "ALL_USERS"; + private final String SIGNATURE_BASE_PATH; + private final String ALL_USERS_FOLDER = "ALL_USERS"; + + public SignatureService() { + SIGNATURE_BASE_PATH = InstallationPathConfig.getSignaturesPath(); + } public boolean hasAccessToFile(String username, String fileName) throws IOException { validateFileName(fileName); diff --git a/src/main/java/stirling/software/SPDF/utils/FileMonitor.java b/src/main/java/stirling/software/SPDF/utils/FileMonitor.java index 493ddd6e3..389d28198 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileMonitor.java +++ b/src/main/java/stirling/software/SPDF/utils/FileMonitor.java @@ -15,6 +15,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; @Component @Slf4j @@ -34,9 +35,7 @@ public class FileMonitor { * monitored, false otherwise */ @Autowired - public FileMonitor( - @Qualifier("watchedFoldersDir") String rootDirectory, - @Qualifier("directoryFilter") Predicate pathFilter) + public FileMonitor(@Qualifier("directoryFilter") Predicate pathFilter) throws IOException { this.newlyDiscoveredFiles = new HashSet<>(); this.path2KeyMapping = new HashMap<>(); @@ -44,7 +43,7 @@ public class FileMonitor { this.pathFilter = pathFilter; this.readyForProcessingFiles = ConcurrentHashMap.newKeySet(); this.watchService = FileSystems.getDefault().newWatchService(); - this.rootDir = Path.of(rootDirectory); + this.rootDir = Path.of(InstallationPathConfig.getPipelineWatchedFoldersDir()); } private boolean shouldNotProcess(Path path) { diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 799511452..ff6e334c4 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -13,8 +13,6 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.file.YamlFileWrapper; @@ -28,6 +26,7 @@ import io.github.pixee.security.HostValidator; import io.github.pixee.security.Urls; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.InstallationPathConfig; @Slf4j public class GeneralUtils { @@ -85,7 +84,7 @@ public class GeneralUtils { // Allow only http and https protocols String protocol = url.getProtocol(); - if (!protocol.equals("http") && !protocol.equals("https")) { + if (!"http".equals(protocol) && !"https".equals(protocol)) { return false; // Disallow other protocols } @@ -229,8 +228,7 @@ public class GeneralUtils { Double result = evaluator.evaluate(sanitizedExpression); // Check if the result is null or not within bounds - if (result == null) - break; + if (result == null) break; if (result.intValue() > 0 && result.intValue() <= maxValue) results.add(result.intValue()); @@ -241,11 +239,15 @@ public class GeneralUtils { private static String sanitizeNFunction(String expression, int nValue) { String sanitizedExpression = expression.replace(" ", ""); - String multiplyByOpeningRoundBracketPattern = "([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2) - sanitizedExpression = sanitizedExpression.replaceAll(multiplyByOpeningRoundBracketPattern, "$1*("); + String multiplyByOpeningRoundBracketPattern = + "([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2) + sanitizedExpression = + sanitizedExpression.replaceAll(multiplyByOpeningRoundBracketPattern, "$1*("); - String multiplyByClosingRoundBracketPattern = "\\)([0-9n)])"; // example: (n-1)n, (n-1)9, (n-1)(n-2) - sanitizedExpression = sanitizedExpression.replaceAll(multiplyByClosingRoundBracketPattern, ")*$1"); + String multiplyByClosingRoundBracketPattern = + "\\)([0-9n)])"; // example: (n-1)n, (n-1)9, (n-1)(n-2) + sanitizedExpression = + sanitizedExpression.replaceAll(multiplyByClosingRoundBracketPattern, ")*$1"); sanitizedExpression = insertMultiplicationBeforeN(sanitizedExpression, nValue); return sanitizedExpression; @@ -341,7 +343,10 @@ public class GeneralUtils { public static void saveKeyToConfig(String id, String key, boolean autoGenerated) throws IOException { - Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml + Path path = + Paths.get( + InstallationPathConfig + .getSettingsPath()); // Target the configs/settings.yml final YamlFile settingsYml = new YamlFile(path.toFile()); DumperOptions yamlOptionssettingsYml = @@ -359,7 +364,7 @@ public class GeneralUtils { public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated) throws IOException { - Path path = Paths.get("configs", "settings.yml"); + Path path = Paths.get(InstallationPathConfig.getSettingsPath()); final YamlFile settingsYml = new YamlFile(path.toFile()); DumperOptions yamlOptionssettingsYml = diff --git a/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java b/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java deleted file mode 100644 index 397ff5bc4..000000000 --- a/src/main/java/stirling/software/SPDF/utils/PDFManipulationUtils.java +++ /dev/null @@ -1,3 +0,0 @@ -package stirling.software.SPDF.utils; - -public class PDFManipulationUtils {} diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index 8827b19b9..e080dc10d 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -64,7 +65,7 @@ public class PDFToFile { .runCommandWithOutputHandling(command, tempOutputDir.toFile()); // Get output files - List outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles()); + File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles()); // Return output files in a ZIP archive fileName = pdfBaseName + "ToHtml.zip"; diff --git a/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java b/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java index e2a5166fa..e3795da9a 100644 --- a/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java +++ b/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java @@ -22,9 +22,11 @@ import org.apache.pdfbox.text.TextPosition; import org.springframework.core.io.InputStreamResource; import org.springframework.web.multipart.MultipartFile; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.misc.HighContrastColorCombination; import stirling.software.SPDF.model.api.misc.ReplaceAndInvert; +@Slf4j public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy { private String textColor; @@ -93,17 +95,17 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy { try { font = PDFontFactory.createFont(text.getFont().getCOSObject()); } catch (IOException io) { - System.out.println("Primary font not found, using fallback font."); + log.info("Primary font not found, using fallback font."); font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); } // if a character is not supported by font, then look for supported font try { byte[] bytes = font.encode(unicodeText); } catch (IOException io) { - System.out.println("text could not be encoded "); + log.info("text could not be encoded "); font = checkSupportedFontForCharacter(unicodeText); } catch (IllegalArgumentException ie) { - System.out.println("text not supported by font "); + log.info("text not supported by font "); font = checkSupportedFontForCharacter(unicodeText); } finally { // if any other font is not supported, then replace default character * @@ -157,9 +159,9 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy { byte[] bytes = currentFont.encode(unicodeText); return currentFont; } catch (IOException io) { - System.out.println("text could not be encoded "); + log.info("text could not be encoded "); } catch (IllegalArgumentException ie) { - System.out.println("text not supported by font "); + log.info("text not supported by font "); } } return null; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d76eadf1b..f0d2e6a73 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -27,8 +27,7 @@ spring.devtools.restart.exclude=stirling.software.SPDF.config.security/** spring.thymeleaf.encoding=UTF-8 spring.web.resources.mime-mappings.webmanifest=application/manifest+json spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000} -#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/ -#spring.thymeleaf.cache=false + spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index bcc330c6f..369c3a941 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,6 @@ + + @@ -7,35 +9,30 @@ - + - logs/invalid-auths.log + ${LOG_PATH}/invalid-auths.log %d %p %c{1} [%thread] %m%n - - - logs/auth-%d{yyyy-MM-dd}.log + ${LOG_PATH}/auth-%d{yyyy-MM-dd}.log 1 - + - logs/info.log + ${LOG_PATH}/info.log %d %p %c{1} [%thread] %m%n - - - logs/info-%d{yyyy-MM-dd}.log + ${LOG_PATH}/info-%d{yyyy-MM-dd}.log 1 - @@ -43,10 +40,9 @@ - + - - + \ No newline at end of file