Commit ae3b67c2 authored by jpetrell's avatar jpetrell

[filemanager] Add engine for file manager. Contributes to JB#5771

parents
TEMPLATE = subdirs
SUBDIRS = src
Name: nemo-qml-plugin-filemanager
Summary: File manager plugin for Nemo Mobile
Version: 0.0.0
Release: 1
Group: System/Libraries
License: BSD
URL: https://git.merproject.org/mer-core/nemo-qml-plugin-filemanager
Source0: %{name}-%{version}.tar.bz2
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Gui)
BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Test)
%description
%{summary}.
%prep
%setup -q -n %{name}-%{version}
%build
%qmake5
make %{?_smp_mflags}
%install
rm -rf %{buildroot}
%qmake5_install
%files
%defattr(-,root,root,-)
%{_libdir}/qt5/qml/Nemo/FileManager/libnemofilemanager.so
%{_libdir}/qt5/qml/Nemo/FileManager/qmldir
#include "consolemodel.h"
#include "globals.h"
enum {
ModelDataRole = Qt::UserRole + 1
};
ConsoleModel::ConsoleModel(QObject *parent) :
QAbstractListModel(parent), m_process(0)
{
}
ConsoleModel::~ConsoleModel()
{
}
int ConsoleModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_lines.count();
}
QVariant ConsoleModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role);
if (!index.isValid() || index.row() > m_lines.count()-1)
return QVariant();
QString line = m_lines.at(index.row());
return line;
}
QHash<int, QByteArray> ConsoleModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(ModelDataRole, QByteArray("modelData"));
return roles;
}
void ConsoleModel::setLines(QStringList lines)
{
if (m_lines == lines)
return;
beginResetModel();
m_lines = lines;
endResetModel();
emit linesChanged();
}
void ConsoleModel::setLines(QString lines)
{
beginResetModel();
m_lines = lines.split(QRegExp("[\n\r]"));
endResetModel();
emit linesChanged();
}
void ConsoleModel::appendLine(QString line)
{
beginInsertRows(QModelIndex(), m_lines.count(), m_lines.count());
m_lines.append(line);
endInsertRows();
}
bool ConsoleModel::executeCommand(QString command, QStringList arguments)
{
// don't execute the command if an old command is still running
if (m_process && m_process->state() != QProcess::NotRunning) {
// if the old process doesn't stop in 1/2 secs, then don't run the new command
if (!m_process->waitForFinished(500))
return false;
}
setLines(QStringList());
m_process = new QProcess(this);
m_process->setReadChannel(QProcess::StandardOutput);
m_process->setProcessChannelMode(QProcess::MergedChannels); // merged stderr channel with stdout channel
connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readProcessChannels()));
connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(handleProcessFinish(int, QProcess::ExitStatus)));
connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleProcessError(QProcess::ProcessError)));
m_process->start(command, arguments);
// the process is killed when ConsoleModel is destroyed (usually when Page is closed)
// should we run the process in bg thread to allow the command to finish(?)
return true;
}
void ConsoleModel::readProcessChannels()
{
while (m_process->canReadLine()) {
QString line = m_process->readLine();
appendLine(line);
}
}
void ConsoleModel::handleProcessFinish(int exitCode, QProcess::ExitStatus status)
{
if (status == QProcess::CrashExit) { // if it crashed, then use some error exit code
exitCode = -99999;
appendLine(tr("** crashed"));
} else if (exitCode != 0) {
appendLine(tr("** error: %1").arg(exitCode));
}
emit processExited(exitCode);
}
void ConsoleModel::handleProcessError(QProcess::ProcessError error)
{
Q_UNUSED(error);
emit processExited(-88888); // if error, then use some error exit code
appendLine(tr("** error"));
}
#ifndef CONSOLEMODEL_H
#define CONSOLEMODEL_H
#include <QAbstractListModel>
#include <QStringList>
#include <QProcess>
/**
* @brief The ConsoleModel class holds a list of strings for a QML list model.
*/
class ConsoleModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QStringList lines READ lines() WRITE setLines(QString) NOTIFY linesChanged())
public:
explicit ConsoleModel(QObject *parent = 0);
~ConsoleModel();
// methods needed by ListView
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
// property accessors
QStringList lines() const { return m_lines; }
void setLines(QStringList lines);
void setLines(QString lines);
void appendLine(QString line);
Q_INVOKABLE bool executeCommand(QString command, QStringList arguments);
signals:
void linesChanged();
void processExited(int exitCode);
private slots:
void readProcessChannels();
void handleProcessFinish(int exitCode, QProcess::ExitStatus status);
void handleProcessError(QProcess::ProcessError error);
private:
QProcess *m_process;
QStringList m_lines;
};
#endif // CONSOLEMODEL_H
This diff is collapsed.
#ifndef ENGINE_H
#define ENGINE_H
#include <QDir>
#include <QVariant>
class FileWorker;
/**
* @brief Engine to handle file operations, settings and other generic functionality.
*/
class Engine : public QObject
{
Q_OBJECT
Q_PROPERTY(int clipboardCount READ clipboardCount() NOTIFY clipboardCountChanged())
Q_PROPERTY(int clipboardContainsCopy READ clipboardContainsCopy() NOTIFY clipboardContainsCopyChanged())
Q_PROPERTY(int progress READ progress() NOTIFY progressChanged())
Q_PROPERTY(QString progressFilename READ progressFilename() NOTIFY progressFilenameChanged())
public:
explicit Engine(QObject *parent = 0);
~Engine();
// properties
int clipboardCount() const { return m_clipboardFiles.count(); }
bool clipboardContainsCopy() const { return m_clipboardContainsCopy; }
int progress() const { return m_progress; }
QString progressFilename() const { return m_progressFilename; }
// For C++
static Engine *instance();
// methods accessible from QML
// asynch methods send signals when done or error occurs
Q_INVOKABLE void deleteFiles(QStringList filenames);
Q_INVOKABLE void cutFiles(QStringList filenames);
Q_INVOKABLE void copyFiles(QStringList filenames);
Q_INVOKABLE void pasteFiles(QString destDirectory);
// cancel asynch methods
Q_INVOKABLE void cancel();
// returns error msg
Q_INVOKABLE QString errorMessage() const { return m_errorMessage; }
// file paths
Q_INVOKABLE QString homeFolder() const;
Q_INVOKABLE QString sdcardPath() const;
Q_INVOKABLE QString androidSdcardPath() const;
// synchronous methods
Q_INVOKABLE bool exists(QString filename);
Q_INVOKABLE QStringList diskSpace(QString path);
Q_INVOKABLE QStringList readFile(QString filename);
Q_INVOKABLE QString mkdir(QString path, QString name);
Q_INVOKABLE QStringList rename(QString fullOldFilename, QString newName);
Q_INVOKABLE QString chmod(QString path,
bool ownerRead, bool ownerWrite, bool ownerExecute,
bool groupRead, bool groupWrite, bool groupExecute,
bool othersRead, bool othersWrite, bool othersExecute);
// access settings
Q_INVOKABLE QString readSetting(QString key, QString defaultValue = QString());
Q_INVOKABLE void writeSetting(QString key, QString value);
signals:
void clipboardCountChanged();
void clipboardContainsCopyChanged();
void progressChanged();
void progressFilenameChanged();
void workerDone();
void workerErrorOccurred(QString message, QString filename);
void fileDeleted(QString fullname);
void settingsChanged();
private slots:
void setProgress(int progress, QString filename);
private:
QStringList mountPoints() const;
QString createHexDump(char *buffer, int size, int bytesPerLine);
QStringList makeStringList(QString msg, QString str = QString());
QStringList m_clipboardFiles;
bool m_clipboardContainsCopy;
int m_progress;
QString m_progressFilename;
QString m_errorMessage;
FileWorker *m_fileWorker;
};
#endif // ENGINE_H
#include "filedata.h"
#include <QDir>
#include <QDateTime>
#include <QMimeDatabase>
#include <QImageReader>
#include "globals.h"
FileData::FileData(QObject *parent) :
QObject(parent)
{
m_file = "";
}
FileData::~FileData()
{
}
void FileData::setFile(QString file)
{
if (m_file == file)
return;
m_file = file;
readInfo();
}
QString FileData::icon() const
{
return infoToIconName(m_fileInfo);
}
QString FileData::permissions() const
{
return permissionsToString(m_fileInfo.permissions());
}
QString FileData::owner() const
{
QString owner = m_fileInfo.owner();
if (owner.isEmpty()) {
uint id = m_fileInfo.ownerId();
if (id != (uint)-2)
owner = QString::number(id);
}
return owner;
}
QString FileData::group() const
{
QString group = m_fileInfo.group();
if (group.isEmpty()) {
uint id = m_fileInfo.groupId();
if (id != (uint)-2)
group = QString::number(id);
}
return group;
}
QString FileData::size() const
{
if (m_fileInfo.isDirAtEnd()) return "-";
return m_fileInfo.size();
}
QString FileData::modified() const
{
return datetimeToString(m_fileInfo.lastModified());
}
QString FileData::created() const
{
return datetimeToString(m_fileInfo.created());
}
QString FileData::absolutePath() const
{
if (m_file.isEmpty())
return QString();
return m_fileInfo.absolutePath();
}
void FileData::refresh()
{
readInfo();
}
bool FileData::mimeTypeInherits(QString parentMimeType)
{
return m_mimeType.inherits(parentMimeType);
}
void FileData::readInfo()
{
m_errorMessage = "";
m_metaData.clear();
m_fileInfo.setFile(m_file);
// exists() checks for target existence in symlinks, so ignore it for symlinks
if (!m_fileInfo.exists() && !m_fileInfo.isSymLink())
m_errorMessage = tr("File does not exist");
readMetaData();
emit fileChanged();
emit isDirChanged();
emit isSymLinkChanged();
emit kindChanged();
emit iconChanged();
emit permissionsChanged();
emit ownerChanged();
emit groupChanged();
emit sizeChanged();
emit modifiedChanged();
emit createdChanged();
emit absolutePathChanged();
emit nameChanged();
emit suffixChanged();
emit symLinkTargetChanged();
emit isSymLinkBrokenChanged();
emit metaDataChanged();
emit mimeTypeChanged();
emit mimeTypeCommentChanged();
emit errorMessageChanged();
}
void FileData::readMetaData()
{
// special file types
// do not sniff mimetype or metadata for these, because these can't really be read
m_mimeType = QMimeType();
if (m_fileInfo.isBlkAtEnd()) {
m_mimeTypeName = "inode/blockdevice";
m_mimeTypeComment = tr("block device");
return;
} else if (m_fileInfo.isChrAtEnd()) {
m_mimeTypeName = "inode/chardevice";
m_mimeTypeComment = tr("character device");
return;
} else if (m_fileInfo.isFifoAtEnd()) {
m_mimeTypeName = "inode/fifo";
m_mimeTypeComment = tr("pipe");
return;
} else if (m_fileInfo.isSocketAtEnd()) {
m_mimeTypeName = "inode/socket";
m_mimeTypeComment = tr("socket");
return;
} else if (m_fileInfo.isDirAtEnd()) {
m_mimeTypeName = "inode/directory";
m_mimeTypeComment = tr("folder");
return;
}
if (!m_fileInfo.isFileAtEnd()) { // something strange
m_mimeTypeName = "application/octet-stream";
m_mimeTypeComment = tr("unknown");
return;
}
// normal files - match content to find mimetype, which means that the file is read
QMimeDatabase db;
QString filename = m_fileInfo.isSymLink() ? m_fileInfo.symLinkTarget() :
m_fileInfo.absoluteFilePath();
m_mimeType = db.mimeTypeForFile(filename);
m_mimeTypeName = m_mimeType.name();
m_mimeTypeComment = m_mimeType.comment();
// read metadata for images
// store in m_metaData, first char is priority, then label:value
if (m_mimeType.name() == "image/jpeg" || m_mimeType.name() == "image/png" ||
m_mimeType.name() == "image/gif") {
// read size
QImageReader reader(m_file);
QSize s = reader.size();
if (s.width() >= 0 && s.height() >= 0) {
QString ar = calculateAspectRatio(s.width(), s.height());
m_metaData.append("0" + tr("Image Size") +
QString(":%1 x %2 %3").arg(s.width()).arg(s.height()).arg(ar));
}
// read comments
QStringList textKeys = reader.textKeys();
foreach (QString key, textKeys) {
QString value = reader.text(key);
m_metaData.append("9"+key+":"+value);
}
}
}
const int aspectWidths[] = { 16, 4, 3, 5, 5, -1 };
const int aspectHeights[] = { 9, 3, 2, 3, 4, -1 };
QString FileData::calculateAspectRatio(int width, int height) const
{
// Jolla Camera almost 16:9 aspect ratio
if ((width == 3264 && height == 1840) || (height == 1840 && width == 3264)) {
return QString("(16:9)");
}
int i = 0;
while (aspectWidths[i] != -1) {
if (width * aspectWidths[i] == height * aspectHeights[i] ||
height * aspectWidths[i] == width * aspectHeights[i]) {
return QString("(%1:%2)").arg(aspectWidths[i]).arg(aspectHeights[i]);
}
++i;
}
return QString();
}
#ifndef FILEDATA_H
#define FILEDATA_H
#include <QObject>
#include <QDir>
#include <QVariantList>
#include <QMimeType>
#include <QSize>
#include "statfileinfo.h"
/**
* @brief The FileData class provides info about one file.
*/
class FileData : public QObject
{
Q_OBJECT
Q_PROPERTY(QString file READ file() WRITE setFile(QString) NOTIFY fileChanged())
Q_PROPERTY(bool isDir READ isDir() NOTIFY isDirChanged())
Q_PROPERTY(bool isSymLink READ isSymLink() NOTIFY isSymLinkChanged())
Q_PROPERTY(QString kind READ kind() NOTIFY kindChanged())
Q_PROPERTY(QString icon READ icon() NOTIFY iconChanged())
Q_PROPERTY(QString permissions READ permissions() NOTIFY permissionsChanged())
Q_PROPERTY(QString owner READ owner() NOTIFY ownerChanged())
Q_PROPERTY(QString group READ group() NOTIFY groupChanged())
Q_PROPERTY(QString size READ size() NOTIFY sizeChanged())
Q_PROPERTY(QString modified READ modified() NOTIFY modifiedChanged())
Q_PROPERTY(QString created READ created() NOTIFY createdChanged())
Q_PROPERTY(QString absolutePath READ absolutePath() NOTIFY absolutePathChanged())
Q_PROPERTY(QString name READ name() NOTIFY nameChanged())
Q_PROPERTY(QString suffix READ suffix() NOTIFY suffixChanged())
Q_PROPERTY(QString symLinkTarget READ symLinkTarget() NOTIFY symLinkTargetChanged())
Q_PROPERTY(bool isSymLinkBroken READ isSymLinkBroken() NOTIFY isSymLinkBrokenChanged())
Q_PROPERTY(QString mimeType READ mimeType() NOTIFY mimeTypeChanged())
Q_PROPERTY(QString mimeTypeComment READ mimeTypeComment() NOTIFY mimeTypeCommentChanged())
Q_PROPERTY(QStringList metaData READ metaData() NOTIFY metaDataChanged())
Q_PROPERTY(QString errorMessage READ errorMessage() NOTIFY errorMessageChanged())
public:
explicit FileData(QObject *parent = 0);
~FileData();
// property accessors
QString file() const { return m_file; }
void setFile(QString file);
bool isDir() const { return m_fileInfo.isDirAtEnd(); }
bool isSymLink() const { return m_fileInfo.isSymLink(); }
QString kind() const { return m_fileInfo.kind(); }
QString icon() const;
QString permissions() const;
QString owner() const;
QString group() const;
QString size() const;
QString modified() const;
QString created() const;
QString absolutePath() const;
QString name() const { return m_fileInfo.fileName(); }
QString suffix() const { return m_fileInfo.suffix().toLower(); }
QString symLinkTarget() const { return m_fileInfo.symLinkTarget(); }
bool isSymLinkBroken() const { return m_fileInfo.isSymLinkBroken(); }
QString mimeType() const { return m_mimeTypeName; }
QString mimeTypeComment() const { return m_mimeTypeComment; }
QStringList metaData() const { return m_metaData; }
QString errorMessage() const { return m_errorMessage; }
// methods accessible from QML
Q_INVOKABLE void refresh();
Q_INVOKABLE bool mimeTypeInherits(QString parentMimeType);
Q_INVOKABLE bool isSafeToOpen() const { return m_fileInfo.isSafeToRead(); }
signals:
void fileChanged();
void isDirChanged();
void isSymLinkChanged();
void kindChanged();
void iconChanged();
void permissionsChanged();
void ownerChanged();
void groupChanged();
void sizeChanged();
void modifiedChanged();
void createdChanged();
void nameChanged();
void suffixChanged();
void absolutePathChanged();
void symLinkTargetChanged();
void isSymLinkBrokenChanged();
void metaDataChanged();
void mimeTypeChanged();
void mimeTypeCommentChanged();
void errorMessageChanged();
private:
void readInfo();
void readMetaData();
QString calculateAspectRatio(int width, int height) const;
QStringList readExifData(QString filename);
QString m_file;
StatFileInfo m_fileInfo;
QMimeType m_mimeType;
QString m_mimeTypeName;
QString m_mimeTypeComment;
QStringList m_metaData;
QString m_errorMessage;
};
#endif // FILEDATA_H
This diff is collapsed.
#ifndef FILEMODEL_H
#define FILEMODEL_H
#include <QAbstractListModel>
#include <QDir>
#include <QFileSystemWatcher>
#include "statfileinfo.h"
/**
* @brief The FileModel class can be used as a model in a ListView to display a list of files
* in the current directory. It has methods to change the current directory and to access
* file info.
* It also actively monitors the directory. If the directory changes, then the model is
* updated automatically if active is true. If active is false, then the directory is
* updated when active becomes true.
*/
class FileModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString dir READ dir() WRITE setDir(QString) NOTIFY dirChanged())
Q_PROPERTY(int fileCount READ fileCount() NOTIFY fileCountChanged())
Q_PROPERTY(QString errorMessage READ errorMessage() NOTIFY errorMessageChanged())
Q_PROPERTY(bool active READ active() WRITE setActive(bool) NOTIFY activeChanged())
Q_PROPERTY(int selectedFileCount READ selectedFileCount() NOTIFY selectedFileCountChanged())
public:
explicit FileModel(QObject *parent = 0);
~FileModel();
// methods needed by ListView
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
// property accessors
QString dir() const { return m_dir; }
void setDir(QString dir);
int fileCount() const;
QString errorMessage() const;
bool active() const { return m_active; }
void setActive(bool active);
int selectedFileCount() const { return m_selectedFileCount; }
// methods accessible from QML
Q_INVOKABLE QString appendPath(QString dirName);
Q_INVOKABLE QString parentPath();
Q_INVOKABLE QString fileNameAt(int fileIndex);
// file selection
Q_INVOKABLE void toggleSelectedFile(int fileIndex);
Q_INVOKABLE void clearSelectedFiles();
Q_INVOKABLE void selectAllFiles();
Q_INVOKABLE QStringList selectedFiles() const;
public slots:
// reads the directory and inserts/removes model items as needed
Q_INVOKABLE void refresh();
// reads the directory and sets all model items
Q_INVOKABLE void refreshFull();
signals:
void dirChanged();
void fileCountChanged();
void errorMessageChanged();
void activeChanged();
void selectedFileCountChanged();
private slots:
void readDirectory();
private:
void recountSelectedFiles();
void readAllEntries();
void refreshEntries();
void clearModel();
bool filesContains(const QList<StatFileInfo> &files, const StatFileInfo &fileData) const;
QString m_dir;
QList<StatFileInfo> m_files;
int m_selectedFileCount;
QString m_errorMessage;
bool m_active;
bool m_dirty;
QFileSystemWatcher *m_watcher;
};
#endif // FILEMODEL_H
#include "fileworker.h"
#include <QDateTime>
#include "globals.h"
FileWorker::FileWorker(QObject *parent) :
QThread(parent),
m_mode(DeleteMode),
m_cancelled(KeepRunning),
m_progress(0)
{
}
FileWorker::~FileWorker()
{
}
void FileWorker::startDeleteFiles(QStringList filenames)
{
if (isRunning()) {
emit errorOccurred(tr("File operation already in progress"), "");
return;
}