diff --git a/lib/vfs/vfscore/interface.v b/lib/vfs/vfscore/interface.v index c62a9471..54716890 100644 --- a/lib/vfs/vfscore/interface.v +++ b/lib/vfs/vfscore/interface.v @@ -23,6 +23,9 @@ pub mut: pub interface FSEntry { get_metadata() Metadata get_path() string + is_dir() bool + is_file() bool + is_symlink() bool } // VFSImplementation defines the interface that all vfscore implementations must follow @@ -47,6 +50,7 @@ mut: get(path string) !FSEntry rename(old_path string, new_path string) ! copy(src_path string, dst_path string) ! + move(src_path string, dst_path string) ! delete(path string) ! // Symlink operations diff --git a/lib/vfs/vfscore/local.v b/lib/vfs/vfscore/local.v index c0e13c45..8e14b62b 100644 --- a/lib/vfs/vfscore/local.v +++ b/lib/vfs/vfscore/local.v @@ -9,6 +9,21 @@ mut: metadata Metadata } +// is_dir returns true if the entry is a directory +pub fn (self &LocalFSEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &LocalFSEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &LocalFSEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} + fn (e LocalFSEntry) get_metadata() Metadata { return e.metadata } @@ -242,6 +257,20 @@ pub fn (myvfs LocalVFS) copy(src_path string, dst_path string) ! { os.cp(abs_src, abs_dst) or { return error('Failed to copy ${src_path} to ${dst_path}: ${err}') } } +pub fn (myvfs LocalVFS) move(src_path string, dst_path string) ! { + abs_src := myvfs.abs_path(src_path) + abs_dst := myvfs.abs_path(dst_path) + + if !os.exists(abs_src) { + return error('Source path does not exist: ${src_path}') + } + if os.exists(abs_dst) { + return error('Destination path already exists: ${dst_path}') + } + + os.mv(abs_src, abs_dst) or { return error('Failed to move ${src_path} to ${dst_path}: ${err}') } +} + // Generic delete operation that handles all types pub fn (myvfs LocalVFS) delete(path string) ! { abs_path := myvfs.abs_path(path) diff --git a/lib/vfs/vfsnested/vfsnested.v b/lib/vfs/vfsnested/vfsnested.v index ccba36b4..dae8b083 100644 --- a/lib/vfs/vfsnested/vfsnested.v +++ b/lib/vfs/vfsnested/vfsnested.v @@ -57,13 +57,13 @@ pub fn (mut self NestedVFS) root_get() !vfscore.FSEntry { } pub fn (mut self NestedVFS) delete(path string) ! { - // mut impl, rel_path := self.find_vfs(path)! - // return impl.file_read(rel_path) + mut impl, rel_path := self.find_vfs(path)! + return impl.delete(rel_path) } pub fn (mut self NestedVFS) link_delete(path string) ! { - // mut impl, rel_path := self.find_vfs(path)! - // return impl.file_read(rel_path) + mut impl, rel_path := self.find_vfs(path)! + return impl.link_delete(rel_path) } pub fn (mut self NestedVFS) file_create(path string) !vfscore.FSEntry { @@ -151,6 +151,22 @@ pub fn (mut self NestedVFS) copy(src_path string, dst_path string) ! { } // Copy across different VFS implementations + // TODO: Q: What if it's not file? What if it's a symlink or directory? + data := src_impl.file_read(src_rel_path)! + dst_impl.file_create(dst_rel_path)! + return dst_impl.file_write(dst_rel_path, data) +} + +pub fn (mut self NestedVFS) move(src_path string, dst_path string) ! { + mut src_impl, src_rel_path := self.find_vfs(src_path)! + mut dst_impl, dst_rel_path := self.find_vfs(dst_path)! + + if src_impl == dst_impl { + return src_impl.move(src_rel_path, dst_rel_path) + } + + // Move across different VFS implementations + // TODO: Q: What if it's not file? What if it's a symlink or directory? data := src_impl.file_read(src_rel_path)! dst_impl.file_create(dst_rel_path)! return dst_impl.file_write(dst_rel_path, data) @@ -185,6 +201,21 @@ fn (e &RootEntry) get_path() string { return '/' } +// is_dir returns true if the entry is a directory +pub fn (self &RootEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &RootEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &RootEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} + pub struct MountEntry { pub mut: metadata vfscore.Metadata @@ -198,3 +229,18 @@ fn (e &MountEntry) get_metadata() vfscore.Metadata { fn (e &MountEntry) get_path() string { return '/${e.metadata.name}' } + +// is_dir returns true if the entry is a directory +pub fn (self &MountEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &MountEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &MountEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} diff --git a/lib/vfs/vfsourdb/vfsourdb.v b/lib/vfs/vfsourdb/vfsourdb.v index a1c7d026..87ec3df6 100644 --- a/lib/vfs/vfsourdb/vfsourdb.v +++ b/lib/vfs/vfsourdb/vfsourdb.v @@ -130,6 +130,10 @@ pub fn (mut self OurDBVFS) copy(src_path string, dst_path string) ! { return error('Not implemented') } +pub fn (mut self OurDBVFS) move(src_path string, dst_path string) ! { + return error('Not implemented') +} + pub fn (mut self OurDBVFS) link_create(target_path string, link_path string) !vfscore.FSEntry { parent_path := os.dir(link_path) link_name := os.base(link_path) @@ -270,6 +274,21 @@ fn (e &DirectoryEntry) get_path() string { return e.path } +// is_dir returns true if the entry is a directory +pub fn (self &DirectoryEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &DirectoryEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &DirectoryEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} + struct FileEntry { metadata vfscore.Metadata path string @@ -283,6 +302,21 @@ fn (e &FileEntry) get_path() string { return e.path } +// is_dir returns true if the entry is a directory +pub fn (self &FileEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &FileEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &FileEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} + struct SymlinkEntry { metadata vfscore.Metadata path string @@ -296,3 +330,18 @@ fn (e &SymlinkEntry) get_metadata() vfscore.Metadata { fn (e &SymlinkEntry) get_path() string { return e.path } + +// is_dir returns true if the entry is a directory +pub fn (self &SymlinkEntry) is_dir() bool { + return self.metadata.file_type == .directory +} + +// is_file returns true if the entry is a file +pub fn (self &SymlinkEntry) is_file() bool { + return self.metadata.file_type == .file +} + +// is_symlink returns true if the entry is a symlink +pub fn (self &SymlinkEntry) is_symlink() bool { + return self.metadata.file_type == .symlink +} diff --git a/lib/vfs/webdav/app.v b/lib/vfs/webdav/app.v index 7a59e2a8..166a603a 100644 --- a/lib/vfs/webdav/app.v +++ b/lib/vfs/webdav/app.v @@ -1,7 +1,6 @@ module webdav import vweb -import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.vfs.vfscore @@ -11,10 +10,10 @@ struct App { user_db map[string]string @[required] // root_dir pathlib.Path @[vweb_global] pub mut: - // lock_manager LockManager - vfs vfscore.VFSImplementation - server_port int - middlewares map[string][]vweb.Middleware + lock_manager LockManager + vfs vfscore.VFSImplementation + server_port int + middlewares map[string][]vweb.Middleware } @[params] diff --git a/lib/vfs/webdav/methods.v b/lib/vfs/webdav/methods.v index 727ed99f..e8a76aa5 100644 --- a/lib/vfs/webdav/methods.v +++ b/lib/vfs/webdav/methods.v @@ -1,106 +1,135 @@ module webdav -// import vweb -// import os -// import freeflowuniverse.herolib.core.pathlib -// import encoding.xml -// import freeflowuniverse.herolib.ui.console -// import net.urllib +import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.vfs.ourdb_fs +import encoding.xml +import net.urllib +import os +import vweb -// // @['/:path...'; LOCK] -// // fn (mut app App) lock_handler(path string) vweb.Result { -// // // Not yet working -// // // TODO: Test with multiple clients -// // resource := app.req.url -// // owner := app.get_header('Owner') -// // if owner.len == 0 { -// // return app.bad_request('Owner header is required.') -// // } +@['/:path...'; options] +fn (mut app App) options(path string) vweb.Result { + app.set_status(200, 'OK') + app.add_header('DAV', '1,2') + app.add_header('Allow', 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') + app.add_header('MS-Author-Via', 'DAV') + app.add_header('Access-Control-Allow-Origin', '*') + app.add_header('Access-Control-Allow-Methods', 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') + app.add_header('Access-Control-Allow-Headers', 'Authorization, Content-Type') + app.send_response_to_client('text/plain', '') + return vweb.not_found() +} -// // depth := if app.get_header('Depth').len > 0 { app.get_header('Depth').int() } else { 0 } -// // timeout := if app.get_header('Timeout').len > 0 { app.get_header('Timeout').int() } else { 3600 } +@['/:path...'; LOCK] +fn (mut app App) lock_handler(path string) vweb.Result { + // Not yet working + // TODO: Test with multiple clients + resource := app.req.url + owner := app.get_header('Owner') + if owner.len == 0 { + app.set_status(400, 'Bad Request') + return app.text('Owner header is required.') + } -// // token := app.lock_manager.lock(resource, owner, depth, timeout) or { -// // app.set_status(423, 'Locked') -// // return app.text('Resource is already locked.') -// // } + depth := if app.get_header('Depth').len > 0 { app.get_header('Depth').int() } else { 0 } + timeout := if app.get_header('Timeout').len > 0 { app.get_header('Timeout').int() } else { 3600 } -// // app.set_status(200, 'OK') -// // app.add_header('Lock-Token', token) -// // return app.text('Lock granted with token: ${token}') -// // } + token := app.lock_manager.lock(resource, owner, depth, timeout) or { + app.set_status(423, 'Locked') + return app.text('Resource is already locked.') + } -// // @['/:path...'; UNLOCK] -// // fn (mut app App) unlock_handler(path string) vweb.Result { -// // // Not yet working -// // // TODO: Test with multiple clients -// // resource := app.req.url -// // token := app.get_header('Lock-Token') -// // if token.len == 0 { -// // console.print_stderr('Unlock failed: `Lock-Token` header required.') -// // return app.bad_request('Unlock failed: `Lock-Token` header required.') -// // } + app.set_status(200, 'OK') + app.add_header('Lock-Token', token) + return app.text('Lock granted with token: ${token}') +} -// // if app.lock_manager.unlock_with_token(resource, token) { -// // app.set_status(204, 'No Content') -// // return app.text('Lock successfully released') -// // } +@['/:path...'; UNLOCK] +fn (mut app App) unlock_handler(path string) vweb.Result { + // Not yet working + // TODO: Test with multiple clients + resource := app.req.url + token := app.get_header('Lock-Token') + if token.len == 0 { + console.print_stderr('Unlock failed: `Lock-Token` header required.') + app.set_status(400, 'Bad Request') + return app.text('Lock failed: `Owner` header missing.') + } -// // console.print_stderr('Resource is not locked or token mismatch.') -// // app.set_status(409, 'Conflict') -// // return app.text('Resource is not locked or token mismatch') -// // } + if app.lock_manager.unlock_with_token(resource, token) { + app.set_status(204, 'No Content') + return app.text('Lock successfully released') + } -// @['/:path...'; get] -// fn (mut app App) get_file(path string) vweb.Result { -// mut file_path := pathlib.get_file(path: app.root_dir.path + path) or { return app.not_found() } -// if !file_path.exists() { -// return app.not_found() -// } + console.print_stderr('Resource is not locked or token mismatch.') + app.set_status(409, 'Conflict') + return app.text('Resource is not locked or token mismatch') +} -// file_data := file_path.read() or { -// console.print_stderr('failed to read file ${file_path.path}: ${err}') -// return app.server_error() -// } +@['/:path...'; get] +fn (mut app App) get_file(path string) vweb.Result { + if !app.vfs.exists(path) { + return app.not_found() + } -// ext := os.file_ext(file_path.path) -// content_type := if v := vweb.mime_types[ext] { -// v -// } else { -// 'text/plain' -// } + fs_entry := app.vfs.get(path) or { + console.print_stderr('failed to get FS Entry ${path}: ${err}') + return app.server_error() + } -// app.set_status(200, 'Ok') -// app.send_response_to_client(content_type, file_data) + file_data := app.vfs.file_read(fs_entry.get_path()) or { return app.server_error() } -// return vweb.not_found() // this is for returning a dummy result -// } + ext := fs_entry.get_metadata().name.all_after_last('.') + content_type := if v := vweb.mime_types[ext] { + v + } else { + 'text/plain' + } -// @['/:path...'; delete] -// fn (mut app App) delete(path string) vweb.Result { -// mut p := pathlib.get(app.root_dir.path + path) -// if !p.exists() { -// return app.not_found() -// } + app.set_status(200, 'Ok') + app.send_response_to_client(content_type, file_data.str()) + return vweb.not_found() // this is for returning a dummy result +} -// if p.is_dir() { -// console.print_debug('deleting directory: ${p.path}') -// os.rmdir_all(p.path) or { return app.server_error() } -// } +@['/:path...'; delete] +fn (mut app App) delete(path string) vweb.Result { + if !app.vfs.exists(path) { + return app.not_found() + } -// if p.is_file() { -// console.print_debug('deleting file: ${p.path}') -// os.rm(p.path) or { return app.server_error() } -// } + fs_entry := app.vfs.get(path) or { + console.print_stderr('failed to get FS Entry ${path}: ${err}') + return app.server_error() + } -// console.print_debug('entry: ${p.path} is deleted') -// app.set_status(204, 'No Content') + if fs_entry.is_dir() { + console.print_debug('deleting directory: ${path}') + app.vfs.dir_delete(path) or { return app.server_error() } + } -// return app.text('entry ${p.path} is deleted') -// } + if fs_entry.is_file() { + console.print_debug('deleting file: ${path}') + app.vfs.file_delete(path) or { return app.server_error() } + } + + if fs_entry.is_symlink() { + console.print_debug('deleting symlink: ${path}') + app.vfs.link_delete(path) or { return app.server_error() } + } + + console.print_debug('entry: ${path} is deleted') + app.set_status(204, 'No Content') + return app.text('entry ${path} is deleted') +} // @['/:path...'; put] // fn (mut app App) create_or_update(path string) vweb.Result { +// fs_entry := app.vfs.get(path) or { +// console.print_stderr('failed to get FS Entry ${path}: ${err}') +// return app.server_error() +// } + // mut p := pathlib.get(app.root_dir.path + path) // if p.is_dir() { @@ -124,136 +153,124 @@ module webdav // return app.text('HTTP 200: Successfully saved file: ${p.path}') // } -// @['/:path...'; copy] -// fn (mut app App) copy(path string) vweb.Result { -// mut p := pathlib.get(app.root_dir.path + path) -// if !p.exists() { -// return app.not_found() -// } +@['/:path...'; copy] +fn (mut app App) copy(path string) vweb.Result { + if !app.vfs.exists(path) { + return app.not_found() + } -// destination := app.get_header('Destination') -// destination_url := urllib.parse(destination) or { -// return app.bad_request('Invalid Destination ${destination}: ${err}') -// } -// destination_path_str := destination_url.path + destination := app.get_header('Destination') + destination_url := urllib.parse(destination) or { + return app.bad_request('Invalid Destination ${destination}: ${err}') + } + destination_path_str := destination_url.path -// mut destination_path := pathlib.get(app.root_dir.path + destination_path_str) -// if destination_path.exists() { -// return app.bad_request('Destination ${destination_path.path} already exists') -// } + app.vfs.get(path) or { + console.print_stderr('failed to get FS Entry ${path}: ${err}') + return app.server_error() + } -// os.cp_all(p.path, destination_path.path, false) or { -// console.print_stderr('failed to copy: ${err}') -// return app.server_error() -// } + app.vfs.copy(path, destination_path_str) or { + console.print_stderr('failed to copy: ${err}') + return app.server_error() + } -// app.set_status(200, 'Successfully copied entry: ${p.path}') -// return app.text('HTTP 200: Successfully copied entry: ${p.path}') + app.set_status(200, 'Successfully copied entry: ${path}') + return app.text('HTTP 200: Successfully copied entry: ${path}') +} + +@['/:path...'; move] +fn (mut app App) move(path string) vweb.Result { + if !app.vfs.exists(path) { + return app.not_found() + } + + destination := app.get_header('Destination') + destination_url := urllib.parse(destination) or { + return app.bad_request('Invalid Destination ${destination}: ${err}') + } + destination_path_str := destination_url.path + + app.vfs.move(path, destination_path_str) or { + console.print_stderr('failed to move: ${err}') + return app.server_error() + } + + app.set_status(200, 'Successfully moved entry: ${path}') + return app.text('HTTP 200: Successfully moved entry: ${path}') +} + +@['/:path...'; mkcol] +fn (mut app App) mkcol(path string) vweb.Result { + if app.vfs.exists(path) { + return app.bad_request('Another collection exists at ${path}') + } + + app.vfs.dir_create(path) or { + console.print_stderr('failed to create directory ${path}: ${err}') + return app.server_error() + } + + app.set_status(201, 'Created') + return app.text('HTTP 201: Created') +} + +@['/:path...'; propfind] +fn (mut app App) propfind(path string) vweb.Result { + if !app.vfs.exists(path) { + return app.not_found() + } + + depth := app.get_header('Depth').int() + + responses := app.get_responses(path, depth) or { + console.print_stderr('failed to get responses: ${err}') + return app.server_error() + } + + doc := xml.XMLDocument{ + root: xml.XMLNode{ + name: 'D:multistatus' + children: responses + attributes: { + 'xmlns:D': 'DAV:' + } + } + } + + res := '${doc.pretty_str('').split('\n')[1..].join('')}' + // println('res: ${res}') + + app.set_status(207, 'Multi-Status') + app.send_response_to_client('application/xml', res) + return vweb.not_found() +} + +fn (mut app App) generate_resource_response(path string) string { + mut response := '' + response += app.generate_element('response', 2) + response += app.generate_element('href', 4) + response += app.generate_element('/href', 4) + response += app.generate_element('/response', 2) + + return response +} + +fn (mut app App) generate_element(element string, space_cnt int) string { + mut spaces := '' + for i := 0; i < space_cnt; i++ { + spaces += ' ' + } + + return '${spaces}<${element}>\n' +} + +// TODO: implement +// @['/'; proppatch] +// fn (mut app App) prop_patch() vweb.Result { // } -// @['/:path...'; move] -// fn (mut app App) move(path string) vweb.Result { -// mut p := pathlib.get(app.root_dir.path + path) -// if !p.exists() { -// return app.not_found() -// } - -// destination := app.get_header('Destination') -// destination_url := urllib.parse(destination) or { -// return app.bad_request('Invalid Destination ${destination}: ${err}') -// } -// destination_path_str := destination_url.path - -// mut destination_path := pathlib.get(app.root_dir.path + destination_path_str) -// if destination_path.exists() { -// return app.bad_request('Destination ${destination_path.path} already exists') -// } - -// os.mv(p.path, destination_path.path) or { -// console.print_stderr('failed to copy: ${err}') -// return app.server_error() -// } - -// app.set_status(200, 'Successfully moved entry: ${p.path}') -// return app.text('HTTP 200: Successfully moved entry: ${p.path}') +// TODO: implement, now it's used with PUT +// @['/'; post] +// fn (mut app App) post() vweb.Result { // } - -// @['/:path...'; mkcol] -// fn (mut app App) mkcol(path string) vweb.Result { -// mut p := pathlib.get(app.root_dir.path + path) -// if p.exists() { -// return app.bad_request('Another collection exists at ${p.path}') -// } - -// p = pathlib.get_dir(path: p.path, create: true) or { -// console.print_stderr('failed to create directory ${p.path}: ${err}') -// return app.server_error() -// } - -// app.set_status(201, 'Created') -// return app.text('HTTP 201: Created') -// } - -// @['/:path...'; options] -// fn (mut app App) options(path string) vweb.Result { -// app.set_status(200, 'OK') -// app.add_header('DAV', '1,2') -// app.add_header('Allow', 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') -// app.add_header('MS-Author-Via', 'DAV') -// app.add_header('Access-Control-Allow-Origin', '*') -// app.add_header('Access-Control-Allow-Methods', 'OPTIONS, PROPFIND, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE') -// app.add_header('Access-Control-Allow-Headers', 'Authorization, Content-Type') -// app.send_response_to_client('text/plain', '') -// return vweb.not_found() -// } - -// @['/:path...'; propfind] -// fn (mut app App) propfind(path string) vweb.Result { -// mut p := pathlib.get(app.root_dir.path + path) -// if !p.exists() { -// return app.not_found() -// } - -// depth := app.get_header('Depth').int() - -// responses := app.get_responses(p.path, depth) or { -// console.print_stderr('failed to get responses: ${err}') -// return app.server_error() -// } - -// doc := xml.XMLDocument{ -// root: xml.XMLNode{ -// name: 'D:multistatus' -// children: responses -// attributes: { -// 'xmlns:D': 'DAV:' -// } -// } -// } - -// res := '${doc.pretty_str('').split('\n')[1..].join('')}' -// // println('res: ${res}') - -// app.set_status(207, 'Multi-Status') -// app.send_response_to_client('application/xml', res) -// return vweb.not_found() -// } - -// fn (mut app App) generate_element(element string, space_cnt int) string { -// mut spaces := '' -// for i := 0; i < space_cnt; i++ { -// spaces += ' ' -// } - -// return '${spaces}<${element}>\n' -// } - -// // TODO: implement -// // @['/'; proppatch] -// // fn (mut app App) prop_patch() vweb.Result { -// // } - -// // TODO: implement, now it's used with PUT -// // @['/'; post] -// // fn (mut app App) post() vweb.Result { -// // } diff --git a/lib/vfs/webdav/methods_vfs.v b/lib/vfs/webdav/methods_vfs.v deleted file mode 100644 index b4c59463..00000000 --- a/lib/vfs/webdav/methods_vfs.v +++ /dev/null @@ -1,33 +0,0 @@ -module webdav - -import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.ui.console -import encoding.xml -import net.urllib -import os -import vweb - -@['/:path...'; get] -fn (mut app App) get_file(path string) vweb.Result { - if !app.vfs.exists(path) { - return app.not_found() - } - - fs_entry := app.vfs.get(path) or { - console.print_stderr('failed to get FS Entry ${path}: ${err}') - return app.server_error() - } - - file_data := app.vfs.file_read(fs_entry.get_path()) or { return app.server_error() } - - ext := fs_entry.get_metadata().name.all_after_last('.') - content_type := if v := vweb.mime_types[ext] { - v - } else { - 'text/plain' - } - - app.set_status(200, 'Ok') - app.send_response_to_client(content_type, file_data.str()) - return vweb.not_found() // this is for returning a dummy result -}