feat: implement path based operations
This commit is contained in:
@@ -8,6 +8,21 @@ import {
|
||||
|
||||
import * as request from 'request';
|
||||
|
||||
/**
|
||||
* Interface for path resolution cache
|
||||
*/
|
||||
interface PathCacheEntry {
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
parentId: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path cache to store resolved paths for performance
|
||||
*/
|
||||
const pathCache: Record<string, PathCacheEntry> = {};
|
||||
|
||||
/**
|
||||
* Make an API request to kDrive API
|
||||
*/
|
||||
@@ -99,4 +114,180 @@ export function handleApiError(this: IExecuteFunctions, error: any): void {
|
||||
});
|
||||
|
||||
throw new Error(`kDrive API Error: ${errorMessage}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear path cache
|
||||
*/
|
||||
export function clearPathCache(): void {
|
||||
Object.keys(pathCache).forEach(key => delete pathCache[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path - convert to absolute path and handle path separators
|
||||
*/
|
||||
export function normalizePath(path: string): string {
|
||||
// Replace Windows-style backslashes with forward slashes
|
||||
let normalized = path.replace(/\\/g, '/');
|
||||
|
||||
// Remove leading/trailing slashes
|
||||
normalized = normalized.replace(/^\/+|\/+$/g, '');
|
||||
|
||||
// Handle empty path (root)
|
||||
if (!normalized) {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
// Collapse multiple consecutive slashes
|
||||
normalized = normalized.replace(/\/+/g, '/');
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse path into components
|
||||
*/
|
||||
export function parsePath(path: string): string[] {
|
||||
const normalized = normalizePath(path);
|
||||
|
||||
if (normalized === 'root') {
|
||||
return ['root'];
|
||||
}
|
||||
|
||||
// Split by slash and filter out empty segments
|
||||
return normalized.split('/').filter(component => component.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file info by ID
|
||||
*/
|
||||
export async function getFileInfoById(
|
||||
this: IExecuteFunctions,
|
||||
fileId: string,
|
||||
driveId: string,
|
||||
credentials: IDataObject
|
||||
): Promise<any> {
|
||||
if (fileId === 'root') {
|
||||
return {
|
||||
id: 'root',
|
||||
name: 'root',
|
||||
type: 'directory',
|
||||
parent_id: null
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await kdriveApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/3/drive/${driveId}/files/${fileId}`,
|
||||
{},
|
||||
credentials
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get file info for ID ${fileId}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve path to file ID
|
||||
*/
|
||||
export async function resolvePathToId(
|
||||
this: IExecuteFunctions,
|
||||
path: string,
|
||||
driveId: string,
|
||||
credentials: IDataObject
|
||||
): Promise<string> {
|
||||
const normalizedPath = normalizePath(path);
|
||||
|
||||
// Check cache first
|
||||
if (pathCache[normalizedPath]) {
|
||||
return pathCache[normalizedPath].fileId;
|
||||
}
|
||||
|
||||
// Handle root path
|
||||
if (normalizedPath === 'root') {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
const pathComponents = parsePath(normalizedPath);
|
||||
let currentId = 'root';
|
||||
|
||||
// Traverse path component by component
|
||||
for (const component of pathComponents) {
|
||||
// Get contents of current directory
|
||||
const endpoint = currentId === 'root'
|
||||
? `/3/drive/${driveId}/files/1/files`
|
||||
: `/3/drive/${driveId}/files/${currentId}/files`;
|
||||
|
||||
const response = await kdriveApiRequest.call(this, 'GET', endpoint, {}, credentials);
|
||||
|
||||
// Find the component in the current directory
|
||||
const foundItem = response.items.find((item: any) =>
|
||||
item.name === component && item.type === 'directory'
|
||||
);
|
||||
|
||||
if (!foundItem) {
|
||||
throw new Error(`Path component not found: ${component} in ${currentId}`);
|
||||
}
|
||||
|
||||
currentId = foundItem.id;
|
||||
}
|
||||
|
||||
// Cache the resolved path
|
||||
pathCache[normalizedPath] = {
|
||||
fileId: currentId,
|
||||
fileName: pathComponents[pathComponents.length - 1],
|
||||
parentId: pathComponents.length === 1 ? 'root' :
|
||||
pathCache[parsePath(normalizedPath).slice(0, -1).join('/')]?.fileId || 'root',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
return currentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve file ID to path
|
||||
*/
|
||||
export async function resolveIdToPath(
|
||||
this: IExecuteFunctions,
|
||||
fileId: string,
|
||||
driveId: string,
|
||||
credentials: IDataObject
|
||||
): Promise<string> {
|
||||
if (fileId === 'root') {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
// Check if we have this in cache
|
||||
const cachedEntry = Object.values(pathCache).find(entry => entry.fileId === fileId);
|
||||
if (cachedEntry) {
|
||||
return Object.keys(pathCache).find(key => pathCache[key].fileId === fileId) || fileId;
|
||||
}
|
||||
|
||||
// Get file info
|
||||
const fileInfo = await getFileInfoById.call(this, fileId, driveId, credentials);
|
||||
|
||||
if (!fileInfo.parent_id) {
|
||||
return fileInfo.name; // Root level file
|
||||
}
|
||||
|
||||
// Recursively get parent path
|
||||
const parentPath = await resolveIdToPath.call(this, fileInfo.parent_id, driveId, credentials);
|
||||
|
||||
// Construct full path
|
||||
const fullPath = parentPath === 'root'
|
||||
? fileInfo.name
|
||||
: `${parentPath}/${fileInfo.name}`;
|
||||
|
||||
// Cache the result
|
||||
pathCache[fullPath] = {
|
||||
fileId: fileInfo.id,
|
||||
fileName: fileInfo.name,
|
||||
parentId: fileInfo.parent_id,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
import {
|
||||
kdriveApiRequest,
|
||||
handleApiError,
|
||||
resolvePathToId,
|
||||
parsePath,
|
||||
normalizePath,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class KDrive implements INodeType {
|
||||
@@ -80,7 +83,7 @@ export class KDrive implements INodeType {
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
options: [
|
||||
{
|
||||
name: 'List Files',
|
||||
value: 'listFiles',
|
||||
@@ -91,21 +94,41 @@ export class KDrive implements INodeType {
|
||||
value: 'getFileInfo',
|
||||
description: 'Get information about a file',
|
||||
},
|
||||
{
|
||||
name: 'Get File Info by Path',
|
||||
value: 'getFileInfoByPath',
|
||||
description: 'Get information about a file using path',
|
||||
},
|
||||
{
|
||||
name: 'Upload File',
|
||||
value: 'uploadFile',
|
||||
description: 'Upload a file',
|
||||
},
|
||||
{
|
||||
name: 'Upload File by Path',
|
||||
value: 'uploadFileByPath',
|
||||
description: 'Upload a file to specific path',
|
||||
},
|
||||
{
|
||||
name: 'Download File',
|
||||
value: 'downloadFile',
|
||||
description: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Download File by Path',
|
||||
value: 'downloadFileByPath',
|
||||
description: 'Download a file using path',
|
||||
},
|
||||
{
|
||||
name: 'Delete File',
|
||||
value: 'deleteFile',
|
||||
description: 'Delete a file (move to trash)',
|
||||
},
|
||||
{
|
||||
name: 'Delete File by Path',
|
||||
value: 'deleteFileByPath',
|
||||
description: 'Delete a file using path',
|
||||
},
|
||||
{
|
||||
name: 'Search Files',
|
||||
value: 'searchFiles',
|
||||
@@ -116,6 +139,11 @@ export class KDrive implements INodeType {
|
||||
value: 'getFileVersions',
|
||||
description: 'Get file versions',
|
||||
},
|
||||
{
|
||||
name: 'Get File Versions by Path',
|
||||
value: 'getFileVersionsByPath',
|
||||
description: 'Get file versions using path',
|
||||
},
|
||||
],
|
||||
default: 'listFiles',
|
||||
},
|
||||
@@ -166,12 +194,52 @@ export class KDrive implements INodeType {
|
||||
default: 'root',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['listFiles'],
|
||||
resource: ['directory'],
|
||||
operation: ['createDirectory', 'createFile'],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the parent directory (use "root" for root directory)',
|
||||
},
|
||||
// Path-based parameters for file operations
|
||||
{
|
||||
displayName: 'File Path',
|
||||
name: 'filePath',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['getFileInfoByPath', 'downloadFileByPath', 'deleteFileByPath', 'getFileVersionsByPath'],
|
||||
},
|
||||
},
|
||||
description: 'The path to the file (e.g., "documents/report.pdf")',
|
||||
},
|
||||
{
|
||||
displayName: 'Directory Path',
|
||||
name: 'directoryPath',
|
||||
type: 'string',
|
||||
default: 'root',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['listFiles'],
|
||||
},
|
||||
},
|
||||
description: 'The path to the directory (use "root" for root, e.g., "documents/project")',
|
||||
},
|
||||
{
|
||||
displayName: 'Upload Path',
|
||||
name: 'uploadPath',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['uploadFileByPath'],
|
||||
},
|
||||
},
|
||||
description: 'The full path where to upload the file (e.g., "documents/report.pdf")',
|
||||
},
|
||||
// Directory ID for directory operations
|
||||
{
|
||||
displayName: 'Parent Directory ID',
|
||||
@@ -322,9 +390,18 @@ export class KDrive implements INodeType {
|
||||
const driveId = credentials.driveId as string;
|
||||
|
||||
if (operation === 'listFiles') {
|
||||
const parentDirectoryId = this.getNodeParameter('parentDirectoryId', i) as string;
|
||||
let parentDirectoryId = this.getNodeParameter('parentDirectoryId', i) as string;
|
||||
const directoryPath = this.getNodeParameter('directoryPath', i) as string;
|
||||
|
||||
// If directoryPath is provided, resolve it to an ID
|
||||
if (directoryPath && directoryPath !== 'root') {
|
||||
parentDirectoryId = await resolvePathToId.call(this, directoryPath, driveId, credentials);
|
||||
} else if (directoryPath === 'root') {
|
||||
parentDirectoryId = 'root';
|
||||
}
|
||||
|
||||
const endpoint = parentDirectoryId === 'root'
|
||||
? `/3/drive/${driveId}/files/root/files`
|
||||
? `/3/drive/${driveId}/files/1/files`
|
||||
: `/3/drive/${driveId}/files/${parentDirectoryId}/files`;
|
||||
const response = await kdriveApiRequest.call(this, 'GET', endpoint, {}, credentials);
|
||||
returnData.push({ json: response });
|
||||
@@ -364,10 +441,48 @@ export class KDrive implements INodeType {
|
||||
|
||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/search`, params, credentials);
|
||||
returnData.push({ json: response });
|
||||
} else if (operation === 'getFileVersions') {
|
||||
} else if (operation === 'getFileVersions') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}/versions`, {}, credentials);
|
||||
returnData.push({ json: response });
|
||||
} else if (operation === 'getFileInfoByPath') {
|
||||
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}`, {}, credentials);
|
||||
returnData.push({ json: response });
|
||||
} else if (operation === 'uploadFileByPath') {
|
||||
const fileData = this.getNodeParameter('fileData', i) as string;
|
||||
const uploadPath = this.getNodeParameter('uploadPath', i) as string;
|
||||
|
||||
// Parse path to get directory and filename
|
||||
const pathComponents = parsePath(uploadPath);
|
||||
const fileName = pathComponents[pathComponents.length - 1];
|
||||
const parentPath = pathComponents.slice(0, -1).join('/') || 'root';
|
||||
const parentDirectoryId = await resolvePathToId.call(this, parentPath, driveId, credentials);
|
||||
|
||||
const formData: IDataObject = {
|
||||
file: fileData,
|
||||
filename: fileName,
|
||||
parent_id: parentDirectoryId === 'root' ? 'root' : parentDirectoryId,
|
||||
};
|
||||
|
||||
const response = await kdriveApiRequest.call(this, 'POST', `/3/drive/${driveId}/upload`, formData, credentials);
|
||||
returnData.push({ json: response });
|
||||
} else if (operation === 'downloadFileByPath') {
|
||||
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||
const response = await kdriveApiRequest.call(this, 'GET', `/2/drive/${driveId}/files/${fileId}/download`, {}, credentials, true);
|
||||
returnData.push({ json: {}, binary: { data: { mimeType: 'application/octet-stream', data: response } } });
|
||||
} else if (operation === 'deleteFileByPath') {
|
||||
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||
const response = await kdriveApiRequest.call(this, 'DELETE', `/2/drive/${driveId}/files/${fileId}`, {}, credentials);
|
||||
returnData.push({ json: response });
|
||||
} else if (operation === 'getFileVersionsByPath') {
|
||||
const filePath = this.getNodeParameter('filePath', i) as string;
|
||||
const fileId = await resolvePathToId.call(this, filePath, driveId, credentials);
|
||||
const response = await kdriveApiRequest.call(this, 'GET', `/3/drive/${driveId}/files/${fileId}/versions`, {}, credentials);
|
||||
returnData.push({ json: response });
|
||||
}
|
||||
} else if (resource === 'directory') {
|
||||
const driveId = credentials.driveId as string;
|
||||
|
||||
Reference in New Issue
Block a user