Commit 046e20b0 authored by Nicolas Carion's avatar Nicolas Carion
Browse files

filewatcher now belongs to projectItemModel and takes undoable insert/remove into account

parent 1eeaa528
......@@ -1111,7 +1111,6 @@ void Bin::setDocument(KdenliveDoc *project)
// Cleanup previous project
m_itemModel->clean();
m_fileWatcher.clear();
delete m_itemView;
m_itemView = nullptr;
m_doc = project;
......@@ -1767,15 +1766,6 @@ void Bin::slotRemoveInvalidClip(const QString &id, bool replace, const QString &
emit requesteInvalidRemoval(id, clip->url(), errorMessage);
}
void Bin::addWatchFile(const QString &binId, const QString &url)
{
m_fileWatcher.addFile(binId, url);
}
void Bin::removeWatchFile(const QString &binId, const QString &url)
{
m_fileWatcher.removeFile(binId, url);
}
// TODO refac cleanup
/*
......
......@@ -24,7 +24,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define KDENLIVE_BIN_H
#include "abstractprojectitem.h"
#include "filewatcher.hpp"
#include "timecode.h"
#include <KMessageWidget>
......@@ -277,8 +276,6 @@ public:
QString getCurrentFolder();
/** @brief Save a clip zone as MLT playlist */
void saveZone(const QStringList &info, const QDir &dir);
void addWatchFile(const QString &binId, const QString &url);
void removeWatchFile(const QString &binId, const QString &url);
// TODO refac: remove this and call directly the function in ProjectItemModel
void cleanup();
......@@ -395,7 +392,6 @@ private:
ProjectSortProxyModel *m_proxyModel;
QToolBar *m_toolbar;
KdenliveDoc *m_doc;
FileWatcher m_fileWatcher;
QLineEdit *m_searchLine;
QToolButton *m_addButton;
QMenu *m_extractAudioAction;
......
......@@ -20,57 +20,58 @@
***************************************************************************/
#include "filewatcher.hpp"
#include "bin/bin.h"
#include "core.h"
#include <QDebug>
#include <QFileInfo>
FileWatcher::FileWatcher(QObject *parent)
: QObject(parent)
, m_fileWatcher(new KDirWatch())
{
m_fileWatcher = KDirWatch::self();
// Init clip modification tracker
m_modifiedTimer.setInterval(1500);
connect(m_fileWatcher, &KDirWatch::dirty, this, &FileWatcher::slotUrlModified);
connect(m_fileWatcher, &KDirWatch::deleted, this, &FileWatcher::slotUrlMissing);
connect(m_fileWatcher.get(), &KDirWatch::dirty, this, &FileWatcher::slotUrlModified);
connect(m_fileWatcher.get(), &KDirWatch::deleted, this, &FileWatcher::slotUrlMissing);
connect(&m_modifiedTimer, &QTimer::timeout, this, &FileWatcher::slotProcessModifiedUrls);
}
void FileWatcher::addFile(const QString &binId, const QString &url)
{
if (m_occurences.contains(url)) {
QStringList currentIds = m_occurences.value(url);
if (!currentIds.contains(binId)) {
currentIds << binId;
m_occurences[url] = currentIds;
}
} else {
// Unknown file, add to list
m_occurences.insert(url, QStringList() << binId);
if (url.isEmpty()) {
return;
}
QFileInfo check_file(url);
// check if file exists and if yes: Is it really a file and no directory?
if (!check_file.exists() || !check_file.isFile()) {
return;
}
if (m_occurences.count(url) == 0) {
m_fileWatcher->addFile(url);
}
m_occurences[url].insert(binId);
m_binClipPaths[binId] = url;
}
void FileWatcher::removeFile(const QString &binId, const QString &url)
void FileWatcher::removeFile(const QString &binId)
{
if (!m_occurences.contains(url)) {
if (m_binClipPaths.count(binId) == 0) {
return;
}
QStringList currentIds = m_occurences.value(url);
currentIds.removeAll(binId);
if (currentIds.isEmpty()) {
QString url = m_binClipPaths[binId];
m_occurences[url].erase(binId);
m_binClipPaths.erase(binId);
if (m_occurences[url].empty()) {
m_fileWatcher->removeFile(url);
m_occurences.remove(url);
} else {
m_occurences[url] = currentIds;
m_occurences.erase(url);
}
}
void FileWatcher::slotUrlModified(const QString &path)
{
if (!m_modifiedUrls.contains(path)) {
m_modifiedUrls << path;
const QStringList ids = m_occurences.value(path);
for (const QString &id : ids) {
pCore->bin()->setWaitingStatus(id);
if (m_modifiedUrls.count(path) == 0) {
m_modifiedUrls.insert(path);
for (const QString &id : m_occurences[path]) {
emit binClipWaiting(id);
}
}
if (!m_modifiedTimer.isActive()) {
......@@ -81,7 +82,7 @@ void FileWatcher::slotUrlModified(const QString &path)
void FileWatcher::slotUrlMissing(const QString &path)
{
// TODO handle missing clips by replacing producer with an invalid producer
const QStringList ids = m_occurences.value(path);
// const QStringList ids = m_occurences.value(path);
/*for (const QString &id : ids) {
emit missingClip(id);
}*/
......@@ -89,17 +90,16 @@ void FileWatcher::slotUrlMissing(const QString &path)
void FileWatcher::slotProcessModifiedUrls()
{
QStringList checkList = m_modifiedUrls;
auto checkList = m_modifiedUrls;
for (const QString &path : checkList) {
if (m_fileWatcher->ctime(path).msecsTo(QDateTime::currentDateTime()) > 1000) {
const QStringList ids = m_occurences.value(path);
for (const QString &id : ids) {
pCore->bin()->reloadClip(id);
for (const QString &id : m_occurences[path]) {
emit binClipModified(id);
}
m_modifiedUrls.removeAll(path);
m_modifiedUrls.erase(path);
}
}
if (m_modifiedUrls.isEmpty()) {
if (m_modifiedUrls.empty()) {
m_modifiedTimer.stop();
}
}
......@@ -107,10 +107,11 @@ void FileWatcher::slotProcessModifiedUrls()
void FileWatcher::clear()
{
m_fileWatcher->stopScan();
const QList<QString> files = m_occurences.keys();
for (const QString &url : files) {
m_fileWatcher->removeFile(url);
for (const auto &f : m_occurences) {
m_fileWatcher->removeFile(f.first);
}
m_occurences.clear();
m_modifiedUrls.clear();
m_binClipPaths.clear();
m_fileWatcher->startScan();
}
......@@ -22,9 +22,11 @@
#ifndef FILEWATCHER_H
#define FILEWATCHER_H
#include "definitions.h"
#include <KDirWatch>
#include <QMap>
#include <QTimer>
#include <unordered_map>
#include <unordered_set>
/** @brief This class is responsible for watching all files used in the project
and triggers a reload notification when a file changes.
......@@ -32,27 +34,41 @@
class FileWatcher : public QObject
{
Q_OBJECT
public:
// Constructor
explicit FileWatcher(QObject *parent = nullptr);
// Add a file to the list of watched items
void addFile(const QString &binId, const QString &url);
// Remove a file from the list of watched items
void removeFile(const QString &binId, const QString &url);
// Remove a binId from the list of watched items
void removeFile(const QString &binId);
// Reset all watched files
void clear();
signals:
/** @brief This signal is triggered whenever the file corresponding to a bin clip has been modified and should be reloaded. Note that this signal is sent no
* more than every 1500 ms. We also make sure that at least 1000ms has passed since the last modification of the file. */
void binClipModified(const QString &binId);
/** @brief Same signal than binClipModified, but triggers immediately. Can be useful to refresh UI without actually reloading the file (yet)*/
void binClipWaiting(const QString &binId);
private slots:
void slotUrlModified(const QString &path);
void slotUrlMissing(const QString &path);
void slotProcessModifiedUrls();
private:
KDirWatch *m_fileWatcher;
// This is a handle to the watcher singleton, not owned by this class.
std::unique_ptr<KDirWatch> m_fileWatcher;
// A list with urls as keys, and the corresponding clip ids as value
QMap<QString, QStringList> m_occurences;
QStringList m_modifiedUrls;
std::unordered_map<QString, std::unordered_set<QString>> m_occurences;
// keys are binId, keys are stored paths
std::unordered_map<QString, QString> m_binClipPaths;
// List of files for which we received an update since the last send
std::unordered_set<QString> m_modifiedUrls;
QTimer m_modifiedTimer;
};
......
......@@ -54,6 +54,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kdenlive_debug.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <QApplication>
#include <QCryptographicHash>
#include <QDir>
#include <QDomElement>
......@@ -77,10 +78,6 @@ ProjectClip::ProjectClip(const QString &id, const QIcon &thumb, std::shared_ptr<
} else {
m_thumbnail = thumb;
}
if (m_clipType == ClipType::AV || m_clipType == ClipType::Audio || m_clipType == ClipType::Image || m_clipType == ClipType::Video ||
m_clipType == ClipType::Playlist || m_clipType == ClipType::TextTemplate) {
pCore->bin()->addWatchFile(id, clipUrl());
}
// Make sure we have a hash for this clip
hash();
connect(m_markerModel.get(), &MarkerListModel::modelChanged, [&]() { setProducerProperty(QStringLiteral("kdenlive:markers"), m_markerModel->toJson()); });
......@@ -139,10 +136,6 @@ std::shared_ptr<ProjectClip> ProjectClip::construct(const QString &id, const QDo
ProjectClip::~ProjectClip()
{
// controller is deleted in bincontroller
if (m_clipType == ClipType::AV || m_clipType == ClipType::Audio || m_clipType == ClipType::Image || m_clipType == ClipType::Video ||
m_clipType == ClipType::Playlist || m_clipType == ClipType::TextTemplate) {
pCore->bin()->removeWatchFile(clipId(), clipUrl());
}
m_thumbMutex.lock();
m_requestedThumbs.clear();
m_thumbMutex.unlock();
......@@ -379,13 +372,10 @@ bool ProjectClip::setProducer(std::shared_ptr<Mlt::Producer> producer, bool repl
if (auto ptr = m_model.lock()) {
std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()),
AbstractProjectItem::DataDuration);
std::static_pointer_cast<ProjectItemModel>(ptr)->updateWatcher(std::static_pointer_cast<ProjectClip>(shared_from_this()));
}
// Make sure we have a hash for this clip
getFileHash();
if (m_clipType == ClipType::AV || m_clipType == ClipType::Audio || m_clipType == ClipType::Image || m_clipType == ClipType::Video ||
m_clipType == ClipType::Playlist || m_clipType == ClipType::TextTemplate) {
pCore->bin()->addWatchFile(clipId(), clipUrl());
}
// set parent again (some info need to be stored in producer)
updateParent(parentItem().lock());
return true;
......
/*
Copyright (C) 2012 Till Theato <root@ttill.de>
Copyright (C) 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
Copyright (C) 2017 Nicolas Carion
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
......@@ -25,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "binplaylist.hpp"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "filewatcher.hpp"
#include "jobs/audiothumbjob.hpp"
#include "jobs/jobmanager.h"
#include "jobs/loadjob.hpp"
......@@ -49,12 +51,15 @@ ProjectItemModel::ProjectItemModel(QObject *parent)
: AbstractTreeModel(parent)
, m_lock(QReadWriteLock::Recursive)
, m_binPlaylist(new BinPlaylist())
, m_fileWatcher(new FileWatcher())
, m_nextId(1)
, m_blankThumb()
{
QPixmap pix(QSize(160, 90));
pix.fill(Qt::lightGray);
m_blankThumb.addPixmap(pix);
connect(m_fileWatcher.get(), &FileWatcher::binClipModified, this, &ProjectItemModel::reloadClip);
connect(m_fileWatcher.get(), &FileWatcher::binClipWaiting, this, &ProjectItemModel::setClipWaiting);
}
std::shared_ptr<ProjectItemModel> ProjectItemModel::construct(QObject *parent)
......@@ -385,6 +390,7 @@ void ProjectItemModel::clean()
}
Q_ASSERT(rootItem->childCount() == 0);
m_nextId = 1;
m_fileWatcher->clear();
}
std::shared_ptr<ProjectFolder> ProjectItemModel::getRootFolder() const
......@@ -448,6 +454,10 @@ void ProjectItemModel::registerItem(const std::shared_ptr<TreeItem> &item)
auto clip = std::static_pointer_cast<AbstractProjectItem>(item);
m_binPlaylist->manageBinItemInsertion(clip);
AbstractTreeModel::registerItem(item);
if (clip->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = std::static_pointer_cast<ProjectClip>(clip);
updateWatcher(clipItem);
}
}
void ProjectItemModel::deregisterItem(int id, TreeItem *item)
{
......@@ -455,6 +465,10 @@ void ProjectItemModel::deregisterItem(int id, TreeItem *item)
m_binPlaylist->manageBinItemDeletion(clip);
// TODO : here, we should suspend jobs belonging to the item we delete. They can be restarted if the item is reinserted by undo
AbstractTreeModel::deregisterItem(id, item);
if (clip->itemType() == AbstractProjectItem::ClipItem) {
auto clipItem = static_cast<ProjectClip *>(clip);
m_fileWatcher->addFile(clipItem->clipId(), clipItem->clipUrl());
}
}
int ProjectItemModel::getFreeFolderId()
......@@ -841,3 +855,28 @@ QMap<QString, QString> ProjectItemModel::getProxies(const QString &root)
{
return m_binPlaylist->getProxies(root);
}
void ProjectItemModel::reloadClip(const QString &binId)
{
std::shared_ptr<ProjectClip> clip = getClipByBinID(binId);
if (clip) {
clip->reloadProducer();
}
}
void ProjectItemModel::setClipWaiting(const QString &binId)
{
std::shared_ptr<ProjectClip> clip = getClipByBinID(binId);
if (clip) {
clip->setClipStatus(AbstractProjectItem::StatusWaiting);
}
}
void ProjectItemModel::updateWatcher(std::shared_ptr<ProjectClip> clipItem)
{
if (clipItem->clipType() == ClipType::AV || clipItem->clipType() == ClipType::Audio || clipItem->clipType() == ClipType::Image ||
clipItem->clipType() == ClipType::Video || clipItem->clipType() == ClipType::Playlist || clipItem->clipType() == ClipType::TextTemplate) {
m_fileWatcher->removeFile(clipItem->clipId());
m_fileWatcher->addFile(clipItem->clipId(), clipItem->clipUrl());
}
}
/*
Copyright (C) 2012 Till Theato <root@ttill.de>
Copyright (C) 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
Copyright (C) 2017 by Nicolas Carion
Copyright (C) 2017 Nicolas Carion
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
......@@ -34,6 +34,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class AbstractProjectItem;
class BinPlaylist;
class FileWatcher;
class MarkerListModel;
class ProjectClip;
class ProjectFolder;
......@@ -62,24 +63,19 @@ public:
friend class ProjectClip;
/** @brief Returns a clip from the hierarchy, given its id
*/
/** @brief Returns a clip from the hierarchy, given its id */
std::shared_ptr<ProjectClip> getClipByBinID(const QString &binId);
/** @brief Helper to check whether a clip with a given id exists
*/
/** @brief Helper to check whether a clip with a given id exists */
bool hasClip(const QString &binId);
/** @brief Gets a folder by its id. If none is found, the root is returned
*/
/** @brief Gets a folder by its id. If none is found, the root is returned */
std::shared_ptr<ProjectFolder> getFolderByBinId(const QString &binId);
/** @brief Gets any item by its id.
*/
/** @brief Gets any item by its id. */
std::shared_ptr<AbstractProjectItem> getItemByBinId(const QString &binId);
/** @brief This function change the global enabled state of the bin effects
*/
/** @brief This function change the global enabled state of the bin effects */
void setBinEffectsEnabled(bool enabled);
/** @brief Returns some info about the folder containing the given index */
......@@ -190,6 +186,12 @@ public:
/** @brief Retrieve a list of proxy/original urls */
QMap<QString, QString> getProxies(const QString &root);
/** @brief Request that the producer of a given clip is reloaded */
void reloadClip(const QString &binId);
/** @brief Set the status of the clip to "waiting". This happens when the corresponding file has changed*/
void setClipWaiting(const QString &binId);
protected:
/* @brief Register the existence of a new element
*/
......@@ -203,6 +205,9 @@ protected:
/* @brief Helper function to add a given item to the tree */
bool addItem(std::shared_ptr<AbstractProjectItem> item, const QString &parentId, Fun &undo, Fun &redo);
/* @brief Function to be called when the url of a clip changes */
void updateWatcher(std::shared_ptr<ProjectClip> item);
public slots:
/** @brief An item in the list was modified, notify */
void onItemUpdated(std::shared_ptr<AbstractProjectItem> item, int role);
......@@ -219,6 +224,8 @@ private:
std::unique_ptr<BinPlaylist> m_binPlaylist;
std::unique_ptr<FileWatcher> m_fileWatcher;
int m_nextId;
QIcon m_blankThumb;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment