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

NmbAlbums.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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.

----------------------------------------------------------------------------]]--

	-- Rx
local Rx = require 'Rx'

local KSTableUtils = require 'KSTableUtils'
local NmbConnectors = require 'NmbConnectors'
local WFCatAlbums = require 'WFCatAlbums'
local WFCatCollaboration = require 'WFCatCollaboration'
local WFCatDevelopSettings = require 'WFCatDevelopSettings'
local WFCatIdUtils = require 'WFCatIdUtils'
local WFCatSharedWithYouAlbums = require 'WFCatSharedWithYouAlbums'
local WFCatSharedWithYouUtils = require 'WFCatSharedWithYouUtils'

local WFRxRunUntilDone = require 'WFRxRunUntilDone'

local runUntilDone = WFRxRunUntilDone

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

local NmbAlbums  = {}

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

local function returnSecondParam( param1, param2 )
	return param2
end

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

local function createFolderWithCache( wfCatalog, albumsCache, folderName, optParentId )
		
	return WFCatAlbums.createFolder( wfCatalog, folderName, optParentId )
			:flatMap( function( localDocId, fullDocId, docValue )
			
			return albumsCache:addAlbumOrFolder( docValue, optParentId )
				:map( function()
					return localDocId, fullDocId, docValue
				end )
		end )
end

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

local function createAlbumWithCache( wfCatalog, albumsCache, albumName, optParentId, optHidden )
	
	return WFCatAlbums.createAlbum( wfCatalog, albumName, optParentId, optHidden )
			:flatMap( function( localDocId, fullDocId, docValue )
			
			return albumsCache:addAlbumOrFolder( docValue, optParentId )
				:map( function()
					return localDocId, fullDocId, docValue
				end )
		end )
end

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

local function createSmartAlbumWithCache( wfCatalog, albumsCache, smartAlbumName, optParentId, predicates )
	
	return WFCatAlbums.createSmartAlbum( wfCatalog, smartAlbumName, optParentId, predicates )
			:flatMap( function( localDocId, fullDocId, docValue )
			
			return albumsCache:addAlbumOrFolder( docValue, optParentId )
				:map( function()
					return localDocId, fullDocId, docValue
				end )
		end )
	
end

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

local function createAdhocAlbumSynchronouslyWithCache( wfCatalog, albumsCache, adhocAlbumName )
	
	local optParentId = nil
	local optHidden = true

	return WFCatAlbums.createAlbumSynchronously( wfCatalog, adhocAlbumName, optParentId, optHidden )
			:flatMap( function( localDocId, albumDocId, docValue, didCreateWithOz )
			
			return albumsCache:addAlbumOrFolder( docValue, optParentId )
				:map( function()

					-- createAdhocAlbumSynchronouslyWithCache is called from a model which
					-- can only support one return value so make a table out of them.
					return {
						localDocId = localDocId,
						albumDocId = albumDocId,
						didCreateWithOz = didCreateWithOz,
					}

				end )
		end )
	
end

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

local function renameAlbumOrFolderWithCache( wfCatalog, albumsCache, albumId, albumName )
		
	return Rx.Observable.combineLatest(
		albumsCache:renameAlbumOrFolder( albumId, albumName ),
		WFCatAlbums.renameAlbumOrFolder( wfCatalog, albumId, albumName ) )
		:map( returnSecondParam )
	
end

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

local function setAssetSortOrderWithCache( wfCatalog, albumsCache, albumId, assetSortOrder )
		
	return Rx.Observable.combineLatest(
		albumsCache:setAssetSortOrder( albumId, assetSortOrder ),
		WFCatAlbums.setAssetSortOrder( wfCatalog, albumId, assetSortOrder ) )
		:map( returnSecondParam )
	
end

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

local function reparentAlbumOrFolderWithCache( wfCatalog, albumsCache, albumId, optNewParentId )
	 
	return Rx.Observable.combineLatest(
		albumsCache:reparentAlbumOrFolder( albumId, optNewParentId ),
		WFCatAlbums.reparentAlbumOrFolder( wfCatalog, albumId, optNewParentId ) )
		:map( returnSecondParam )
end

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

local function reparentMultipleAlbumsOrFoldersWithCache( wfCatalog, albumsCache, albumIds, optNewParentId, optOrder )
	
	return Rx.Observable.combineLatest(
		albumsCache:reparentMultipleAlbumsOrFolders( albumIds, optNewParentId, optOrder ),
		WFCatAlbums.reparentMultipleAlbumsOrFolders( wfCatalog, albumIds, optNewParentId, optOrder) )
		:map( returnSecondParam )

