You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

312 lines
8.4 KiB

/*****************************************************************************
* Copyright (C) 2020 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* ( at your option ) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef LISTCACHE_HPP
#define LISTCACHE_HPP
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <cassert>
#include <memory>
#include <vector>
#include <set>
#include <QObject>
#include <QSharedPointer>
#include "listcacheloader.hpp"
struct MLRange
{
size_t offset = 0;
size_t count = 0;
MLRange() = default;
MLRange(size_t offset, size_t count)
: offset(offset)
, count(count)
{
}
MLRange(const MLRange& other)
: offset(other.offset)
, count(other.count)
{}
MLRange& operator=(const MLRange& other)
{
offset = other.offset;
count = other.count;
return *this;
}
bool isEmpty() const {
return count == 0;
}
bool contains(size_t index) const {
return index >= offset && index < offset + count;
}
void reset()
{
offset = 0;
count = 0;
}
///returns the overlapping range of the current range and @a other
MLRange overlap(const MLRange& other) const
{
if (isEmpty() || other.isEmpty())
return MLRange{};
if (contains(other.offset))
return MLRange(other.offset, (offset + count) - other.offset);
else if (other.contains(offset))
return MLRange(offset, (other.offset + other.count) - offset);
else
return MLRange{};
}
};
class ListCacheBase : public QObject
{
Q_OBJECT
signals:
void localSizeChanged(size_t querySize, size_t maximumSize);
void localDataChanged(int sourceFirst, int sourceLast);
void beginInsertRows(int sourceFirst, int sourceLast);
void endInsertRows();
void beginRemoveRows(int sourceFirst, int sourceLast);
void endRemoveRows();
void beginMoveRows(int sourceFirst, int sourceLast, int destination);
void endMoveRows();
};
/**
* `ListCache` represents a cache for a (constant) list of items.
*
* The caller must provide a `ListCacheLoader<T>` to load and count data
*
* The cache will load data by chunk.
* When data is invalidated, it will use a differentiation algorithm to provide
* update events on data that changes
*
* All its public methods must be called from the UI thread.
*/
template<typename T>
class ListCache : public ListCacheBase
{
public:
using ItemType = T;
struct CacheData {
explicit CacheData(std::vector<ItemType>&& list_,
size_t queryCount_,
size_t maximumCount_)
: list(std::move(list_))
, queryCount(queryCount_)
, maximumCount(maximumCount_)
{
loadedCount = list.size();
}
std::vector<ItemType> list;
//How many items are does the query returns min(maximumCount - offset, limit)
size_t queryCount = 0;
//how many items in the table
size_t maximumCount = 0;
//how many items are loaded (list.size)
size_t loadedCount = 0;
};
public:
static constexpr ssize_t COUNT_UNINITIALIZED = -1;
ListCache(std::unique_ptr<ListCacheLoader<ItemType>>&& loader,
bool useMove, size_t limit, size_t offset, size_t chunkSize = 100);
/**
* Return the item at specified index
*
* This returns the local item (`nullptr` if not present), and does not
* retrieve anything from the loader.
*/
const ItemType *get(size_t index) const;
/**
* Return the first item in the cache for which the functor f returns true
*
* This returns the local item (`nullptr` if not present), and does not
* retrieve anything from the loader.
*/
const ItemType *find(const std::function<bool (const ItemType&)> &&f, int *index = nullptr) const;
/**
* replace item in the cache with the one provided. replacement is based on newItem MLItemId
*
* this returns the index of the replaced item, or -1 if the item is not in the cache
*/
int updateItem(ItemType&& newItem);
/**
* Removes from the cache list given its item id
*
* it returns the index row when the id is found and removed, -1 otherwise
*/
int deleteItem(const std::function<bool (const ItemType&)> &&f);
/**
* move items in the cache contained between @a first
* and @a last to the index @a to
*/
void moveRange(int first, int last, int to);
/**
* remove items in the cache from the index @a first up to the
* index @a last
*/
void deleteRange(int first, int last);
/**
* @return the number of items or `COUNT_UNINITIALIZED`
*
* This returns the local count, and does not retrieve anything from the
* loader.
*/
ssize_t queryCount() const;
/**
* @return the total number of elements in the list, without taking @ref m_limit in account.
* COUNT_UNINITIALIZED is returned if the list isn't initialized
*
* This may be usefull to know whether there are additionnal elements past the limit
*/
ssize_t maximumCount() const;
/**
* Init the list size
*
* This method must be called at most once (the list size is not expected
* to change).
*/
void initCount();
/**
* Request to load data so that the item as index will become available
*/
void refer(size_t index);
/*
* reload
*/
void invalidate();
private:
static uint32_t cacheDataLength(const void* data);
static bool cacheDataCompare(const void* dataOld, uint32_t oldIndex, const void* dataNew, uint32_t newIndex);
//this function must be specialized
static bool compareItems(const ItemType& a, const ItemType& b);
void asyncFetchMore();
void asyncCountAndLoad();
void partialUpdate();
size_t fixupIndexForMove(size_t index) const;
bool m_useMove = false;
/* Ownershipshared between this cache and the runnable spawned to execute
* loader callbacks */
QSharedPointer<ListCacheLoader<ItemType>> m_loader;
size_t m_chunkSize;
//0 limit means no limit
size_t m_limit = 0;
size_t m_offset = 0;
//highest index requested by the view (1 based, 0 is nothing referenced)
size_t m_maxReferedIndex = 0;
bool m_needReload = false;
uint64_t m_appendTask = 0;
uint64_t m_countTask = 0;
std::unique_ptr<CacheData> m_cachedData;
std::unique_ptr<CacheData> m_oldData;
MLRange m_rangeRequested;
//access the list while it's being updated
size_t m_partialIndex = 0;
size_t m_partialX = 0;
size_t m_partialLoadedCount = 0;
//represent a redirection of items that are after m_partialIndex
struct PartialIndexRedirect {
enum class Operation
{
ADD,
DEL
};
explicit PartialIndexRedirect(Operation op_, size_t index_, size_t count_, size_t x_ = 0)
: op(op_)
, index(index_)
, count(count_)
{
if (op == Operation::ADD)
{
val.add.x = x_;
}
}
Operation op;
union {
struct {
size_t x;
} add;
struct {
} del;
} val;
size_t index;
size_t count;
//for set ordering
friend bool operator<(const PartialIndexRedirect& l, const PartialIndexRedirect& r)
{
return l.index < r.index;
}
};
//store index redirection keeping index order
std::set<PartialIndexRedirect> m_partialIndexRedirect;
};
#include "listcache.hxx"
#endif