Lightroom 5 SDK: catalog:getFolderByPath does not work when folder is on a network drive.

  • 3
  • Problem
  • Updated 4 years ago
Is there any way around this? - thanks in advance..

For example:
local photo = catalog:getTargetPhoto()
local file = photo:getRawMetadata( 'path' )
local parent = LrPathUtils.parent( file )
local folder = catalog:getFolderByPath( parent )
assert( folder ~= nil, "problem.." )

no problem asserted if photo on local drive
problem asserted if photo on network drive

note: when LrFolder found by starting with catalog:getFolders(), and recursing.., the path returned via: lrFolder:getPath() is the same as 'parent' computed above - something like:

\\MYDRV\MyFolder

So the folder path is correctly recorded in the catalog.. - just catalog:getFolderByPath is broken.

Rob
Photo of Rob Cole

Rob Cole

  • 4831 Posts
  • 382 Reply Likes

Posted 4 years ago

  • 3
Photo of John R. Ellis

John R. Ellis, Champion

  • 3690 Posts
  • 963 Reply Likes
Interestingly, I can find a network folder if is named with a drive letter, e.g. Y:\test, but not if it is named with a UNC path, e.g. \\ellisthinkpad2\test.

Also, catalog:findPhotoByPath() works just fine with UNC paths.
Photo of Rob Cole

Rob Cole

  • 4831 Posts
  • 382 Reply Likes
Thanks John - 'tis a viable work-around in some cases (mapping network drives to a letter).
Photo of John R. Ellis

John R. Ellis, Champion

  • 3690 Posts
  • 963 Reply Likes
Have you considered just recursing the catalog:getFolders() tree looking for the desired folder?
Photo of Rob Cole

Rob Cole

  • 4831 Posts
  • 382 Reply Likes
Yes - in fact, that may be the long term solution (until an Adobe fix), since it does not depend on user action - thanks again John.
Photo of Rob Cole

Rob Cole

  • 4831 Posts
  • 382 Reply Likes
Initial prototype for function which works for unmapped network drives too:

--- Initialize folder cache to assure fresh results via get-folder-by-path.
--
function Catalog:initFolderCache()
self.folderCache = {}
end

--- Equivalent to Lr's native method, except works for unmapped network drives too (Lr's doesn't, @Lr5.4 anyway).
--
-- @usage it is recommended but not required to initialize folder cache before calling the first time.
--
-- @param folderPath (string, required) path of folder for which corresponding lr-folder object is desired.
--
-- @return folder (lr-folder object) hopefully never nil (assuming valid folder-path), but best to check in calling context.
--
function Catalog:getFolderByPath( folderPath )
local folder = catalog:getFolderByPath( folderPath )
if folder then -- mapped drive..
return folder
end
if not self.folderCache then
Debug.pause( "Consider initializing folder cache before first call." )
self.folderCache = {} -- lookup
else
folder = self.folderCache[folderPath]
end
if not folder then
local y = 0
local function find( lrFolder )
local path = lrFolder:getPath()
self.folderCache[path] = lrFolder
if path == folderPath then -- case sensitive is OK, I hope ###1.
folder = lrFolder
return true
end
y = app:yield( y ) -- yield every 20 times, so Lr stays responsive.
for j, v in ipairs( lrFolder:getChildren() ) do
if find( v ) then return true end
end
return false
end
local allFolders = catalog:getFolders()
for i, f in ipairs( allFolders ) do
if find( f ) then break end
end
Debug.pauseIf( folder==nil, "no folder for path: "..folderPath )
end
return folder
end

John - this is an example of a case that I wonder if weak tables could help, since folders could change around asynchronously - ya know, maybe have weak references in folder-cache that would last for a "reasonable" amount of time, then go "poof" and disappear somehow (when garbage collected).. - still workin' on this idea.
Photo of John R. Ellis

John R. Ellis, Champion

  • 3690 Posts
  • 963 Reply Likes