end

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

local function updateSmartAlbumWithCache( wfCatalog, albumsCache, albumId, smartAlbumName, optNewParentId, predicates )
	
	return Rx.Observable.combineLatest(
		albumsCache:updateSmartAlbum( albumId, smartAlbumName, optNewParentId, predicates ),		
		WFCatAlbums.updateSmartAlbum( wfCatalog, albumId, smartAlbumName, optNewParentId, predicates ) )
			:map( returnSecondParam )
		
end

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

local function deleteAlbumOrFolderWithCache( wfCatalog, albumsCache, albumId )
	
	-- SPAM [matthewj 2018-01-24]: NMB-7104 Note that deletion is different within the cache
	-- here.  Since the cache is what is displayed to users, we need to make sure that we only
	-- delete from the real doc store those folders/albums we removed from the cache.  Otherwise
	-- we can delete things that the user does not realize they are deleting.
	return albumsCache:getCache()
		:take( 1 )
		:flatMap( function( cachedHierarchy )
			-- SPAM [matthewj 2018-08-14]: I would generally prefer that the client is explicitly choosing 
			-- to call either 'deleteAlbum' or 'deleteProjectAlbum' because different types of albums likely
			-- have different actions that need to happen on delete. For now we'll make that distinction here
			local albumInfo = WFCatAlbums._getAlbumFromHierarchyById( cachedHierarchy, albumId )
			
			if not albumInfo then
				return Rx.Observable.empty()
			end
			
			if albumInfo.subtype == 'project' then
				-- do project deletion stuff
				return albumsCache:deleteProjectAlbum( albumId )
					:flatMap( function( albumIdsRemovedFromCache )
						return WFCatAlbums.deleteProjectAlbums( wfCatalog, albumIdsRemovedFromCache )
					end )
				
			else
				return albumsCache:deleteAlbumOrFolder( albumId )
					:flatMap( function( folderAndAlbumIdsRemovedFromCache )
						return WFCatAlbums.deleteIndividualAlbums( wfCatalog, folderAndAlbumIdsRemovedFromCache )
				end )
			end
			
		end )
		:takeLast( 0 )
end

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

local function shareAlbumWithCache( wfCatalog, albumsCache, albumId, ... )

	return Rx.Observable.combineLatest(
		albumsCache:shareAlbum( albumId ),
		WFCatCollaboration.shareAlbum( wfCatalog, albumId, ... ) )
		:map( returnSecondParam ) 

end

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

local function unshareAlbumWithCache( wfCatalog, albumsCache, albumId )

	return Rx.Observable.combineLatest(
		albumsCache:unshareAlbum( albumId ),
		WFCatCollaboration.unshareAlbum( wfCatalog, albumId ) )
		:map( returnSecondParam ) 

end

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

local function makeCatalogAndCacheBasedObservableIntoSubModel( fn, optObservableType, maybeRunUntilDone )
	
	local deliverOneResult = optObservableType == 'deliverOneResult'
	
	return function( model, ... )
		local params = { ... }
		local nParams = select( '#', ... )
		local value

		local catalogStream = deliverOneResult and model._wfCatalog or model._wfCatalog:take( 1 )
		local subModelStream = catalogStream
                             	:mapSwitch( function( wfCatalog, cache )
									
									if maybeRunUntilDone then
                             			return fn( wfCatalog, cache, unpack( params, 1, nParams ) )
											:call( runUntilDone )
									else
										return fn( wfCatalog, cache, unpack( params, 1, nParams ) )
									end
									
                               	end )
								
								
		value = deliverOneResult and subModelStream:take( 1 ) or subModelStream

		return {
			_wfCatalog = model._wfCatalog,

			value = value,
		}
		
	end
	
end

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

local function makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( fn, optObservableType )

	return makeCatalogAndCacheBasedObservableIntoSubModel( fn, optObservableType, true )

end

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

local function makeCatalogBasedObservableIntoSubModel( fn, optObservableType, maybeRunUntilDone )
	
	local deliverOneResult = optObservableType == 'deliverOneResult'
	
	return function( model, ... )
		local params = { ... }
		local nParams = select( '#', ... )
		local value
		
		local catalogStream = deliverOneResult and model._wfCatalog or model._wfCatalog:take( 1 )
		local subModelStream = catalogStream
                             	:mapSwitch( function( wfCatalog )
								
									if maybeRunUntilDone then
                             			return fn( wfCatalog, unpack( params, 1, nParams ) )
											:call( runUntilDone )
									else
										return fn( wfCatalog, unpack( params, 1, nParams ) )
									end
									
                               	end )
								
		value = deliverOneResult and subModelStream:take( 1 ) or subModelStream

		return {
			_wfCatalog = model._wfCatalog,

			value = value,
		}
	end
