aboutsummaryrefslogtreecommitdiff
path: root/src/server/ApiManagers/FlashcardManager.ts
blob: fd7c424370d144f85c82fb30c17bbec51791814e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * @file FlashcardManager.ts
 * @description This file defines the FlashcardManager class, responsible for managing API routes
 * related to flashcard creation and manipulation. It provides functionality for handling file processing,
 * running Python scripts in a virtual environment, and managing dependencies.
 */

import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { Method } from '../RouteManager';
import ApiManager, { Registration } from './ApiManager';

/**
 * Runs a Python script using the provided virtual environment and passes file and option arguments.
 * @param {string} venvPath - Path to the virtual environment.
 * @param {string} scriptPath - Path to the Python script.
 * @param {string} [file] - Optional file to pass to the Python script.
 * @param {string} [drag] - Optional argument to control drag mode.
 * @param {string} [smart] - Optional argument to control smart mode.
 * @returns {Promise<string>} - Resolves with the output from the Python script, or rejects on error.
 */
function runPythonScript(venvPath: string, scriptPath: string, file?: string, drag?: string, smart?: string): Promise<string> {
    return new Promise((resolve, reject) => {
        const pythonPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'python.exe') : path.join(venvPath, 'bin', 'python3');

        const tempFilePath = path.join(__dirname, `temp_data.txt`); // Unique temp file name

        if (file) {
            // Write the raw file data to the temp file without conversion
            fs.writeFileSync(tempFilePath, file, 'utf8');
        }

        const pythonProcess = spawn(
            pythonPath,
            [scriptPath, file ? tempFilePath : undefined, drag, smart].filter(arg => arg !== undefined)
        );

        let pythonOutput = '';
        let stderrOutput = '';

        pythonProcess.stdout.on('data', data => {
            pythonOutput += data.toString();
        });

        pythonProcess.stderr.on('data', data => {
            stderrOutput += data.toString();
        });

        pythonProcess.on('close', code => {
            if (code === 0) {
                resolve(pythonOutput);
            } else {
                reject(`Python process exited with code ${code}: ${stderrOutput}`);
            }
        });
    });
}

/**
 * Installs Python dependencies using pip in the specified virtual environment.
 * @param {string} venvPath - Path to the virtual environment.
 * @param {string} requirementsPath - Path to the requirements.txt file.
 * @returns {Promise<void>} - Resolves when dependencies are successfully installed, rejects on failure.
 */
function installDependencies(venvPath: string, requirementsPath: string): Promise<void> {
    return new Promise((resolve, reject) => {
        const pipPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'pip.exe') : path.join(venvPath, 'bin', 'pip3');

        const installProcess = spawn(pipPath, ['install', '-r', requirementsPath]);

        installProcess.stdout.on('data', data => {
            console.log(`pip stdout: ${data}`);
        });

        installProcess.stderr.on('data', data => {
            console.error(`pip stderr: ${data}`);
        });

        installProcess.on('close', code => {
            if (code !== 0) {
                reject(`Failed to install dependencies. Exit code: ${code}`);
            } else {
                resolve();
            }
        });
    });
}

/**
 * Creates a new Python virtual environment.
 * @param {string} venvPath - Path to the virtual environment that will be created.
 * @returns {Promise<void>} - Resolves when the virtual environment is successfully created, rejects on failure.
 */
function createVirtualEnvironment(venvPath: string): Promise<void> {
    return new Promise((resolve, reject) => {
        const createVenvProcess = spawn('python3', ['-m', 'venv', venvPath]);

        createVenvProcess.on('close', code => {
            if (code !== 0) {
                reject(`Failed to create virtual environment. Exit code: ${code}`);
            } else {
                resolve();
            }
        });
    });
}

/**
 * Manages the creation of the virtual environment, installation of dependencies, and running of the Python script.
 * @param {string} [file] - Optional file data to be processed by the Python script.
 * @param {string} [drag] - Optional argument controlling drag mode.
 * @param {string} [smart] - Optional argument controlling smart mode.
 * @returns {Promise<string>} - Resolves with the Python script output, or rejects on failure.
 */
async function manageVenvAndRunScript(file?: string, drag?: string, smart?: string): Promise<string> {
    const venvPath = path.join(__dirname, '../flashcard/venv'); // Virtual environment path
    const requirementsPath = path.join(__dirname, '../flashcard/requirements.txt');
    const pythonScriptPath = path.join(__dirname, '../flashcard/labels.py');
    console.log('venvPath:', venvPath);

    // Check if the virtual environment exists
    if (!fs.existsSync(path.join(venvPath, 'bin', 'python3')) && !fs.existsSync(path.join(venvPath, 'Scripts', 'python.exe'))) {
        await createVirtualEnvironment(venvPath);

        await installDependencies(venvPath, requirementsPath);
    }

    return runPythonScript(venvPath, pythonScriptPath, file, drag, smart);
}

/**
 * FlashcardManager class responsible for managing API routes related to flashcard functionality.
 * It initializes API routes for handling YouTube subscriptions and label creation using a Python backend.
 */
export default class FlashcardManager extends ApiManager {
    /**
     * Initializes the API routes for the FlashcardManager class.
     * @param {Registration} register - The registration function for defining API routes.
     */
    protected initialize(register: Registration): void {
        register({
            method: Method.POST,
            subscription: '/labels',
            secureHandler: async ({ req, res }) => {
                const { file, drag, smart } = req.body;

                try {
                    // Run the Python process
                    const result = await manageVenvAndRunScript(file, drag, smart);
                    res.status(200).send({ result });
                } catch (error) {
                    console.error('Error initiating document creation:', error);
                    res.status(500).send({
                        error: 'Failed to initiate document creation',
                    });
                }
            },
        });
    }
}