Skip to content

Commit

Permalink
[IMP] client: validate and fill vars in addons paths
Browse files Browse the repository at this point in the history
  • Loading branch information
mmahrouss committed Nov 5, 2024
1 parent 861d7ea commit 35fa996
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 77 deletions.
2 changes: 1 addition & 1 deletion server/src/core/odoo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ impl Odoo {
if configurations.contains_key(&selected_configuration) {
let odoo_conf = configurations.get(&selected_configuration).unwrap();
let odoo_conf = odoo_conf.as_object().unwrap();
config.addons = odoo_conf.get("addons").expect("An odoo config must contains a addons value")
config.addons = odoo_conf.get("validatedAddonsPaths").expect("An odoo config must contains a addons value")
.as_array().expect("the addons value must be an array")
.into_iter().map(|v| v.as_str().unwrap().to_string()).collect();
config.odoo_path = odoo_conf.get("odooPath").expect("odooPath must exist").as_str().expect("odooPath must be a String").to_string();
Expand Down
87 changes: 61 additions & 26 deletions vscode/client/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExtensionContext, Uri, Webview, workspace} from "vscode";
import { ExtensionContext, Uri, Webview, window, workspace } from "vscode";
import * as fs from 'fs';
import * as path from "path";
import { URI } from "vscode-languageclient";
import untildify from 'untildify';
import * as readline from 'readline';
Expand All @@ -17,7 +18,7 @@ import * as readline from 'readline';
* @returns A URI pointing to the file/resource
*/
export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
}

