diff --git a/main-installer.sh b/main-installer.sh index 41c69a2..989e832 100755 --- a/main-installer.sh +++ b/main-installer.sh @@ -401,8 +401,14 @@ fi # Step 7: Set up update script log "INFO" "Setting up update script..." mkdir -p "${SCRIPT_DIR}/scripts" -cp "${SCRIPT_DIR}/scripts/update.sh" "${SCRIPT_DIR}/scripts/update.sh" 2>/dev/null || { - # If copy fails, it probably doesn't exist, so we'll create it +# Check if update script exists - don't copy it to itself +if [ ! -f "${SCRIPT_DIR}/scripts/update.sh" ]; then + # First, check if we have an update script to copy + if [ -f "${SCRIPT_DIR}/update.sh" ]; then + cp "${SCRIPT_DIR}/update.sh" "${SCRIPT_DIR}/scripts/update.sh" + log "INFO" "Copied update script from root to scripts directory" + else + # Create the update script since it doesn't exist cat > "${SCRIPT_DIR}/scripts/update.sh" << 'EOL' #!/bin/bash @@ -491,6 +497,8 @@ echo -e "Changes will take effect immediately." EOL chmod +x "${SCRIPT_DIR}/scripts/update.sh" + log "INFO" "Created update script: ${SCRIPT_DIR}/scripts/update.sh" + fi } # Step 8: Final setup and permissions diff --git a/modules/file-creator-module.sh b/modules/file-creator-module.sh index bb018a9..c840e6c 100644 --- a/modules/file-creator-module.sh +++ b/modules/file-creator-module.sh @@ -41,9 +41,13 @@ function create_config_files() { # Create initial config.json echo "Creating config.json..." mkdir -p "$CONFIG_DIR" + + # Get version from package.json dynamically + VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "2.0.9") + cat > $CONFIG_DIR/config.json << EOF { - "version": "2.0.6", + "version": "$VERSION", "installPath": "$INSTALL_DIR", "transmissionConfig": { "host": "$TRANSMISSION_HOST", @@ -301,12 +305,13 @@ app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); //============================== // Get the version from package.json -let appVersion = '2.0.6'; // Default fallback version +let appVersion; try { const packageJson = require('./package.json'); appVersion = packageJson.version; } catch (err) { console.warn('Could not read version from package.json, using default'); + appVersion = '2.0.9'; // Default fallback version aligned with package.json } // Server status API diff --git a/modules/rss-feed-manager.js b/modules/rss-feed-manager.js index d7b5762..785263f 100644 --- a/modules/rss-feed-manager.js +++ b/modules/rss-feed-manager.js @@ -27,21 +27,19 @@ class RssFeedManager { // Log the data path for debugging console.log(`Data directory path set to: ${this.dataPath}`); - - // Create the data directory synchronously to make sure it exists - try { - // Check if fs.mkdirSync is available - if (fs.mkdirSync) { - const fsSync = require('fs'); - if (!fsSync.existsSync(this.dataPath)) { - fsSync.mkdirSync(this.dataPath, { recursive: true }); - console.log(`Created data directory synchronously: ${this.dataPath}`); - } - } - } catch (err) { - console.warn(`Warning: Could not create data directory synchronously: ${err.message}`); - // Will try again asynchronously in ensureDataDirectory + } + + // We'll always ensure the data directory exists regardless of where it's set + // Use synchronous operation to ensure directory exists immediately upon construction + try { + const fsSync = require('fs'); + if (!fsSync.existsSync(this.dataPath)) { + fsSync.mkdirSync(this.dataPath, { recursive: true }); + console.log(`Created data directory synchronously: ${this.dataPath}`); } + } catch (err) { + console.warn(`Warning: Could not create data directory synchronously: ${err.message}`); + // Will try again asynchronously in ensureDataDirectory when start() is called } // Maximum items to keep in memory to prevent memory leaks @@ -158,10 +156,13 @@ class RssFeedManager { console.log(`Updating feed: ${feed.name || 'Unnamed'} (${feed.url})`); try { + // Get version from package.json if available, fallback to environment or hardcoded + const version = process.env.APP_VERSION || require('../package.json').version || '2.0.9'; + const response = await fetch(feed.url, { timeout: 30000, // 30 second timeout headers: { - 'User-Agent': 'Transmission-RSS-Manager/2.0.6' + 'User-Agent': `Transmission-RSS-Manager/${version}` } }); @@ -507,11 +508,16 @@ class RssFeedManager { } } + /** + * Ensures the data directory exists, using a consistent approach across the application + * @returns {Promise} true if directory exists or was created + */ async ensureDataDirectory() { try { // Create data directory with recursive option (creates all parent directories if they don't exist) await fs.mkdir(this.dataPath, { recursive: true }); console.log(`Ensured data directory exists at: ${this.dataPath}`); + return true; } catch (error) { // Log the error details for debugging console.error(`Error creating data directory ${this.dataPath}:`, error); @@ -521,9 +527,10 @@ class RssFeedManager { const { execSync } = require('child_process'); execSync(`mkdir -p "${this.dataPath}"`); console.log(`Created data directory using fallback method: ${this.dataPath}`); + return true; } catch (fallbackError) { - console.error('Fallback method for creating data directory also failed:', fallbackError); - throw error; // Throw the original error + console.error('All methods for creating data directory failed:', fallbackError); + throw new Error(`Failed to create data directory: ${this.dataPath}. Original error: ${error.message}`); } } } diff --git a/modules/transmission-client.js b/modules/transmission-client.js index 0edff70..88635a5 100644 --- a/modules/transmission-client.js +++ b/modules/transmission-client.js @@ -125,6 +125,15 @@ class TransmissionClient { */ async addTorrent(url, options = {}) { try { + // Verify client is initialized + if (!this.client) { + await this.initializeConnection(); + + if (!this.client) { + throw new Error("Failed to initialize Transmission client"); + } + } + const downloadDir = options.downloadDir || null; const result = await this.client.addUrl(url, { "download-dir": downloadDir, diff --git a/modules/utils-module.sh b/modules/utils-module.sh index e52fc0a..829835d 100644 --- a/modules/utils-module.sh +++ b/modules/utils-module.sh @@ -101,6 +101,29 @@ function create_dir_if_not_exists() { function ensure_npm_packages() { local install_dir=$1 + # First ensure the installation directory exists + if [ ! -d "$install_dir" ]; then + log "INFO" "Creating installation directory: $install_dir" + mkdir -p "$install_dir" || { + log "ERROR" "Failed to create installation directory: $install_dir" + return 1 + } + } + + # Ensure data directory exists + if [ ! -d "$install_dir/data" ]; then + log "INFO" "Creating data directory: $install_dir/data" + mkdir -p "$install_dir/data" || { + log "ERROR" "Failed to create data directory: $install_dir/data" + return 1 + } + + # Initialize empty data files + echo "[]" > "$install_dir/data/rss-feeds.json" + echo "[]" > "$install_dir/data/rss-items.json" + log "INFO" "Initialized empty data files" + fi + # Ensure package.json exists in the installation directory if [ ! -f "$install_dir/package.json" ]; then log "INFO" "Copying package.json to installation directory..." @@ -108,7 +131,7 @@ function ensure_npm_packages() { log "ERROR" "Failed to copy package.json to installation directory" return 1 } - fi + } # Install NPM packages if not already installed or if it's an update if [ ! -d "$install_dir/node_modules" ] || [ "$IS_UPDATE" = "true" ]; then @@ -199,9 +222,12 @@ function finalize_setup() { # Make sure CONFIG_DIR exists mkdir -p "$CONFIG_DIR" + # Get version from package.json dynamically + VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "2.0.9") + cat > $CONFIG_DIR/config.json << EOF { - "version": "2.0.6", + "version": "$VERSION", "transmissionConfig": { "host": "${TRANSMISSION_HOST}", "port": ${TRANSMISSION_PORT}, diff --git a/scripts/create-module-links.sh b/scripts/create-module-links.sh index 4e52345..01cd36f 100755 --- a/scripts/create-module-links.sh +++ b/scripts/create-module-links.sh @@ -1,49 +1,78 @@ #!/bin/bash -# Create bi-directional symlinks for module compatibility +# Script to create symlinks for all modules in different naming styles +# This ensures compatibility with different module import styles -MODULES_DIR="$(dirname "$(dirname "$0")")/modules" +APP_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")" +MODULE_DIR="$APP_DIR/modules" -echo "Creating module symlinks in $MODULES_DIR..." +echo "Creating module symlinks for compatibility..." +echo "Module directory: $MODULE_DIR" -# Check if modules directory exists -if [ ! -d "$MODULES_DIR" ]; then - echo "Error: Modules directory not found: $MODULES_DIR" - exit 1 -fi - -# Create bidirectional symlinks -create_bidirectional_links() { - local hyphenated="$1" - local camelCase="$2" - - # Check if hyphenated version exists - if [ -f "$MODULES_DIR/$hyphenated.js" ]; then - # Create camelCase symlink - ln -sf "$hyphenated.js" "$MODULES_DIR/$camelCase.js" - echo "Created symlink: $camelCase.js -> $hyphenated.js" +# Create a function to make bidirectional symlinks +create_module_symlinks() { + if [ -d "$MODULE_DIR" ]; then + # Create symlinks for hyphenated modules + for module in "$MODULE_DIR"/*-*.js; do + if [ -f "$module" ]; then + # Convert hyphenated to camelCase + BASE_NAME=$(basename "$module") + CAMEL_NAME=$(echo "$BASE_NAME" | sed -E 's/-([a-z])/\U\1/g') + + # Create camelCase symlink if needed + if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ] && [ ! -L "$MODULE_DIR/$CAMEL_NAME" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME" + echo "Created symlink: $CAMEL_NAME -> $BASE_NAME" + fi + + # Create extension-less symlink for both versions + NO_EXT_BASE="${BASE_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE" + echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME" + fi + + NO_EXT_CAMEL="${CAMEL_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ] && [ ! -L "$MODULE_DIR/$NO_EXT_CAMEL" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL" + echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME" + fi + fi + done - # Create symlinks without extension - ln -sf "$hyphenated.js" "$MODULES_DIR/$hyphenated" - ln -sf "$hyphenated.js" "$MODULES_DIR/$camelCase" - echo "Created extension-less symlinks: $hyphenated, $camelCase -> $hyphenated.js" - # Check if camelCase version exists - elif [ -f "$MODULES_DIR/$camelCase.js" ]; then - # Create hyphenated symlink - ln -sf "$camelCase.js" "$MODULES_DIR/$hyphenated.js" - echo "Created symlink: $hyphenated.js -> $camelCase.js" + # Create symlinks for camelCase modules (only non-symlinked files) + for module in "$MODULE_DIR"/[a-z]*[A-Z]*.js; do + if [ -f "$module" ] && [ ! -L "$module" ]; then + # Convert camelCase to hyphenated + BASE_NAME=$(basename "$module") + HYPHEN_NAME=$(echo "$BASE_NAME" | sed -E 's/([a-z])([A-Z])/\1-\L\2/g') + + # Create hyphenated symlink if needed + if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ] && [ ! -L "$MODULE_DIR/$HYPHEN_NAME" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME" + echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME" + fi + + # Create extension-less symlink for both versions + NO_EXT_BASE="${BASE_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE" + echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME" + fi + + NO_EXT_HYPHEN="${HYPHEN_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ] && [ ! -L "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN" + echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME" + fi + fi + done - # Create symlinks without extension - ln -sf "$camelCase.js" "$MODULES_DIR/$hyphenated" - ln -sf "$camelCase.js" "$MODULES_DIR/$camelCase" - echo "Created extension-less symlinks: $hyphenated, $camelCase -> $camelCase.js" + echo "Module symlinks created successfully" else - echo "Warning: Neither $hyphenated.js nor $camelCase.js exists in $MODULES_DIR" + echo "Error: Module directory not found at $MODULE_DIR" + exit 1 fi } -# Create symlinks for all modules -create_bidirectional_links "rss-feed-manager" "rssFeedManager" -create_bidirectional_links "transmission-client" "transmissionClient" -create_bidirectional_links "post-processor" "postProcessor" - -echo "Module symlinks created successfully." \ No newline at end of file +# Execute the symlink creation function +create_module_symlinks \ No newline at end of file diff --git a/scripts/test-and-start.sh b/scripts/test-and-start.sh index 28d00d6..9b18b40 100755 --- a/scripts/test-and-start.sh +++ b/scripts/test-and-start.sh @@ -25,17 +25,25 @@ if [ ! -d "$DATA_DIR" ]; then fi # Set permissions -chmod -R 755 "$DATA_DIR" +chmod -R 755 "$DATA_DIR" || { + echo "Warning: Failed to set permissions on data directory" +} # Check for RSS files if [ ! -f "$DATA_DIR/rss-feeds.json" ]; then echo "Creating initial empty rss-feeds.json file" - echo "[]" > "$DATA_DIR/rss-feeds.json" + echo "[]" > "$DATA_DIR/rss-feeds.json" || { + echo "ERROR: Failed to create rss-feeds.json file" + exit 1 + } fi if [ ! -f "$DATA_DIR/rss-items.json" ]; then echo "Creating initial empty rss-items.json file" - echo "[]" > "$DATA_DIR/rss-items.json" + echo "[]" > "$DATA_DIR/rss-items.json" || { + echo "ERROR: Failed to create rss-items.json file" + exit 1 + } fi # Find the node executable path @@ -57,12 +65,74 @@ if [ -z "$NODE_PATH" ]; then fi # Create module symlinks to ensure compatibility -if [ -f "$APP_DIR/scripts/create-module-links.sh" ]; then - echo "Creating module symlinks for compatibility..." - bash "$APP_DIR/scripts/create-module-links.sh" -else - echo "Warning: Module symlink script not found" -fi +echo "Creating module symlinks for compatibility..." +MODULE_DIR="$APP_DIR/modules" + +# Create a function to make bidirectional symlinks +create_module_symlinks() { + if [ -d "$MODULE_DIR" ]; then + # Create symlinks for hyphenated modules + for module in "$MODULE_DIR"/*-*.js; do + if [ -f "$module" ]; then + # Convert hyphenated to camelCase + BASE_NAME=$(basename "$module") + CAMEL_NAME=$(echo "$BASE_NAME" | sed -E 's/-([a-z])/\U\1/g') + + # Create camelCase symlink if needed + if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME" + echo "Created symlink: $CAMEL_NAME -> $BASE_NAME" + fi + + # Create extension-less symlink for both versions + NO_EXT_BASE="${BASE_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE" + echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME" + fi + + NO_EXT_CAMEL="${CAMEL_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL" + echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME" + fi + fi + done + + # Create symlinks for camelCase modules + for module in "$MODULE_DIR"/[a-z]*[A-Z]*.js; do + if [ -f "$module" ]; then + # Convert camelCase to hyphenated + BASE_NAME=$(basename "$module") + HYPHEN_NAME=$(echo "$BASE_NAME" | sed -E 's/([a-z])([A-Z])/\1-\L\2/g') + + # Create hyphenated symlink if needed + if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME" + echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME" + fi + + # Create extension-less symlink for both versions + NO_EXT_BASE="${BASE_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE" + echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME" + fi + + NO_EXT_HYPHEN="${HYPHEN_NAME%.js}" + if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then + ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN" + echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME" + fi + fi + done + else + echo "Warning: Module directory not found at $MODULE_DIR" + fi +} + +# Execute the symlink creation function +create_module_symlinks # Start the application cd "$APP_DIR" || { echo "Failed to change to application directory"; exit 1; } diff --git a/server.js b/server.js index cd5d763..e0da954 100644 --- a/server.js +++ b/server.js @@ -21,60 +21,62 @@ const semver = require('semver'); // For semantic version comparison // Import custom modules let RssFeedManager, TransmissionClient, PostProcessor; -// Helper function to try multiple module paths -function tryRequire(modulePaths) { +/** + * Helper function to try multiple module paths + * This function tries to require a module using different naming conventions + * to work around issues with module resolution in different Node.js environments + * + * @param {string} baseName - The base module name without extension or path + * @returns {Object} The loaded module + * @throws {Error} If module cannot be loaded from any path + */ +function loadModule(baseName) { + // Generate all possible module paths + const paths = [ + `./modules/${baseName}.js`, // With extension + `./modules/${baseName}`, // Without extension + + // Convert hyphenated to camelCase + `./modules/${baseName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}.js`, + `./modules/${baseName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}`, + + // Convert camelCase to hyphenated + `./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}.js`, + `./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}` + ]; + let lastError = null; - for (const modulePath of modulePaths) { + for (const modulePath of paths) { try { return require(modulePath); } catch (err) { - console.log(`Attempted to load module from ${modulePath}, but got error: ${err.message}`); + if (err.code !== 'MODULE_NOT_FOUND' || !err.message.includes(modulePath)) { + // This is a real error in the module, not just not finding it + throw err; + } lastError = err; } } - throw lastError; + + // If we get here, we couldn't load the module from any path + const error = new Error(`Could not load module '${baseName}' from any path. Original error: ${lastError.message}`); + error.original = lastError; + throw error; } -// Try loading modules with various namings (with and without .js extension, with hyphens or camelCase) +// Try loading modules with improved error reporting try { - const rssPaths = [ - './modules/rss-feed-manager.js', - './modules/rssFeedManager.js', - './modules/rss-feed-manager', - './modules/rssFeedManager' - ]; - RssFeedManager = tryRequire(rssPaths); + RssFeedManager = loadModule('rss-feed-manager'); console.log('Successfully loaded RssFeedManager module'); -} catch (err) { - console.error('Failed to load RssFeedManager module:', err); - process.exit(1); -} - -try { - const transmissionPaths = [ - './modules/transmission-client.js', - './modules/transmissionClient.js', - './modules/transmission-client', - './modules/transmissionClient' - ]; - TransmissionClient = tryRequire(transmissionPaths); + + TransmissionClient = loadModule('transmission-client'); console.log('Successfully loaded TransmissionClient module'); -} catch (err) { - console.error('Failed to load TransmissionClient module:', err); - process.exit(1); -} - -try { - const processorPaths = [ - './modules/post-processor.js', - './modules/postProcessor.js', - './modules/post-processor', - './modules/postProcessor' - ]; - PostProcessor = tryRequire(processorPaths); + + PostProcessor = loadModule('post-processor'); console.log('Successfully loaded PostProcessor module'); } catch (err) { - console.error('Failed to load PostProcessor module:', err); + console.error('Fatal error loading required module:', err.message); + console.error('Please make sure all module files exist and are valid JavaScript'); process.exit(1); } @@ -456,7 +458,7 @@ app.get('/api/status', authenticateJWT, async (req, res) => { res.json({ success: true, status: 'running', - version: '2.0.6', + version: APP_VERSION, // Use the dynamic APP_VERSION from package.json transmissionConnected: transmissionStatus.connected, transmissionVersion: transmissionStatus.version, transmissionStats: {