1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2018-2019 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "avatarlistmodel.h"
20 #include "../libshared/util/paths.h"
21 
22 #include <QDir>
23 #include <QBuffer>
24 #include <QCryptographicHash>
25 
AvatarListModel(QObject * parent)26 AvatarListModel::AvatarListModel(QObject *parent)
27 	: QAbstractListModel(parent)
28 {
29 }
30 
rowCount(const QModelIndex & parent) const31 int AvatarListModel::rowCount(const QModelIndex &parent) const
32 {
33 	if(parent.isValid())
34 		return 0;
35 	return m_avatars.size();
36 }
37 
data(const QModelIndex & index,int role) const38 QVariant AvatarListModel::data(const QModelIndex &index, int role) const
39 {
40 	if(!index.isValid() || index.row() < 0 || index.row() >= m_avatars.size())
41 		return QVariant();
42 
43 	const Avatar &a = m_avatars.at(index.row());
44 
45 	switch(role) {
46 	case Qt::DisplayRole: return a.icon.isNull() ? a.filename : QString();
47 	case Qt::DecorationRole: return a.icon;
48 	case FilenameRole: return a.filename;
49 	}
50 
51 	return QVariant();
52 }
53 
flags(const QModelIndex & index) const54 Qt::ItemFlags AvatarListModel::flags(const QModelIndex &index) const
55 {
56 	Qt::ItemFlags f = Qt::NoItemFlags;
57 	if(index.isValid() && index.row() >= 0 && index.row() < m_avatars.size()) {
58 		f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
59 	}
60 	return f;
61 }
62 
removeRows(int row,int count,const QModelIndex & parent)63 bool AvatarListModel::removeRows(int row, int count, const QModelIndex &parent)
64 {
65 	if(row<0 || row + count > m_avatars.size() || parent.isValid())
66 		return false;
67 
68 	beginRemoveRows(QModelIndex(), row, row+count-1);
69 	while(count--) {
70 		if(!m_avatars[row].filename.isEmpty())
71 			m_deletions << m_avatars[row].filename;
72 		m_avatars.remove(row);
73 	}
74 	endRemoveRows();
75 	return true;
76 }
77 
getAvatar(const QString & filename) const78 QModelIndex AvatarListModel::getAvatar(const QString &filename) const
79 {
80 	for(int i=0;i<m_avatars.size();++i) {
81 		if(m_avatars.at(i).filename == filename)
82 			return index(i);
83 	}
84 
85 	return QModelIndex();
86 }
87 
addAvatar(const QPixmap & icon)88 void AvatarListModel::addAvatar(const QPixmap &icon)
89 {
90 	beginInsertRows(QModelIndex(), m_avatars.size(), m_avatars.size());
91 	m_avatars << Avatar {
92 		icon,
93 		QString()
94 	};
95 	endInsertRows();
96 }
97 
loadAvatars(bool includeBlank)98 void AvatarListModel::loadAvatars(bool includeBlank)
99 {
100 	QVector<Avatar> avatars;
101 
102 	if(includeBlank) {
103 		avatars << Avatar {
104 			QPixmap(),
105 			tr("No avatar")
106 		};
107 	}
108 
109 	QDir dir = utils::paths::writablePath("avatars");
110 	const QStringList files = dir.entryList(QStringList() << "*.png", QDir::Files|QDir::Readable);
111 
112 	for(const QString &filename : files) {
113 		avatars << Avatar {
114 			QPixmap(dir.filePath(filename), "PNG"),
115 			filename
116 		};
117 	}
118 
119 	beginResetModel();
120 	m_avatars = avatars;
121 	m_deletions.clear();
122 	endResetModel();
123 }
124 
commit()125 bool AvatarListModel::commit()
126 {
127 	QDir dir = utils::paths::writablePath("avatars", ".");
128 
129 	// Commit deletions
130 	for(const QString &filename : m_deletions) {
131 		dir.remove(filename);
132 	}
133 	m_deletions.clear();
134 
135 	// Save newly added avatars
136 	for(Avatar &a : m_avatars) {
137 		if(!a.icon.isNull() && a.filename.isEmpty()) {
138 			Q_ASSERT(!a.icon.isNull());
139 
140 			QBuffer buf;
141 			buf.open(QBuffer::ReadWrite);
142 
143 			a.icon.save(&buf, "PNG");
144 			buf.seek(0);
145 
146 			QCryptographicHash hash(QCryptographicHash::Md5);
147 			hash.addData(&buf);
148 
149 			const QString filename = QString::fromUtf8(hash.result().toHex() + ".png");
150 			QFile f { dir.filePath(filename) };
151 			if(!f.open(QFile::WriteOnly)) {
152 				qWarning("Couldn't save %s", qPrintable(filename));
153 				return false;
154 			}
155 			f.write(buf.data());
156 			f.close();
157 
158 			a.filename = filename;
159 		}
160 	}
161 	return true;
162 }
163