|
1 | 1 | # SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
2 | 2 | # SPDX-License-Identifier: AGPL-3.0-or-later |
| 3 | +import niquests |
3 | 4 | from langchain_core.tools import tool |
4 | 5 | from nc_py_api import AsyncNextcloudApp |
5 | | -import niquests |
| 6 | +from nc_py_api.files.files_async import AsyncFilesAPI, FsNode |
6 | 7 |
|
| 8 | +from ex_app.lib.all_tools.lib.decorator import dangerous_tool, safe_tool |
7 | 9 | from ex_app.lib.all_tools.lib.files import get_file_id_from_file_url |
8 | 10 |
|
9 | | -from ex_app.lib.all_tools.lib.decorator import safe_tool, dangerous_tool |
10 | | - |
11 | 11 |
|
12 | 12 | def _validate_path(path: str) -> str: |
13 | 13 | """Reject path traversal attempts in a user-supplied file path.""" |
@@ -61,11 +61,64 @@ async def get_file_content_by_file_link(file_url: str): |
61 | 61 |
|
62 | 62 | return response.text |
63 | 63 |
|
| 64 | + def __format_fs_node(fsnode: FsNode) -> dict: |
| 65 | + # todo: permissions info |
| 66 | + return { |
| 67 | + 'path': fsnode.user_path, |
| 68 | + 'file_id': fsnode.info.fileid, |
| 69 | + 'etag': fsnode.etag.replace('"', '').replace("'", ''), |
| 70 | + 'bytes': fsnode.info.size, |
| 71 | + 'creation_date': fsnode.info.creation_date.isoformat(), |
| 72 | + 'last_modified': fsnode.info.last_modified.isoformat(), |
| 73 | + 'mimetype': fsnode.info.mimetype, |
| 74 | + 'is_shared': fsnode.is_shared, |
| 75 | + 'is_favourite': fsnode.info.favorite, |
| 76 | + 'is_version': fsnode.info.is_version, |
| 77 | + 'trash_info': { |
| 78 | + 'in_trash': fsnode.info.in_trash, |
| 79 | + **({ |
| 80 | + 'trashbin_filename': fsnode.info.trashbin_filename, |
| 81 | + 'original_location': fsnode.info.trashbin_original_location, |
| 82 | + 'deletion_time': fsnode.info.trashbin_deletion_time, |
| 83 | + } if fsnode.info.in_trash else {}), |
| 84 | + }, |
| 85 | + 'lock_info': { |
| 86 | + 'is_locked': fsnode.lock_info.is_locked, |
| 87 | + **({ |
| 88 | + 'owner': fsnode.lock_info.owner, |
| 89 | + 'owner_display_name': fsnode.lock_info.owner_display_name, |
| 90 | + 'type': fsnode.lock_info.type.name, |
| 91 | + 'creation_time': fsnode.lock_info.lock_creation_time, |
| 92 | + 'ttl': fsnode.lock_info.lock_ttl, |
| 93 | + 'locked_by_app': fsnode.lock_info.owner_editor, |
| 94 | + } if fsnode.lock_info.is_locked else {}), |
| 95 | + }, |
| 96 | + } |
| 97 | + |
| 98 | + |
| 99 | + @tool |
| 100 | + @safe_tool |
| 101 | + async def get_file_tree(path: str = '/', include_metadata = False, depth: int = 1): |
| 102 | + """ |
| 103 | + Get the file tree of the user (lists the folders and files the user has in Nextcloud Files) |
| 104 | + :param path: the path to enumerate. It should be relative to the root directory like /Media and NOT /userid/files/Media |
| 105 | + :param include_metadata: include the etag, file/folder id, last modified times, etc. with the file/folder paths |
| 106 | + :param depth: how many directory levels should be included in output. Default = 1 (only specified directory). Max depth = 5. |
| 107 | + :return: |
| 108 | + """ |
| 109 | + |
| 110 | + files_handle = AsyncFilesAPI(nc._session) |
| 111 | + fsnode_list = await files_handle.listdir(path, min(5, depth)) |
| 112 | + if include_metadata: |
| 113 | + return [__format_fs_node(fsnode) for fsnode in fsnode_list] |
| 114 | + |
| 115 | + return [fsnode.user_path for fsnode in fsnode_list] |
| 116 | + |
64 | 117 | @tool |
65 | 118 | @safe_tool |
66 | 119 | async def get_folder_tree(depth: int): |
67 | 120 | """ |
68 | | - Get the folder tree of the user (lists the files the user has in Nextcloud Files) |
| 121 | + Get the folder tree of the user (lists only the folders the user has in Nextcloud Files) |
69 | 122 | :param depth: the depth of the returned folder tree |
70 | 123 | :return: |
71 | 124 | """ |
@@ -182,6 +235,7 @@ async def delete_file(path: str): |
182 | 235 | return [ |
183 | 236 | get_file_content, |
184 | 237 | get_file_content_by_file_link, |
| 238 | + get_file_tree, |
185 | 239 | get_folder_tree, |
186 | 240 | create_public_sharing_link, |
187 | 241 | upload_file, |
|
0 commit comments