File paths dynamic (#2605)

# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
Co-authored-by: a <a>
This commit is contained in:
Anthony Stirling 2025-01-06 12:41:30 +00:00 committed by GitHub
parent f08f8c734b
commit ed633616e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 450 additions and 156 deletions

View File

@ -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' &&

View File

@ -123,11 +123,13 @@ jpackage {
windows {
launcherAsService = false
appVersion = project.version
winConsole = false
winDirChooser = true
winMenu = true
winShortcut = true
winPerUserInstall = true
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"

View File

@ -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<String, String> 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());
}

View File

@ -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(
() -> {
try {
if (loadingWindow != null) {
Timer timer =
new Timer(
500,
e -> {
log.info("Starting UI initialization sequence");
// 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);
}
});
timer.setRepeats(false);
timer.start();
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();
}
}
});
}

View File

@ -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();
}
}

View File

@ -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<Path> processOnlyFiles() {
return path -> {

View File

@ -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<ConfigurableApplicationContext> {
@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);
}

View File

@ -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,9 +268,14 @@ public class EndpointConfiguration {
}
private void processEnvironmentConfigs() {
if (applicationProperties != null && applicationProperties.getEndpoints() != null) {
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
if (!bookAndHtmlFormatsInstalled) {
if (groupsToRemove == null) {
groupsToRemove = new ArrayList<>();
}
groupsToRemove.add("Calibre");
}
if (endpointsToRemove != null) {
@ -284,6 +290,7 @@ public class EndpointConfiguration {
}
}
}
}
public Set<String> getEndpointsForGroup(String group) {
return endpointGroups.getOrDefault(group, new HashSet<>());

View File

@ -33,7 +33,8 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
String characterEncoding,
Map<String, Object> 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);

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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(

View File

@ -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<ResponseToken, Saml2Authentication> {

View File

@ -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));

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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);

View File

@ -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<Path> pathFilter)
public FileMonitor(@Qualifier("directoryFilter") Predicate<Path> 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) {

View File

@ -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 =

View File

@ -1,3 +0,0 @@
package stirling.software.SPDF.utils;
public class PDFManipulationUtils {}

View File

@ -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<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());
// Return output files in a ZIP archive
fileName = pdfBaseName + "ToHtml.zip";

View File

@ -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;

View File

@ -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

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<define name="LOG_PATH" class="stirling.software.SPDF.config.LogbackPropertyLoader" />
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@ -7,35 +9,30 @@
</encoder>
</appender>
<!-- Rolling File Appender -->
<!-- Rolling File Appender for Auth Logs -->
<appender name="AUTHLOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/invalid-auths.log</file>
<file>${LOG_PATH}/invalid-auths.log</file>
<encoder>
<pattern>%d %p %c{1} [%thread] %m%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover and keep 7 days' worth of history -->
<fileNamePattern>logs/auth-%d{yyyy-MM-dd}.log</fileNamePattern>
<fileNamePattern>${LOG_PATH}/auth-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory>
</rollingPolicy>
</appender>
<!-- Rolling File Appender -->
<!-- Rolling File Appender for General Logs -->
<appender name="GENERAL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/info.log</file>
<file>${LOG_PATH}/info.log</file>
<encoder>
<pattern>%d %p %c{1} [%thread] %m%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover and keep 7 days' worth of history -->
<fileNamePattern>logs/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<fileNamePattern>${LOG_PATH}/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory>
</rollingPolicy>
</appender>
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
@ -43,10 +40,9 @@
</root>
<!-- Specific Logger -->
<logger name="stirling.software.SPDF.config.security.CustomAuthenticationFailureHandler" level="ERROR"
additivity="false">
<logger name="stirling.software.SPDF.config.security.CustomAuthenticationFailureHandler"
level="ERROR" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="AUTHLOG"/>
</logger>
</configuration>