--[[----------------------------------------------------------------------------

WFCatImportManifestAlbumsProcessor.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2017 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--
--[[
	Strategy to avoid duplicates due to non-atomic update
	of docstore+manifeststatus db
		
		1. Before calling the docstore add api, we update the
			manifest status db, with pending status along with
			a generated uuid.
			
		2. If docstore got updated, but the app was killed
			before the status update, then the affected albums
			will have pending status in manifest-status db.
			
		3. On resume, we will go through the list of albums
			in pending status and will query the docstore
			to verify if we have updated the albums or not.
			Note that in the case of synced albums, it will
			not be sufficient to check the existence of the
			document. Hence we add the manifest source id as
			a _localOnly value and read the same to confirm.
			
		4. We will update all pending albums' status, to success
			if the manifest-source-id is found in the document.
			
		5. We are not removing the verification-failed records,
			since they will be retried and will be overwritten
			with the proper status after processing.
			
]]
	-- Rx
local Rx = require 'Rx'
local Observable = Rx.Observable
local ReplaySubject = Rx.ReplaySubject

	-- WF.oz_client.managed_catalog
local WFCatAlbums = require 'WFCatAlbums'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'

	-- WF.oz_client.managed_catalog.import
local WFCatImportManifestConstants = require 'WFCatImportManifestConstants'
local WFCatImportManifestProcessorUtils = require 'WFCatImportManifestProcessorUtils'
local WFCatImportManifestProcessorBase = require 'WFCatImportManifestProcessorBase'

	-- wf.core
local KSTableUtils = require 'KSTableUtils'
local WFConfiguration = require 'WFConfiguration'

local derefOpt = KSTableUtils.derefOpt
--------------------------------------------------------------------------------

local fakeRevisionExistsErrorUUID = WFConfiguration.Manifest.fakeRevisionExistsErrorUUIDForAlbum

local kAlbumLevels = 5 + 1
	-- Number of album-sets + 1
	
local kCollectionSetSubType = 'collection_set'

local kAlbumFlatteningSeparator = '_'

local kNilKey = "__nil__"

local DONT_MOVE_SYNCED_ALBUMS = WFConfiguration.Migrate.DontMoveSyncedAlbums

local DONT_CREATE_UNDER_ROOT_ALBUM_SET = WFConfiguration.Migrate.DontCreateUnderRootAlbumSet or false

--------------------------------------------------------------------------------

local WFCatImportManifestAlbumsProcessor = {

	WFCatImportManifestProcessorBase,
}

--------------------------------------------------------------------------------

function WFCatImportManifestAlbumsProcessor:__init()
	self._stageName = WFCatImportManifestConstants.stageNames.albums

	self._messageCodeCompleted = WFCatImportManifestConstants.messages.albumProcessingComplete.code
	self._messageCodeErrorCount = WFCatImportManifestConstants.messages.albumProcessingErrorCount.code
	self._messageCodeGenericError = WFCatImportManifestConstants.messages.albumProcessingError.code
end

--------------------------------------------------------------------------------

function WFCatImportManifestAlbumsProcessor:_updateStatus( manifestStatusDatabase, albums, status )
	
	local statuses = {}
	for i, album in ipairs( albums ) do
		statuses[ #statuses + 1 ] = {
			local_id = album.local_id,
			status = status,
		}
	end
	
	return manifestStatusDatabase:writeData( 'updateAlbumStatus', statuses )
		:ignoreElements()
	
end

--------------------------------------------------------------------------------

function WFCatImportManifestAlbumsProcessor:_addStatus( manifestStatusDatabase, local_id, oz_id, oz_name, status )
	
	local statusData = { {
		local_id = local_id,
		oz_id = oz_id,
		oz_name = oz_name,
		status = status,
	} }
	
	return manifestStatusDatabase:writeData( 'addOrReplaceAlbumStatus', statusData )
		:ignoreElements()
	
end

--------------------------------------------------------------------------------

function WFCatImportManifestAlbumsProcessor:setup( catalog, manifestDatabase, manifestStatusDatabase, idCache, messageHandler )
	
	return self:preProcessPending( catalog, manifestDatabase, manifestStatusDatabase, idCache, messageHandler )
		:defaultIfEmpty()
		:flatMap( function()
			
			return self:WFCatImportManifestProcessorBase_setup( catalog, 
				manifestStatusDatabase, idCache, messageHandler, 'selectAlbumsStatus', 
				WFCatImportManifestConstants.idCacheSets.albums )
		end )
	
end

--------------------------------------------------------------------------------

--[[
	If there are pending records, we go through them and
	verify if they already exist. If they do, then we
	update the status of the same, so that it will get
	considered in the cache-setup phase.
]]
function WFCatImportManifestAlbumsProcessor:preProcessPending( catalog, manifestDatabase, manifestStatusDatabase, idCache, messageHandler )
	
	return manifestStatusDatabase:readData( "selectAlbumStatusWithStatus" , { status = WFCatImportManifestConstants.status.pending } )
	
		:flatMap( function( pendingEntries )
		
			local docIds = {}
			local statusForDocId = {}
			
			for i, entry in ipairs( pendingEntries ) do
					
				docIds[ i ] = entry.oz_id
				statusForDocId[ entry.oz_id ] = entry
					
			end
		
			return WFCatImportManifestProcessorUtils.maybeReadManifestSourceId( manifestDatabase )
			
			:flatMap( function( sourceId )
				
				return catalog:docStore():bulkDocValue( docIds )
			
					:flatMap( function( docValues )
			
						local statusListToUpdate = {}
						
						for i, docValue in ipairs( docValues ) do
						
							if not docValue.empty then
								-- Document exists. 
								-- Check for import_source to reconfirm
								-- we have updated this doc.( synced albums
								-- can already be present. )
								
								local isManifestSourceIdPresent = not not derefOpt( docValue, 
															"_localOnly",
															"manifest",
															sourceId )
								
								if isManifestSourceIdPresent then
								
									local statusInfo = statusForDocId[ docValue._id ]
									statusListToUpdate[ #statusListToUpdate + 1 ] = {
										local_id = statusInfo.local_id,
									}
								
								end

							end
						end
						
						if #statusListToUpdate > 0 then
							return self:_updateStatus( manifestStatusDatabase, statusListToUpdate, WFCatImportManifestConstants.status.success )
						else
							return Observable.empty()
						end
						
					end )
			end )
			
			
		end )

end

--------------------------------------------------------------------------------

local function _resolveNameCollisions( albumInfo, takenNames, existingAlbumNamesForId, albumOzIdToDocstoreLocalId, idCache )

	albumInfo.name = WFCatImportManifestProcessorUtils.findUniqueName( albumInfo.name, takenNames )
	takenNames[ albumInfo.name ] = true
	
	-- Recurse through the tree since flattening may have resulted in some collisions
	if albumInfo.subType == kCollectionSetSubType then
		local childrenNames = {}
		
		local albumOzId = idCache:getOzIdForLocalId( WFCatImportManifestConstants.idCacheSets.albums, albumInfo.localId )
		local albumDocstoreLocalId = albumOzId and albumOzIdToDocstoreLocalId[ albumOzId ]

		-- Add current children if the album exists already
		if albumOzId then
			local existingChildren = existingAlbumNamesForId[ albumDocstoreLocalId ] or {}
			for childName in pairs( existingChildren ) do
				childrenNames[ childName ] = true
			end
		end
		
		for _, childInfo in ipairs( albumInfo.children ) do
			_resolveNameCollisions( childInfo, childrenNames, existingAlbumNamesForId, albumOzIdToDocstoreLocalId, idCache )
		end
	end
end

--------------------------------------------------------------------------------

local function _flattenImpl( album, parent, depth, namePrefix )
	-- `album` is at level `depth`

	if album.subType == kCollectionSetSubType then
		
		for i = #album.children, 1, -1 do
			local child = album.children[ i ]
			
			if depth < kAlbumLevels then
				_flattenImpl( child, album, depth + 1 )
			else
				table.remove( album.children, i )
				table.insert( parent.children, child )
				child.parent = parent.local_id
				
				local prefix = ( namePrefix or '' ) .. album.name .. kAlbumFlatteningSeparator
				
				_flattenImpl( child, parent, depth + 1, prefix )
				
				child.name = prefix .. child.name
			end
		end
	
		-- Mark `album` as an empty collection set (can be ignored)
		if depth >= kAlbumLevels then
			album.flattenedOut = true
		end
	
	end
	
end

--------------------------------------------------------------------------------

local function _flatten( firstLevelAlbumsWithHierarchy )

	local firstAlbumChildLevel = DONT_CREATE_UNDER_ROOT_ALBUM_SET and 2 or 3
	
	for i, albumInfo in ipairs( firstLevelAlbumsWithHierarchy ) do
		for _, child in ipairs( albumInfo.children or {} ) do
			_flattenImpl( child, albumInfo, firstAlbumChildLevel )
		end
	end
		
	return firstLevelAlbumsWithHierarchy
	
end

--------------------------------------------------------------------------------

local function _setupHierarchy( albums )
	
	-- Make a copy of the albums list we can play with
	local possibleParents = {}
	
	for _, albumInfo in ipairs( albums ) do
		if albumInfo.subType == kCollectionSetSubType then
			albumInfo.children = {}
			possibleParents[ albumInfo.local_id ] = albumInfo
		end
	end
	
	local firstLevelAlbums = {}
		
	-- Move the root-level albums to the top of the rootAlbum
	-- And make a map of the albums index-able by the local_id
	for _, albumInfo in ipairs( albums ) do
		
		if albumInfo.parent then
			-- Add child to parent
			local parentAlbum = possibleParents[ albumInfo.parent ]
			assert( parentAlbum )
			
			parentAlbum.children[ #parentAlbum.children + 1 ] = albumInfo
		else
			-- Add child to the root album
			albumInfo.parent = not DONT_CREATE_UNDER_ROOT_ALBUM_SET and WFCatImportManifestConstants.kRootAlbumLocalId
			firstLevelAlbums[ #firstLevelAlbums + 1 ] = albumInfo
		end
	end
	
	return firstLevelAlbums
end

--------------------------------------------------------------------------------

local function createAlbumOrFolderObservable( catalog, subType, name, parentId, hidden, importSource, optDocId, manifestSourceId )

	if subType == kCollectionSetSubType then
		return WFCatAlbums.createFolder( catalog, name, parentId, importSource, optDocId, manifestSourceId )
	else
		return WFCatAlbums.createAlbum( catalog, name, parentId, hidden, importSource, optDocId, manifestSourceId )
	end

end

--------------------------------------------------------------------------------

local function maybeMoveAlbumObservable( catalog, subType, name, parentId, hidden, ozId, docValue, importSource, manifestSourceId, self, messageHandler )
	-- If the album already exists, then move it to the right place (under the new hierarchy)
	
	local docStore = catalog:docStore()
	
	local docStoreLocalId
	local retSubject = ReplaySubject( 1 )
	
	local docValueObservable = 	Observable.just()
		:flatMapLatest( function()
			docStoreLocalId = docValue._localId
			
			if not docValue._empty then
				return Observable.just( docValue )
			else
				-- The album doesn't really exist - maybe not yet synced down. Create it.
				return createAlbumOrFolderObservable( catalog, subType, name, parentId, hidden, importSource, ozId, manifestSourceId )
					:tap( function()
						self:_sendMessage( messageHandler, WFCatImportManifestConstants.messages.createdMissingSyncedAlbumWarning.code, { { name } } )
					end )
					:map( function( localDocStoreId, ozId, docValue )
						docStoreLocalId = docValue._localId
						return docValue
					end )
			end
			
		end )
		:share()
		
	local moveAlbumObservable = Observable.combineLatest( docValueObservable, docStore:docValue( parentId ) )
		:flatMapLatest( function( docValue, parentDocValue )
			if ( not docValue.parent ) or ( docValue.parent and docValue.parent.id ~= parentId ) then
				return WFCatAlbums.reparentAlbumOrFolder( catalog, docValue._id, parentId )
					:tapOnCompleted( function()
						self:_sendMessage( messageHandler, WFCatImportManifestConstants.messages.albumReparentedWarning.code, { { docValue.name, parentDocValue.name } } )
					end )
			else
				return Observable.empty()
			end
		end )
	
	local renameAlbumObservable = docValueObservable
		:flatMapLatest( function( docValue )
			if docValue.name ~= name then
				return WFCatAlbums.renameAlbumOrFolder( catalog, docValue._id, name )
					:tapOnCompleted( function()
						self:_sendMessage( messageHandler, WFCatImportManifestConstants.messages.albumRenamedWarning.code, { { docValue.name, name } } )
					end )
			else
				return Observable.empty()
			end
		end )
	
	Observable.combineLatest( moveAlbumObservable, renameAlbumObservable )
		:subscribe(
			-- onNext
			nil,
			
			-- onError
			function( ... )
				retSubject:onError( ... )
			end,
			
			-- onCompleted
			function()
				retSubject:onNext( docStoreLocalId, ozId )
				retSubject:onCompleted()
			end
		)
		
	return retSubject

end

--------------------------------------------------------------------------------

local function serializeHierarchy( albums )

	local serializedAlbumInfo = {}
	local pendingList = {}
	
	for i, album in ipairs( albums or {} ) do
	
		if not album.flattenedOut then
			-- Ignore albums whose children have been moved out
		
			serializedAlbumInfo[ #serializedAlbumInfo + 1 ] = album

			if album.subType == kCollectionSetSubType and album.children then
				pendingList[ #pendingList + 1 ] = album.children
			end
		end
	end
	
	for _, childrenList in ipairs( pendingList ) do
		local serializedChildren = serializeHierarchy( childrenList )
		KSTableUtils.appendArray( serializedAlbumInfo, serializedChildren )
	end
	
	return serializedAlbumInfo
end

--------------------------------------------------------------------------------

local function importAlbumHierarchy( self, catalog, firstLevelAlbumsWithHierarchy, manifestStatusDatabase, idCache, lrcatStoreProviderId, manifestSourceId, messageHandler )

	local serializedAlbumInfo = serializeHierarchy( firstLevelAlbumsWithHierarchy )
	
	self._totalCount = #serializedAlbumInfo
	
	-- Remove the albums that have already been processed and update progress
	do
		local alreadyProcessedCount = 0
		local filteredAlbumInfo = {}
		for _, albumInfo in ipairs( serializedAlbumInfo ) do
			if self:_isAlreadyProcessed( albumInfo.local_id ) then
				alreadyProcessedCount = alreadyProcessedCount + 1
			else
				table.insert( filteredAlbumInfo, albumInfo )
			end
		end
		
		if alreadyProcessedCount > 0 then
			self:_updateAlreadyProcessed( messageHandler, alreadyProcessedCount )
		end
		
		serializedAlbumInfo = filteredAlbumInfo
	end
	
	
	-- Import the albums one-by-one. Wait for one album to be imported before starting the next so we 
	-- can specify the hierarchy.
	local albumInfoObserver, iterator, val
	
	local albumInfoObservable = Observable.create( function( observer )
		albumInfoObserver = observer
		
		-- Only the first album
		iterator, val = next( serializedAlbumInfo )
		if val then
			observer:onNext( val )
		else
			albumInfoObserver:onCompleted()
		end
		
	end )
	
		:flatMap( function( albumInfo )
			
			if not albumInfo.ozAlbumId then
				return Observable.just( albumInfo )
			end
			
			return catalog:docStore():docValue( albumInfo.ozAlbumId )
				:map( function( docValue )
					return albumInfo, docValue
				end )
		end )

		:flatMap( function( albumInfo, mayBeDocValue )
			-- Create the album
			
			local parentId
			
			if albumInfo.parent then
				parentId = idCache:getOzIdForLocalId( WFCatImportManifestConstants.idCacheSets.albums, albumInfo.parent )
				
				if not parentId then
					return Observable.just( false, albumInfo )
				end
			end
			
			-- Special case for migrating from Lightroom - to give it a chance to handle round-tripping workflows
			local importSource = lrcatStoreProviderId and {
				lrcatStoreProviderId = lrcatStoreProviderId,
				lrcatAlbumId = albumInfo.local_id,
			}

			-- Create the folder/album or move an existing synced one
			local albumObservable
			local uuid
			
			local isDeleted = mayBeDocValue and mayBeDocValue._deleted or false
			
			if not DONT_MOVE_SYNCED_ALBUMS and albumInfo.ozAlbumId and not isDeleted then
				uuid = albumInfo.ozAlbumId
				albumObservable = maybeMoveAlbumObservable( catalog, albumInfo.subType, albumInfo.name, parentId, albumInfo.hidden, albumInfo.ozAlbumId, mayBeDocValue, importSource, manifestSourceId, self, messageHandler )
			else
				-- Use the pending id, if it exists already.
				local pendingUUID = self._processedRows[ albumInfo.local_id ] and self._processedRows[ albumInfo.local_id ].oz_id
				uuid = pendingUUID or KSIdGenerator.uuidInLowercaseHex( )
				
				if fakeRevisionExistsErrorUUID and not pendingUUID then
					uuid = fakeRevisionExistsErrorUUID
				end
				
				albumObservable = createAlbumOrFolderObservable( catalog, albumInfo.subType, albumInfo.name, parentId, albumInfo.hidden, importSource, uuid, manifestSourceId )
			end
			
			-- Just so that we dont update the status db with
			-- this fake uuid. If updated once, we will always
			-- try to use the same id and hence even in resume
			-- we wont be able to recover.
			if uuid == fakeRevisionExistsErrorUUID then
				uuid = KSIdGenerator.uuidInLowercaseHex( )
				fakeRevisionExistsErrorUUID = nil
			end
			
			return self:_addStatus( manifestStatusDatabase, albumInfo.local_id, uuid, albumInfo.name, WFCatImportManifestConstants.status.pending )
				:defaultIfEmpty()
				:flatMap( function()
					return albumObservable
				end )
				:map( function( docStoreLocalId, ozId )
					return albumInfo, ozId
				end )
				:catch( function( err )
					
					if WFCatImportManifestProcessorUtils.isDocAlreadyExistsError( err ) then
						
						self:_sendMessage( messageHandler, WFCatImportManifestConstants.messages.albumProcessingRevisionExists.code, { err } )
						
						local pauseInfo = {
							pauseType = WFCatImportManifestConstants.pauseType.resumeAuto,
						}
						
						return Observable.throw( pauseInfo )
					end
					
					-- Eating off the error so that other album
					-- processing can continue.
					return Observable.just( albumInfo, false )
				end )

		end )
		
		:flatMap( function( albumInfo, ozId )
			local updateStatusObservable
			if not ozId then
				self:_sendMessage( messageHandler, WFCatImportManifestConstants.messages.albumImportError.code, { { albumInfo.name, albumInfo.local_id } } )
				updateStatusObservable = self:_updateStatus( manifestStatusDatabase, { { local_id = albumInfo.local_id } }, WFCatImportManifestConstants.status.failure )
				self:_updateError( messageHandler, 1 )
			else
				-- Save the new doc id
				idCache:setOzIdForLocalId( WFCatImportManifestConstants.idCacheSets.albums, albumInfo.local_id, ozId )
				updateStatusObservable = self:_updateStatus( manifestStatusDatabase, { { local_id = albumInfo.local_id } }, WFCatImportManifestConstants.status.success )
				self:_updateSuccess( messageHandler, 1 )
			end
			
			return updateStatusObservable
				:defaultIfEmpty( albumInfo, ozId )
		end )
		
		:tap( function( ... )
		
			-- We have imported one album. Import another.
			iterator, val = next( serializedAlbumInfo, iterator )
			
			if iterator then
				albumInfoObserver:onNext( val )
			else
				--require 'WFRxScheduler'.sharedScheduler():schedule( function()
					albumInfoObserver:onCompleted()
				--end )
			end

		end )
		
	return albumInfoObservable
	
end

--------------------------------------------------------------------------------

local function _extractAlbumNamesAndIds( albumsStructure )
	
	local albumNames = {}
	local albumOzIdToDocstoreLocalId = {}
	
	for _, albumInfo in ipairs( albumsStructure or {} ) do
		
		local parentId = albumInfo.parentId or kNilKey
		local albumNamesForAlbum = albumNames[ parentId ] or {}
		albumNamesForAlbum[ albumInfo.name ] = true
		
		albumNames[ parentId ] = albumNamesForAlbum
		
		albumOzIdToDocstoreLocalId[ albumInfo.fullDocId ] = albumInfo.id

	end
	
	return albumNames, albumOzIdToDocstoreLocalId
end

--------------------------------------------------------------------------------

function WFCatImportManifestAlbumsProcessor:processImpObservable( catalog, profileCatalog, wfCatSynchronizer, 
		manifestDatabase, manifestStatusDatabase, idCache, messageHandler, contextInfo )
	
	return Observable.zip(
			WFCatImportManifestProcessorUtils.maybeReadLrCatStoreProviderId( manifestDatabase ),
			WFCatImportManifestProcessorUtils.maybeReadManifestSourceId( manifestDatabase )
		)
	
		:flatMap( function( lrcatStoreProviderId, manifestSourceId )
			
			return manifestDatabase:readData( 'selectAlbums' )
				:map( function( results )
					
					return lrcatStoreProviderId, manifestSourceId, results
				end )
		end )
	
		:flatMap( function( lrcatStoreProviderId, manifestSourceId, results )
		
			-- If there are no albums, don't even create the hierarchy, not even the root album
			if #results == 0 then
				return Observable.empty()
			end
			
			-- Setup the hierarchy into our data-structure and flatten it as Nimbus requires
			local firstLevelAlbumsWithHierarchy = _setupHierarchy( results )
			local flattenedFirstLevelAlbumsWithHierarchy = _flatten( firstLevelAlbumsWithHierarchy )
			
			
			-- Get the list of existing albums (need to check for name-conflicts)
			return WFCatAlbums.getAlbumsList( catalog )
				
				:take( 1 )
					-- We only need the current set. Also avoid recursive updates.
					
				:map( function( structure )
					return _extractAlbumNamesAndIds( structure )
				end )
				
				:catch( function( err )
					self:_sendMessage( messageHandler, self._messageCodeGenericError, { "album fetch: "..tostring( err ) } )
					return Observable.throw( err )
				end )
				
				:map( function( existingAlbumNamesForId, albumOzIdToDocstoreLocalId )
				
					-- resolve name conflicts for all albums
					-- except root, since it should be already
					-- be there.
					local rootOzId = not DONT_CREATE_UNDER_ROOT_ALBUM_SET and idCache:getOzIdForLocalId( WFCatImportManifestConstants.idCacheSets.albums, WFCatImportManifestConstants.kRootAlbumLocalId )
					local rootLocalId = not DONT_CREATE_UNDER_ROOT_ALBUM_SET and albumOzIdToDocstoreLocalId[ rootOzId or kNilKey ]
					
					for _, album in ipairs( flattenedFirstLevelAlbumsWithHierarchy ) do
						_resolveNameCollisions( album, existingAlbumNamesForId[ rootLocalId or kNilKey ] or {}, existingAlbumNamesForId, albumOzIdToDocstoreLocalId, idCache )
					end
					
					return flattenedFirstLevelAlbumsWithHierarchy
					
				end )
				
				:catch( function( err )
					self:_sendMessage( messageHandler, self._messageCodeGenericError, { "resolution phase: "..tostring( err ) } )
					return Observable.throw( err )
				end )
				
				:flatMap( function( flattenedFirstLevelAlbumsWithHierarchy )
					return importAlbumHierarchy( self, catalog, flattenedFirstLevelAlbumsWithHierarchy, manifestStatusDatabase, idCache, lrcatStoreProviderId, manifestSourceId, messageHandler )
				end )
				
				:catch( function( err )
					self:_sendMessage( messageHandler, self._messageCodeGenericError, { "import phase: "..tostring( err ) } )
					return Observable.throw( err )
				end )
				
		end )
		:ignoreElements()
		:defaultIfEmpty()
		:flatMap( function()
		
			local queryId = "countAlbumsStatus"
			local dataType = WFCatImportManifestConstants.dataTypes.albums
			return self:_sendSummary( messageHandler, manifestStatusDatabase, queryId, dataType )
			
		end )
		:ignoreElements()
			-- Adding summary step to the chain and then explicitely
			-- ignoring elements to denote that we are not interested
			-- in the return values.

end

--------------------------------------------------------------------------------

return Rx.Class 'WFCatImportManifestAlbumsProcessor'( WFCatImportManifestAlbumsProcessor )