export function getNonce() {
Expand All @@ -33,42 +34,76 @@ export function getNonce() {

export async function getCurrentConfig(context: ExtensionContext) {
const configs = JSON.parse(JSON.stringify(workspace.getConfiguration().get("Odoo.configurations")));
const activeConfig: number = Number(workspace.getConfiguration().get('Odoo.selectedConfiguration'));
const activeConfig: number = Number(workspace.getConfiguration().get('Odoo.selectedConfiguration'));

// if config is disabled return nothing
if(activeConfig == -1 || !configs[activeConfig]){
if (activeConfig == -1 || !configs[activeConfig]) {
return null;
}
return (Object.keys(configs[activeConfig]).length !== 0? configs[activeConfig] : null);
return (Object.keys(configs[activeConfig]).length !== 0 ? configs[activeConfig] : null);
}

export async function evaluateOdooPath(odooPath){
if(!odooPath){
return
export function isReallyModule(directoryPath: string, moduleName: string): boolean {
const fullPath = path.join(directoryPath, moduleName, "__manifest__.py");
return fs.existsSync(fullPath) && fs.lstatSync(fullPath).isFile();
}

export function isAddonPath(directoryPath: string): boolean {
return fs.existsSync(directoryPath) && fs.statSync(directoryPath).isDirectory() && fs.readdirSync(directoryPath).some((name) =>
isReallyModule(directoryPath, name)
);
}

export async function fillTemplate(template, vars = {}) {
const handler = new Function('vars', [
'const tagged = ( ' + Object.keys(vars).join(', ') + ' ) =>',
'`' + template + '`',
'return tagged(...Object.values(vars))'
].join('\n'));
try {
return handler(vars);
} catch (error) {
if (error instanceof ReferenceError) {
const missingVariableMatch = error.message.match(/(\w+) is not defined/);
if (missingVariableMatch) {
const missingVariable = missingVariableMatch[1];
window.showErrorMessage(`Invalid path template paramater "${missingVariable}". Only "workspaceFolder" and "userHome" are currently supported`)
}
}
throw error;
}
}

export async function validateAddonPath(addonPath) {
const workspaceFolders = workspace.workspaceFolders;
odooPath = odooPath.replaceAll("\\","/");
addonPath = addonPath.replaceAll("\\", "/");
for (const i in workspaceFolders) {
const folder = workspaceFolders[i];
const PATH_VAR_LOCAL = { ...global.PATH_VARIABLES };
PATH_VAR_LOCAL["workspaceFolder"] = folder.uri.fsPath.replaceAll("\\", "/");
const filledPath = path.resolve(await fillTemplate(addonPath, PATH_VAR_LOCAL)).replaceAll("\\", "/");
if (filledPath && isAddonPath(filledPath)) {
return filledPath;
}
}
return null;
}

const fillTemplate = (template, vars = {}) => {
const handler = new Function('vars', [
'const tagged = ( ' + Object.keys(vars).join(', ') + ' ) =>',
'`' + template + '`',
'return tagged(...Object.values(vars))'
].join('\n'));
const res = handler(vars)
return res;
};
export async function evaluateOdooPath(odooPath) {
if (!odooPath) {
return
}
const workspaceFolders = workspace.workspaceFolders;
odooPath = odooPath.replaceAll("\\", "/");


for (const i in workspaceFolders){
for (const i in workspaceFolders) {
const folder = workspaceFolders[i];
let PATH_VAR_LOCAL = global.PATH_VARIABLES;
PATH_VAR_LOCAL["workspaceFolder"] = folder.uri.fsPath.replaceAll("\\","/");
odooPath = fillTemplate(odooPath,PATH_VAR_LOCAL);
const version = await getOdooVersion(odooPath);
if (version){
global.PATH_VARIABLES["workspaceFolder"] = folder.uri.path.replaceAll("\\","/");
return {"path": odooPath,"version": version};
global.PATH_VARIABLES["workspaceFolder"] = folder.uri.fsPath.replaceAll("\\", "/");
const filledOdooPath = path.resolve(await fillTemplate(odooPath, global.PATH_VARIABLES)).replaceAll("\\", "/");
const version = await getOdooVersion(filledOdooPath);
if (version) {
return { "path": odooPath, "version": version };
}
}
return null;
Expand Down
123 changes: 78 additions & 45 deletions vscode/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ import {
ConfigurationsChange,
clientStopped
} from './common/events'
import {
IInterpreterDetails,
getInterpreterDetails,
initializePython,
onDidChangePythonInterpreter,
onDidChangePythonInterpreterEvent
import {
IInterpreterDetails,
getInterpreterDetails,
initializePython,
onDidChangePythonInterpreter,
onDidChangePythonInterpreterEvent
} from "./common/python";
import { evaluateOdooPath, getCurrentConfig } from "./common/utils";
import { evaluateOdooPath, getCurrentConfig, validateAddonPath } from "./common/utils";
import { getConfigurationStructure, stateInit } from "./common/validation";
import { execSync } from "child_process";
import {
Expand Down Expand Up @@ -118,7 +118,7 @@ function setMissingStateVariables(context: ExtensionContext) {
let globalVariables = new Map<string, any>([
["Odoo.nextConfigId", stateInit["Odoo.nextConfigId"]],
["Odoo.stateVersion", stateInit["Odoo.stateVersion"]],
["Odoo.lastRecordedVersion", context.extension.packageJSON.version],
["Odoo.lastRecordedVersion", context.extension.packageJSON.version],
]);

for (let key of globalVariables.keys()) {
Expand Down Expand Up @@ -364,7 +364,7 @@ async function initLanguageServerClient(context: ExtensionContext, outputChannel
}
})
);
global.PATH_VARIABLES = {"userHome" : homedir().replaceAll("\\","\\\\")};
global.PATH_VARIABLES = {"userHome" : homedir().replaceAll("\\","/")};
if (autoStart) {
await client.start();
}
Expand Down Expand Up @@ -395,7 +395,7 @@ function deleteOldFiles(context: ExtensionContext) {
const files = fs.readdirSync(logDir.fsPath).filter(fn => fn.startsWith('odoo_logs_'));
let dateLimit = new Date();
dateLimit.setDate(dateLimit.getDate() - 2);

for (const file of files) {
const date = extractDateFromFileName(file);
if (date && date < dateLimit) {
Expand All @@ -407,38 +407,71 @@ function deleteOldFiles(context: ExtensionContext) {
}

async function checkAddons(context: ExtensionContext) {
let files = await workspace.findFiles('**/__manifest__.py')
let currentConfig = await getCurrentConfig(context);
if (currentConfig) {
let missingFiles = files.filter(file => {
return !(
currentConfig.addons.some((addon) => file.fsPath.replaceAll("\\","/").startsWith(addon)) ||
file.fsPath.replaceAll("\\","/").startsWith(currentConfig.odooPath)
)
})
let missingPaths = [...new Set(missingFiles.map(file => {
let filePath = file.fsPath.split(path.sep)
return filePath.slice(0, filePath.length - 2).join(path.sep)
}))]
if (missingPaths.length > 0) {
global.LSCLIENT.warn("Missing addon paths : " + JSON.stringify(missingPaths))
window.showWarningMessage(
`We detected addon paths that weren't added in the current configuration. Would you like to add them?`,
"Update current configuration",
"View Paths",
"Ignore"
).then(selection => {
switch (selection) {
case ("Update current configuration"):
ConfigurationWebView.render(context, currentConfig);
break
case ("View Paths"):
global.LSCLIENT.outputChannel.show();
break
}
});
if (!currentConfig) {
return
}
const validAddons = [];
const invalidAddons = [];
for (const addonPath of currentConfig.addons) {
const validationResult = await validateAddonPath(addonPath);

if (validationResult !== null) {
validAddons.push(validationResult);
} else {
invalidAddons.push(addonPath);
}
}
if (invalidAddons.length > 0) {
const invalidPathsMessage = invalidAddons.join("\n");

window.showWarningMessage(
`The following addon paths in this configuration seem invalid: (${invalidPathsMessage}). Would you like to change the configuration?`,
"Update current configuration",
"Ignore"
).then(selection => {
switch (selection) {
case "Update current configuration":
ConfigurationWebView.render(context, currentConfig);
break;
}
});
}
let configs = JSON.parse(JSON.stringify(workspace.getConfiguration().get("Odoo.configurations")));
configs[currentConfig.id]["validatedAddonsPaths"] = validAddons;
workspace.getConfiguration().update("Odoo.configurations", configs, ConfigurationTarget.Global);

// Check if workspace folders could also be addons folder
currentConfig = configs[currentConfig.id];
let files = await workspace.findFiles('**/__manifest__.py')
let missingFiles = files.filter(file => {
return !(
currentConfig.addons.some((addon) => file.fsPath.replaceAll("\\", "/").startsWith(addon)) ||
file.fsPath.replaceAll("\\", "/").startsWith(currentConfig.odooPath)
)
})
let missingPaths = [...new Set(missingFiles.map(file => {
let filePath = file.fsPath.split(path.sep)
return filePath.slice(0, filePath.length - 2).join(path.sep)
}))]
if (missingPaths.length > 0) {
global.LSCLIENT.warn("Missing addon paths : " + JSON.stringify(missingPaths))
window.showWarningMessage(
"We detected addon paths that weren't added in the current configuration. Would you like to add them?",
"Update current configuration",
"View Paths",
"Ignore"
).then(selection => {
switch (selection) {
case ("Update current configuration"):
ConfigurationWebView.render(context, currentConfig);
break
case ("View Paths"):
global.LSCLIENT.outputChannel.show();
break
}
});
}
}

async function checkOdooPath(context: ExtensionContext) {
Expand All @@ -464,7 +497,7 @@ async function checkOdooPath(context: ExtensionContext) {
})
return
}


let odooFound = currentConfig ? workspace.getWorkspaceFolder(Uri.file(odoo.path)) : true
if (!odooFound) {
Expand Down Expand Up @@ -520,7 +553,7 @@ async function initializeSubscriptions(context: ExtensionContext): Promise<void>
await setStatusConfig(context);
const RELOAD_ON_CHANGE = ["rawOdooPath","addons","pythonPath"];
if (changes && (changes.some(r=> RELOAD_ON_CHANGE.includes(r)))) {

await checkOdooPath(context);
await checkAddons(context);
if (client.diagnostics) client.diagnostics.clear();
Expand All @@ -545,13 +578,13 @@ async function initializeSubscriptions(context: ExtensionContext): Promise<void>
selectedConfigurationChange.event(async (oldConfig) => {
try {
if (!global.CAN_QUEUE_CONFIG_CHANGE) return;

if (global.CLIENT_IS_STOPPING) {
global.CAN_QUEUE_CONFIG_CHANGE = false;
await waitForClientStop();
global.CAN_QUEUE_CONFIG_CHANGE = true;
}

let client = global.LSCLIENT;
const config = await getCurrentConfig(context)
if (config) {
Expand Down Expand Up @@ -802,7 +835,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
validateState(context);
handleMigration(context)


await initStatusBar(context);
await initializeSubscriptions(context);

Expand Down Expand Up @@ -880,7 +913,7 @@ export async function getStandalonePythonVersion(context: ExtensionContext): Pro

const versionString = execSync(`${pythonPath} --version`).toString().replace("Python ", "")

return semver.parse(versionString)
return semver.parse(versionString)
}

async function checkStandalonePythonVersion(context: ExtensionContext): Promise<boolean>{
Expand Down
8 changes: 4 additions & 4 deletions vscode/client/views/configurations/configurationWebView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class ConfigurationWebView {
cspSource: webview.cspSource,
nonce: nonce,
odooVersion: configsVersion ? configsVersion[`${this.configId}`] : null,
pythonExtensionMode: global.IS_PYTHON_EXTENSION_READY,
pythonExtensionMode: global.IS_PYTHON_EXTENSION_READY,
};
return ejs.render(htmlFile, data);
}
Expand All @@ -148,7 +148,7 @@ export class ConfigurationWebView {
if (configs[this.configId]["rawOdooPath"] != rawOdooPath) {
changes.push("rawOdooPath");
}

if (configs[this.configId]["name"] != name) {
changes.push("name");
}
Expand Down Expand Up @@ -184,7 +184,7 @@ export class ConfigurationWebView {
if (workspace.getConfiguration().get("Odoo.selectedConfigurations") == this.configId) {
ConfigurationsChange.fire(changes);
}

if (changes.includes('name')){
this._updateWebviewTitle(this._panel, name)
}
Expand Down Expand Up @@ -303,7 +303,7 @@ export class ConfigurationWebView {
version: version
});
};

let versions = this._context.globalState.get('Odoo.configsVersion', {});
const odoo = await evaluateOdooPath(rawOdooPath);
if (odoo){
Expand Down
2 changes: 1 addition & 1 deletion vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"default": "python3",
"description": "the python that will be used if you don't have the python extension"
},
"addonsPath": {
"addons": {
"type": "array",
"default": [],
"description": "paths of addons",
Expand Down

0 comments on commit 35fa996

Please sign in to comment.