Here's a slight tweak to the find() subfunction that should make it significantly faster on average -- it stops recursing when it gets to a folder, e.g. "/a/c", that couldn't possibly be a parent of the folder path you're looking for, e.g. "/a/b/x":

local function find( lrFolder )
local path = lrFolder:getPath()
self.folderCache[path] = lrFolder
if path == folderPath then -- case sensitive is OK, I hope ###1.
folder = lrFolder
return true
elseif path ~= folderPath:sub( 1, #path ) then
return false
end
y = app:yield( y ) -- yield every 20 times, so Lr stays responsive.
for j, v in ipairs( lrFolder:getChildren() ) do
if find( v ) then return true end
end
return false
end


My guess is that this will be fast enough for most uses that there would be no need to cache the result (and thus no need for thinking about caching policies and weak references). But just a guess...
Photo of Rob Cole

Rob Cole

  • 4831 Posts
  • 382 Reply Likes
Here is the "final" version, which uses a non-cached method with 'find' function ala John Ellis, by default, but supports cached mode if calling context is willing to take the care needed to manage cache initialization.

--- Initialize folder cache to assure fresh results via (and set mode of) get-folder-by-path.
--
-- @param unInit (boolean, default = false) if false, get-folder-by-path will use cached mode (faster, but results will be stale unless care is taken to init before each "run"..).
--
if true, get-folder-by-path will use non-cached mode (slower, but results are always fresh).
--
function Catalog:initFolderCache( unInit )
if unInit then
self.folderCache = nil
else
self.folderCache = {}
end
end


--- Equivalent to Lr's native method, except works for unmapped network drives too (Lr's doesn't, @Lr5.4 anyway).
--
-- @usage it is recommended but not required to initialize folder cache before calling the first time.
--
-- @param folderPath (string, required) path of folder for which corresponding lr-folder object is desired.
--
-- @return folder (lr-folder object) hopefully never nil (assuming valid folder-path), but best to check in calling context.
--
function Catalog:getFolderByPath( folderPath )
local folder = catalog:getFolderByPath( folderPath ) -- ### set to nil to test "finding" below.
if folder then -- mapped drive..
return folder
end
local find
if not self.folderCache then
--Debug.pause( "Consider initializing folder cache before first call." )
find = function( lrFolder )
local path = lrFolder:getPath()
if path == folderPath then -- ###2: case sensitive.
folder = lrFolder
return true
elseif path ~= folderPath:sub( 1, #path ) then -- i.e. if not str:isBeginningWith( folderPath, path ) then
return false
end
-- assume find will be fast enough that yielding is not required.
for j, v in ipairs( lrFolder:getChildren() ) do
if find( v ) then return true end
end
return false
end
else
folder = self.folderCache[folderPath]
if folder then
return folder
end
local y = 0
find = function( lrFolder )
local path = lrFolder:getPath()
self.folderCache[path] = lrFolder
if path == folderPath then -- ###2: ditto.
folder = lrFolder
return true
end
y = app:yield( y ) -- yield every 20 times, so Lr stays responsive.
for j, v in ipairs( lrFolder:getChildren() ) do
if find( v ) then return true end
end
return false
end
end
local allFolders = catalog:getFolders()
for i, f in ipairs( allFolders ) do
if find( f ) then break end
end
Debug.pauseIf( folder==nil, "no folder for path: "..folderPath )
return folder
end

Timing results:
* without caching, to find folders of 11884 photos: 22.094 seconds.
* with freshly initialized cache: 1.111 seconds.
* again without re-initializing cache: 0.746 seconds.

My intention is to convert all plugins immediately to use non-cached mode, so they work with network drives too (reminder: ~NO penalty if photos *not* on network drive), then use cached mode more strategically..

Rob
Photo of John R. Ellis

John R. Ellis, Champion

  • 3690 Posts
  • 963 Reply Likes
So it takes about 2 msecs / folder without caching, and 0.1 msecs with caching. Another reminder that many APIs in the SDK are not all that efficient.