end

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

local function makeCatalogBasedObservableIntoRunUntilDoneSubModel( fn, optObservableType )

	return makeCatalogBasedObservableIntoSubModel( fn, optObservableType, true )
	
end

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

local function addAssetsToAlbum( wfCatalog, albumId, assetIds, maybeRedactionOptions )

	return WFCatSharedWithYouAlbums.getSpaceIdOfAlbumId( wfCatalog, albumId )
	
		:flatMap( function( spaceId )
		
			if spaceId then
			
				return WFCatDevelopSettings.waitForPendingDevelopSettingsSaveForAssetIds( assetIds )
				
					:flatMap( function()
					
						return WFCatSharedWithYouAlbums.addAssetsToAlbum( wfCatalog, spaceId, albumId, assetIds, maybeRedactionOptions )
					
					end )
					
					:map( function( added, duplicates, invalidAssets )
						return {
							added = added,
							duplicates = duplicates,
							invalidAssets = invalidAssets,
						}
					end )
			
			else
			
				return WFCatAlbums.addAssetsToAlbum( wfCatalog, albumId, assetIds )
			
			end
		
		end )

end

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

local function removeAssetsFromAlbum( wfCatalog, albumId, assetIds )

	return WFCatSharedWithYouAlbums.getSpaceIdOfAlbumId( wfCatalog, albumId )
	
		:flatMap( function( spaceId )
		
			if spaceId then
			
				return WFCatSharedWithYouAlbums.deleteAssets( wfCatalog, spaceId, assetIds )
			
			else
			
				return WFCatAlbums.removeAssetsFromAlbum( wfCatalog, albumId, assetIds )
			
			end
		
		end )

end

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

