/guitone-1.0rc5/src/model/InventoryWatcher.cpp
C++ | 386 lines | 285 code | 50 blank | 51 comment | 76 complexity | 49dd9a1f7436dd05fb6c3220004b41a2 MD5 | raw file
Possible License(s): GPL-3.0
- /***************************************************************************
- * Copyright (C) 2008 by Thomas Keller *
- * me@thomaskeller.biz *
- * *
- * 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 3 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, see <http://www.gnu.org/licenses/>. *
- ***************************************************************************/
- #include "InventoryWatcher.h"
- #include "InventoryItem.h"
- #include "BasicIOParser.h"
- #include "GuitoneCore.h"
- InventoryWatcher::InventoryWatcher(QObject * parent)
- : QFileSystemWatcher(parent),
- // do not watch more than 200 paths at once
- lruCache(200)
- {
- connect(
- this, SIGNAL(directoryChanged(const QString &)),
- this, SLOT(pathChanged(const QString &))
- );
- connect(
- this, SIGNAL(fileChanged(const QString &)),
- this, SLOT(pathChanged(const QString &))
- );
- notifyTimer = new QTimer(this);
- connect(
- notifyTimer, SIGNAL(timeout()),
- this, SLOT(notifyChangedPaths())
- );
- }
- InventoryWatcher::~InventoryWatcher()
- {
- delete notifyTimer;
- }
- //! TODO: monitor _MTN/options for changes as well
- void InventoryWatcher::setMonotoneHandle(const MonotoneHandlePtr & handle)
- {
- monotoneHandle = handle;
- QString workspace = handle->getData();
- QString revPath(workspace + "/_MTN/revision");
- QFile file(revPath);
- if (!file.open(QIODevice::ReadOnly))
- {
- C(QString("cannot open %1/_MTN/revision for reading").arg(workspace));
- }
- QString contents(file.readAll());
- file.close();
- BasicIOParser parser(contents);
- if (!parser.parse())
- {
- C("could not parse contents of _MTN/revision");
- }
- oldRevisionEntries = parser.getStanzas();
- addPath(revPath);
- }
- void InventoryWatcher::watchPaths(const QStringList & paths)
- {
- QStringList watchedPaths = files() + directories();
- QString workspace = monotoneHandle->getData();
- foreach (const QString & path, paths)
- {
- QString fullPath(workspace);
- if (path != "/") fullPath += "/" + path;
- if (watchedPaths.indexOf(fullPath) != -1) continue;
- D(QString("adding %1").arg(fullPath));
- addPath(fullPath);
- // directories end with a slash and should keep a little longer
- // around, thats why we give them one additional heat point
- // more initially
- QString removedPath = lruCache.add(
- path, path.right(1) == "/" ? 2 : 1, 1
- );
- if (!removedPath.isEmpty())
- {
- QString fullRemovedPath(workspace);
- if (removedPath != "/") fullRemovedPath += "/" + removedPath;
- D(QString("removing %1 because of LRU").arg(fullRemovedPath));
- removePath(fullRemovedPath);
- }
- }
- }
- void InventoryWatcher::unwatchPaths(const QStringList & paths)
- {
- QStringList watchedPaths = files() + directories();
- foreach (const QString & path, paths)
- {
- QString fullPath(monotoneHandle->getData());
- if (path != "/") fullPath += "/" + path;
- if (watchedPaths.indexOf(fullPath) == -1) continue;
- removePath(fullPath);
- lruCache.remove(path);
- }
- }
- void InventoryWatcher::pathChanged(const QString & path)
- {
- QString workspace = monotoneHandle->getData();
- I(path.indexOf(workspace) != -1);
- if (path == QString(workspace + "/_MTN/revision"))
- {
- checkForBookkeepChanges();
- // we need to re-add this path since
- // mtn apparently re-creates the file
- addPath(workspace + "/_MTN/revision");
- return;
- }
- QString nodePath =
- path.mid(path.indexOf(workspace) + workspace.length() + 1);
- if (nodePath == ".mtn-ignore")
- {
- // automate stdio up until 0.46 needs to be restarted in order to
- // pick up changes in .mtn-ignore
- L(QString(".mtn-ignore changed, stopping all threads for %1")
- .arg(workspace));
- APP->manager()->stopThreads(monotoneHandle);
- // if something inside .mtn-ignore has changed, the complete workspace
- // could be affected, so do a full reload
- markPathForNotification("");
- return;
- }
- markPathForNotification(nodePath);
- }
- void InventoryWatcher::markPathForNotification(const QString & newPath)
- {
- //
- // The following algorithm only picks those paths for an update
- // which are not a sub path of an already existing path
- //
- // current: "a/b" "b/c"
- // add "g": "a/b" "b/c" "g"
- // add "b": "a/b" "b"
- // add "a/b/c": "a/b" "b/c"
- // add "": ""
- //
- QStringList actualNotifiedPaths;
- if (newPath.isEmpty())
- {
- actualNotifiedPaths.append("");
- }
- else
- {
- enum { skip, replace, add } action = add;
- foreach (QString oldPath, changedPaths)
- {
- if (newPath.startsWith(oldPath) ||
- newPath == oldPath || oldPath == "")
- {
- action = skip;
- break;
- }
- if (oldPath.startsWith(newPath))
- {
- action = replace;
- }
- }
- if (action == add)
- {
- actualNotifiedPaths = changedPaths;
- actualNotifiedPaths.append(newPath);
- }
- else
- if (action == replace)
- {
- foreach (QString oldPath, changedPaths)
- {
- if (!oldPath.startsWith(newPath))
- {
- actualNotifiedPaths.append(oldPath);
- }
- }
- actualNotifiedPaths.append(newPath);
- }
- else
- if (action == skip)
- {
- actualNotifiedPaths = changedPaths;
- }
- else
- I(false);
- }
- changedPaths = actualNotifiedPaths;
- if (notifyTimer->isActive())
- {
- notifyTimer->stop();
- }
- // FIXME: make this configurable eventually
- notifyTimer->start(500);
- }
- void InventoryWatcher::notifyChangedPaths()
- {
- notifyTimer->stop();
- D(QString("notifying changed paths %1").arg(changedPaths.join(",")));
- foreach (QString path, changedPaths)
- {
- emit changedPath(path);
- }
- changedPaths.clear();
- }
- void InventoryWatcher::checkForBookkeepChanges()
- {
- QString workspace = monotoneHandle->getData();
- QFile file(workspace + "/_MTN/revision");
- if (!file.open(QIODevice::ReadOnly))
- {
- C(QString("cannot open %1/_MTN/revision for reading").arg(workspace));
- return;
- }
- QString contents(file.readAll());
- file.close();
- BasicIOParser parser(contents);
- if (!parser.parse())
- {
- C("could not parse contents of _MTN/revision");
- return;
- }
- StanzaList newRevisionEntries = parser.getStanzas();
- foreach (const Stanza & newStanza, newRevisionEntries)
- {
- QString newEntryType = newStanza.at(0).sym;
- // we don't care about patches, since we're notified of them
- // separately; and the other two are uninteresting for us as well
- if (newEntryType == "format_version" ||
- newEntryType == "new_manifest" ||
- newEntryType == "patch")
- {
- continue;
- }
- bool foundOrAlreadyHandled = false;
- enum { unknown, file, dir } type = unknown;
- // Unfortunately monotone's revision format doesn't record the node type
- // so we can only derive type info from add_dir or add_file, but
- // unfortunately not for deleted nodes or attribute changes.
- // In this case this code only works as long as the file is still
- // still present in the file system so we can manually query its type.
- type = newEntryType == "add_dir" ? dir :
- newEntryType == "add_file" ? file : unknown;
- for (int i=0; i<oldRevisionEntries.size(); )
- {
- Stanza oldStanza = oldRevisionEntries.at(i);
- QString oldEntryType = oldStanza.at(0).sym;
- if (oldEntryType == "format_version" ||
- oldEntryType == "new_manifest" ||
- oldEntryType == "patch")
- {
- oldRevisionEntries.removeAt(i);
- foundOrAlreadyHandled = true;
- continue;
- }
- if (newEntryType != oldEntryType)
- {
- i++;
- continue;
- }
- if (newEntryType == "old_revision")
- {
- if (oldStanza.at(0).vals.at(0) != newStanza.at(0).vals.at(0))
- {
- emit changedBaseRevision(newStanza.at(0).vals.at(0));
- }
- oldRevisionEntries.removeAt(i);
- foundOrAlreadyHandled = true;
- break;
- }
- // one stanza entries to compare
- if (newEntryType == "delete" ||
- newEntryType == "add_file" ||
- newEntryType == "add_dir")
- {
- if (oldStanza.at(0).vals.at(0) == newStanza.at(0).vals.at(0))
- {
- oldRevisionEntries.removeAt(i);
- foundOrAlreadyHandled = true;
- break;
- }
- }
- // two stanza entries to compare
- if (newEntryType == "rename" || newEntryType == "clear")
- {
- if (oldStanza.at(0).vals.at(0) == newStanza.at(0).vals.at(0) &&
- oldStanza.at(1).vals.at(0) == newStanza.at(1).vals.at(0))
- {
- oldRevisionEntries.removeAt(i);
- foundOrAlreadyHandled = true;
- break;
- }
- }
- // three stanza entries to compare
- if (newEntryType == "set")
- {
- if (oldStanza.at(0).vals.at(0) == newStanza.at(0).vals.at(0) &&
- oldStanza.at(1).vals.at(0) == newStanza.at(1).vals.at(0) &&
- oldStanza.at(2).vals.at(0) == newStanza.at(2).vals.at(0))
- {
- oldRevisionEntries.removeAt(i);
- foundOrAlreadyHandled = true;
- break;
- }
- }
- i++;
- }
- if (foundOrAlreadyHandled) continue;
- QString path = newStanza.at(0).vals.at(0);
- if (type == unknown)
- {
- QFileInfo info(path);
- type = info.isDir() ? dir : unknown;
- }
- if (type == dir)
- path += "/";
- markPathForNotification(path);
- }
- // now loop through all the left old items;
- // we should only have a subset of items in this list now
- foreach (const Stanza & oldStanza, oldRevisionEntries)
- {
- markPathForNotification(oldStanza.at(0).vals.at(0));
- }
- oldRevisionEntries = newRevisionEntries;
- }
- void InventoryWatcher::clearAllWatches()
- {
- notifyTimer->stop();
- changedPaths.clear();
- removePaths(directories());
- removePaths(files());
- lruCache.clear();
- }