0 Replies Latest reply on May 24, 2012 5:23 AM by areohbee

    Functions for multi-phase catalog access, with retries...

    areohbee Level 5

      See Catalog:action & Catalog:privateAction below.

       

      These functions support iterative catalog access, to divide catalog updates into chunks...

       

      Also, they support retries to deal with contention in case another plugin task is already accessing the catalog.

       

       

      -- private method:

      function Catalog:_isAccessContention( qual )

          local found = qual:find( "LrCatalog:with", 1, true ) or 0 -- return position or zero, instead of position or nil.

          if found == 1 then -- problem reported by with-catalog-do method.

              local found2 = qual:find( "already inside", 15, true )

              if found2 == nil then

                  found2 = qual:find( "was blocked", 15, true ) -- Lr4b

              end

              if found2 then

                  -- problem is due to catalog access contention.

                  Debug.logn( 'catalog contention:', str:to( qual ) )

                  return true

              else

                  return false

              end

          else

              return false

          end

      end

       

       

       

      -- private method:

      function Catalog:_action( catFunc, tmo, name, func, ... )

          local t = { ... }

          local tries = math.max( math.ceil( tmo * 2 ), 1 )

          local sts, msg

          local function _func( context )

              sts, msg = func( unpack( t ) )

          end

          local count = 10000000 -- ten million max, for sanity.

          repeat

              local s, other

              if name then

                  s, other = LrTasks.pcall( catFunc, catalog, name, _func )

              else

                  s, other = LrTasks.pcall( catFunc, catalog, _func )

              end

              if s then -- no error thrown

                  if sts then

                      return true -- all done.

                  elseif str:is( msg ) then

                      return false, "Unable to complete catalog update - " .. msg

                  else

                      -- continue

                      count = count - 1                       

                  end

              else

                  if self:_isAccessContention( other ) then

                      tries = tries - 1

                      if tries == 0 then

                          return nil, "Unable to access catalog for " .. str:to( tmo ) .. " seconds."

                      else

                          LrTasks.sleep( math.random( .1, 1 ) ) -- sleep for half second, plus or minus.

                      end

                  else

                      return nil, "Catalog access function error: " .. str:to( other )

                  end

              end

          until count == 0

          return nil, "Program failure"

      end

       

       

       

      --- Wrapper for named/undoable catalog:withWriteAccessDo method - divide to conquor func.

      --

      --  @param tmo (number) max seconds to get in.

      --  @param name (string) undo title.

      --  @param func (function) divided catalog writing function: returns sts, msg = true when done; false if to be continued; nil, error message if trouble.

      --  @param ... (any) passed to func - often a table containing photo start index.

      --

      --  @usage example:<br>

      --           local function catalogAction( t )<br>

      --               if t.i > #photos then<br>

      --                   return true -- done, no errors (although should pre-check for photos to process).<br>

      --               else<br>

      --                   local k = math.min( t.i + 1000, #photos )<br>

      --                   while ( t.i <= k ) do<br>

      --                       -- do something to photos[t.i]<br>

      --                       -- if trouble, return nil, msg.<br>

      --                       t.i = t.i + 1

      --                   end<br>

      --                   if t.i > #photos then<br>

      --                       return true -- done, no errors.<br>

      --                   else<br>

      --                       return false -- continue, no errors.<br>

      --                   end<br>

      --               end<br>

      --           end<br>

      --           local sts, msg = cat:action( 10, "Test", catalogAction, { i=1 } )<br>

      --           if sts then<br>

      --               -- log successful message.<br>

      --           else<br>

      --               -- print error message...<br>

      --           end<br>

      --

      function Catalog:action( tmo, name, func, ... )

          return self:_action( catalog.withWriteAccessDo, tmo, name, func, ... )

      end

       

       

       

      --- Wrapper for un-named catalog:withPrivateWriteAccessDo method - divide to conquor func.

      --

      --

      --  @param tmo (number) max seconds to get in.

      --  @param func (function) divided catalog writing function: returns sts, msg = true when done; false if to be continued; nil, error message if trouble.

      --  @param ... (any) passed to func.

      --

      function Catalog:privateAction( tmo, func, ... )

          return self:_action( catalog.withPrivateWriteAccessDo, tmo, nil, func, ... ) -- no name.

      end

       

       

      Note: These functions were changed a bit after further testing and integration - consider them for example purposes only. The released version of these functions will be available shortly as part of the Elare Plugin Framework:

       

      https://www.assembla.com/spaces/lrdevplugin/

       

      PS - After some mods, the final version allows me to write code like this which I find convenient (still not released yet at assembla.com):

       

      local coll

      local s, m = cat:update( 10, "Assure Folder Collection", function( context, phase )

          if phase == 1 then

              coll = catalog:createCollection( folder:getName(), parent, true )

              return false -- keep going.

          elseif phase == 2 then

              coll:removeAllPhotos()

              return false -- keep going.

          elseif phase == 3 then

              coll:addPhotos( folder:getPhotos() )

              return true -- done (same as returning nil).

          else

              app:error( "bad phase" )

          end

      end )

      if s then

          app:logVerbose( "Assured collection: ^1", coll:getName() )

          return coll

      else

          return nil, m

      end

       

      or

       

      local photos = catalog:getAllPhotos()

      local s, m = cat:update( 10, "Update Photos", function( context, phase )

           local i1 = (phase - 1) * 1000 + 1

           local i2 = math.min( phase * 1000, #photos )

           for i = i1, i2 do

              local photo = photos[i]

              -- do something with photo that writes catalog.

          end

          if i2 < #photos then

              return false -- keep going

          end

      end )

      if s then

          app:logVerbose( "Photos updated..." )

          return true

      else

          return false, m

      end