local function albumAssetsAndSubtype( wfCatalog, albumId, albumType )
	
	local albumAssetIdsObservable
	if albumType == 'project' then
		albumAssetIdsObservable = WFCatAlbums.getProjectAlbumAssetIds( wfCatalog, albumId )
	else
		albumAssetIdsObservable = WFCatAlbums.getAlbumAssetIds( wfCatalog, albumId )
	end
	   
	return albumAssetIdsObservable
		:take( 1 )
        :flatMap( function( assetIdsMap )
		
            local assetIds = {}
			
            for assetId, _ in pairs( assetIdsMap ) do
                assetIds[ #assetIds + 1 ] = assetId
            end
			
            return WFCatIdUtils.getOzAssetIdsAndTypeForIds( wfCatalog, assetIds )
					:map( function( assetIdsAndSubtypes ) 
						
						local assetIdsToSubtypes = {}
						for _, row in ipairs( assetIdsAndSubtypes ) do
							assetIdsToSubtypes[ row[ 1 ] ] = row[ 2 ]
						end
						
						local result = {}
						for _, assetId in ipairs( assetIds ) do
							result[ #result + 1 ] = { 
								id = assetId, 
								subtype = assetIdsToSubtypes[ assetId ] 
							}
						end
						return result
						
					end )
        end )
		
end

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

--[[
	\brief Returns an array of tables that contain the asset ids in an album along with the subtype of the asset
	(image, video, stack, etc.) if we have it. i.e. <code>{ { id="abc123", subtype="image" }</code>. 
	
	There may be some asset subtypes that we don't have a readily accessible subtype for and for these the subtype may 
	be <code>nil</code> or additional assets and subtypes may be added to the album later. For these reasons it 
	is best to check the subtypes affirmatively when using the results of this function. For example,
	
	<code>if row.subtype == 'image' or row.subtype =='video' then ...</code>
	
	\param wfCatalog WFCatalog
	\param albumId the local or full doc id of an album to get the assets
	
	\return <code>Observable<table></code> list of tables containing the <code>id</code> and <code>subtype</code> 
		of each asset in the provided album
--]]
local function getAlbumAssetsAndSubtype( wfCatalog, albumId )

    return albumAssetsAndSubtype( wfCatalog, albumId )
        
end 

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

--[[
	\brief Returns an array of tables that contain the asset ids in a project album along with the subtype of the asset
	(image, video, stack, etc.) if we have it. i.e. <code>{ { id="abc123", subtype="image" }</code>. 
	
	There may be some asset subtypes that we don't have a readily accessible subtype for and for these the subtype may 
	be <code>nil</code> or additional assets and subtypes may be added to the album later. For these reasons it 
	is best to check the subtypes affirmatively when using the results of this function. For example,
	
	<code>if row.subtype == 'image' or row.subtype =='video' then ...</code>
	
	\param wfCatalog WFCatalog
	\param projectAlbumId the local or full doc id of a project album to get the assets
	
	\return <code>Observable<table></code> list of tables containing the <code>id</code> and <code>subtype</code> 
		of each asset in the project album
--]]
local function getProjectAlbumAssetsAndSubtype( wfCatalog, projectAlbumId )
	 
	 return albumAssetsAndSubtype( wfCatalog, projectAlbumId, 'project' )

end

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

local function distinctTableCompare( _, tbl1, _, tbl2 )

	return KSTableUtils.deepEquals( tbl1, tbl2 )

end

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

--[[
	\param wfCatalog an Observable( WFCatalog )
	Returns a model whose value is an Observable array of top-level albums, each containing fields:
		id : (int) the uuid of the album
		name : (user string) the name of the album
		path : (user string) The name of the parents and album separated by /
		parentId : (uuid, or nil) the id of the albums parent
		children : (array) array of child albums
--]]

function NmbAlbums.makeAlbumsModel( wfCatalog )
	
	local wfCatalogAndCache = wfCatalog
		:filter( function( catalog ) return catalog ~= nil end )
		:map( function( nmbCatalog )

			return nmbCatalog, nmbCatalog:albumsCache()
			
		end )

	return {
	
		_wfCatalog = wfCatalogAndCache,
		
		priority = 'high',
		
		value = {},

		models = {
		
			-- use cache
			albumsList = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache )
					return cache:getAlbumsFromCache( )
						:map( KSTableUtils.deepCopy ) -- Deep copy so the following distinctTableCompare works.
						:distinctUntilChanged( nil, distinctTableCompare )
						:map( function( results )
							return { albums = results }
						end )
				end ),
			
			adhocAlbums = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache )
				 	return cache:getAdhocAlbumsFromCache( )
						:map( KSTableUtils.deepCopy ) -- Deep copy so the following distinctTableCompare works.
						:distinctUntilChanged( nil, distinctTableCompare )
						:map( function( results )
							return { albums = results }
						end  )
				end ),
					
			projectAlbums = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache ) 
					return cache:getProjectAlbumsFromCache( catalog )
						:map( KSTableUtils.deepCopy ) -- Deep copy so the following distinctTableCompare works.
						:distinctUntilChanged( nil, distinctTableCompare )
						:map( function( results )
							return { albums = results } 
						end )
				end ),
			
			sharedAlbums = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache )
					return cache:getSharedAlbumsFromCache( )
						:map( KSTableUtils.deepCopy ) -- Deep copy so the following distinctTableCompare works.
						:distinctUntilChanged( nil, distinctTableCompare )
						:map( function( sharedAlbums )
							return { albums = sharedAlbums }
						end ) 
				end ),

			sharedWithYouAlbumsList = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache )
					return cache:getSharedWithYouAlbumsFromCache()
						:map( KSTableUtils.deepCopy ) -- Deep copy so the following distinctTableCompare works.
						:distinctUntilChanged( nil, distinctTableCompare )
						:map( function( results )
							return { albums = results }
						end )
				end ),
				
			enableConnectionNotification = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache, projectAlbumId, serviceId, editUrl )
				return WFCatAlbums.enableProjectAlbumPublishNotification( catalog, projectAlbumId, serviceId, editUrl )
			end ),
			
			removeConnectionNotification = makeCatalogAndCacheBasedObservableIntoSubModel( function( catalog, cache, projectAlbumId )
				return WFCatAlbums.removeProjectAlbumPublishNotification( catalog, projectAlbumId )
			end ),

			ongoingProjectAlbumNotificationStatus = makeCatalogBasedObservableIntoSubModel( function( catalog, projectAlbumId )			
				return WFCatAlbums.ongoingProjectAlbumNotificationStatus( catalog, projectAlbumId )
			end ),
			
			-- album ops
			createFolder = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( createFolderWithCache, 'deliverOneResult' ),
			createAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( createAlbumWithCache, 'deliverOneResult' ),
			createAdhocAlbumSynchronously = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( createAdhocAlbumSynchronouslyWithCache, 'deliverOneResult' ),
			deleteAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( deleteAlbumOrFolderWithCache, 'deliverOneResult' ),
			renameAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( renameAlbumOrFolderWithCache, 'deliverOneResult' ),
			reparentAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( reparentAlbumOrFolderWithCache, 'deliverOneResult' ),
			reparentMultipleAlbums = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( reparentMultipleAlbumsOrFoldersWithCache, 'deliverOneResult' ),
			
			shareAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( shareAlbumWithCache, 'deliverOneResult' ),
			unshareAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( unshareAlbumWithCache, 'deliverOneResult' ),
			setAssetSortOrder = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( setAssetSortOrderWithCache, 'deliverOneResult' ),
			
			-- do not use cache
			addAssetsToAlbum = makeCatalogBasedObservableIntoRunUntilDoneSubModel( addAssetsToAlbum, 'deliverOneResult' ),
			removeAssetsFromAlbum = makeCatalogBasedObservableIntoRunUntilDoneSubModel( removeAssetsFromAlbum ),
			setAlbumCover = makeCatalogBasedObservableIntoRunUntilDoneSubModel( WFCatAlbums.setAlbumCover, 'deliverOneResult' ),
			unhideAlbum = makeCatalogBasedObservableIntoRunUntilDoneSubModel( WFCatAlbums.unhideAlbum, 'deliverOneResult' ),
			getAlbumAssets = makeCatalogBasedObservableIntoRunUntilDoneSubModel( WFCatAlbums.getAlbumAssetIds, 'deliverOneResult' ),
			getAlbumAssetsAndSubtype = makeCatalogBasedObservableIntoRunUntilDoneSubModel( getAlbumAssetsAndSubtype, 'deliverOneResult' ),
			getProjectAlbumAssets = makeCatalogBasedObservableIntoRunUntilDoneSubModel( WFCatAlbums.getProjectAlbumAssetIds, 'deliverOneResult' ),
			getProjectAlbumAssetsAndSubtype = makeCatalogBasedObservableIntoRunUntilDoneSubModel( getProjectAlbumAssetsAndSubtype, 'deliverOneResult' ),
			
			sharedAlbumUrl = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.sharedAlbumUrl ),
			getLrWebShareUrl = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.getLrWebShareUrl ),
			albumCover = makeCatalogBasedObservableIntoSubModel( WFCatAlbums.albumCover ),
			getSharedAlbumAttributes = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.getSharedAlbumAttributes ),
			setSharedAlbumAttributes = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.setSharedAlbumAttributes ),
	
			hasSharedAlbumsWithDisplayFilters = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.hasSharedAlbumsWithDisplayFilters ),
			deleteDisplayFiltersFromAlbum = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.deleteDisplayFiltersFromAlbum ),
			getCollaborators = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.getCollaborators ),
			inviteEmailAllowed = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.inviteEmailAllowed, 'deliverOneResult'  ),
			inviteByEmail = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.inviteByEmail ),
			getPendingRequests = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.getPendingRequests ),
			getInvitations = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.getInvitations ),
			updateCollaborators = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.updateCollaborators ),
			spaceNeededToContributeAssets = makeCatalogBasedObservableIntoSubModel( WFCatSharedWithYouUtils.spaceNeededToContributeAssets ),
	
			waitForAlbumSync = makeCatalogBasedObservableIntoSubModel( WFCatCollaboration.waitForAlbumSync ),

			createConnectionFromAlbum = makeCatalogBasedObservableIntoRunUntilDoneSubModel( NmbConnectors.createConnectionFromAlbum, 'deliverOneResult' ),
			createConnectionFromPhotos = makeCatalogBasedObservableIntoRunUntilDoneSubModel( NmbConnectors.createConnectionFromPhotos, 'deliverOneResult' ),
			updateConnection = makeCatalogBasedObservableIntoRunUntilDoneSubModel( NmbConnectors.updateConnection, 'deliverOneResult' ),
			getConnectionAttributes = makeCatalogBasedObservableIntoRunUntilDoneSubModel( NmbConnectors.getConnectionAttributes, 'deliverOneResult' ),
			setProjectState = makeCatalogBasedObservableIntoRunUntilDoneSubModel( NmbConnectors.setProjectState, 'deliverOneResult' ),
			
			-- smart album ops
			createSmartAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( createSmartAlbumWithCache, 'deliverOneResult' ),
			updateSmartAlbum = makeCatalogAndCacheBasedObservableIntoRunUntilDoneSubModel( updateSmartAlbumWithCache, 'deliverOneResult' ),
			
		},

	}

end

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

return NmbAlbums
