1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2015-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 
20 #include "listservermodel.h"
21 #include "../libshared/util/paths.h"
22 
23 #include <QImage>
24 #include <QBuffer>
25 #include <QCryptographicHash>
26 #include <QFile>
27 #include <QDir>
28 #include <QSettings>
29 #include <QSslSocket>
30 
31 namespace sessionlisting {
32 
ListServerModel(bool includeReadOnly,QObject * parent)33 ListServerModel::ListServerModel(bool includeReadOnly, QObject *parent)
34 	: QAbstractListModel(parent)
35 {
36 	loadServers(includeReadOnly);
37 }
38 
rowCount(const QModelIndex & parent) const39 int ListServerModel::rowCount(const QModelIndex &parent) const
40 {
41 	if(parent.isValid())
42 		return 0;
43 	return m_servers.size();
44 }
45 
data(const QModelIndex & index,int role) const46 QVariant ListServerModel::data(const QModelIndex &index, int role) const
47 {
48 	if(index.isValid() && index.row() >= 0 && index.row() < m_servers.size()) {
49 		const ListServer &srv = m_servers.at(index.row());
50 		switch(role) {
51 		case Qt::DisplayRole: return srv.name;
52 		case Qt::DecorationRole: return srv.icon;
53 		case Qt::ToolTipRole: return QString("%1\n\n%2\n\nURL: %3\nRead-only: %4, public: %5, private: %6").arg(
54 			srv.name,
55 			srv.description,
56 			srv.url,
57 			srv.readonly ? QStringLiteral("yes") : QStringLiteral("no"),
58 			srv.publicListings ? QStringLiteral("yes") : QStringLiteral("no"),
59 			srv.privateListings ? QStringLiteral("yes") : QStringLiteral("no")
60 			);
61 		case UrlRole: return srv.url;
62 		case DescriptionRole: return srv.description;
63 		case PublicListRole: return srv.publicListings;
64 		case PrivateListRole: return srv.privateListings;
65 		}
66 	}
67 	return QVariant();
68 }
69 
moveRow(const QModelIndex & sourceParent,int sourceRow,const QModelIndex & destinationParent,int destinationChild)70 bool ListServerModel::moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild)
71 {
72 	if(sourceParent.isValid() || destinationParent.isValid())
73 		return false;
74 
75 	if(sourceRow < 0 || sourceRow >= m_servers.size())
76 		return false;
77 
78 	const int destinationRow = qBound(0, destinationChild, m_servers.size()-1);
79 
80 	if(sourceRow == destinationRow)
81 		return false;
82 
83 	beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationRow + (destinationRow > sourceRow));
84 	m_servers.move(sourceRow, destinationRow);
85 	endMoveRows();
86 	return true;
87 }
88 
removeRows(int row,int count,const QModelIndex & parent)89 bool ListServerModel::removeRows(int row, int count, const QModelIndex &parent)
90 {
91 	if(parent.isValid())
92 		return false;
93 
94 	if(row<0 || count<=0 || row+count>m_servers.count())
95 		return false;
96 
97 	beginRemoveRows(parent, row, row+count-1);
98 	while(count-->0) m_servers.removeAt(row);
99 	endRemoveRows();
100 	return true;
101 }
102 
addServer(const QString & name,const QString & url,const QString & description,bool readonly,bool pub,bool priv)103 bool ListServerModel::addServer(const QString &name, const QString &url, const QString &description, bool readonly, bool pub, bool priv)
104 {
105 	const ListServer lstSrv {
106 			QIcon(),
107 			QString(),
108 			name,
109 			url,
110 			description,
111 			readonly,
112 			pub,
113 			priv
114 		};
115 
116 	// First check if a server with this URL already exists
117 	for(int i=0;i<m_servers.size();++i) {
118 		if(m_servers.at(i).url == url) {
119 			// Already exists! Update data instead of adding
120 			m_servers[i] = lstSrv;
121 			const auto idx = index(i);
122 			emit dataChanged(idx, idx);
123 			return false;
124 		}
125 	}
126 
127 	// No? Then add it
128 	beginInsertRows(QModelIndex(), m_servers.size(), m_servers.size());
129 	m_servers << lstSrv;
130 	endInsertRows();
131 	return true;
132 }
133 
removeServer(const QString & url)134 bool ListServerModel::removeServer(const QString &url)
135 {
136 	// Find the server
137 	int found = -1;
138 	for(int i=0;i<m_servers.size();++i) {
139 		if(m_servers[i].url == url) {
140 			found = i;
141 			break;
142 		}
143 	}
144 
145 	if(found < 0)
146 		return false;
147 
148 	removeRow(found);
149 
150 	return true;
151 }
152 
setFavicon(const QString & url,const QImage & icon)153 void ListServerModel::setFavicon(const QString &url, const QImage &icon)
154 {
155 	// Find the server
156 	for(int i=0;i<m_servers.size();++i) {
157 		ListServer &s = m_servers[i];
158 		if(s.url != url)
159 			continue;
160 
161 		// Make sure the icon is not huge
162 		QImage scaledIcon;
163 
164 		if(icon.width() > 128 || icon.height() > 128)
165 			scaledIcon = icon.scaled(QSize(128, 128), Qt::KeepAspectRatio);
166 		else
167 			scaledIcon = icon;
168 
169 		// Serialize icon in PNG format
170 		QByteArray data;
171 		QBuffer buffer(&data);
172 		buffer.open(QIODevice::WriteOnly);
173 		scaledIcon.save(&buffer, "PNG");
174 
175 		// Generate file name
176 		QCryptographicHash hash(QCryptographicHash::Sha1);
177 		hash.addData(data);
178 
179 		s.iconName = hash.result().toHex() + ".png";
180 		s.icon = QIcon(QPixmap::fromImage(scaledIcon));
181 
182 		// Save file to disk
183 		QFile f(utils::paths::writablePath("favicons/", s.iconName));
184 		if(!f.open(QIODevice::WriteOnly)) {
185 			qWarning() << "Unable to open" << f.fileName() << f.errorString();
186 		} else {
187 			f.write(data);
188 			f.close();
189 		}
190 	}
191 }
192 
listServers(bool includeReadOnly)193 QVector<ListServer> ListServerModel::listServers(bool includeReadOnly)
194 {
195 	QVector<ListServer> list;
196 
197 	QSettings cfg;
198 	const QString iconPath = utils::paths::writablePath("favicons/");
199 	const int count = cfg.beginReadArray("listservers");
200 	for(int i=0;i<count;++i) {
201 		cfg.setArrayIndex(i);
202 		ListServer ls {
203 			QIcon(),
204 			cfg.value("icon").toString(),
205 			cfg.value("name").toString(),
206 			cfg.value("url").toString(),
207 			cfg.value("description").toString(),
208 			cfg.value("readonly").toBool(),
209 			cfg.value("public", true).toBool(),
210 			cfg.value("private", true).toBool()
211 		};
212 
213 		if(ls.readonly && !includeReadOnly)
214 			continue;
215 
216 		if(ls.iconName == "drawpile")
217 			ls.icon = QIcon(":/icons/drawpile.png");
218 		else if(!ls.iconName.isEmpty())
219 			ls.icon = QIcon(iconPath + ls.iconName);
220 
221 		list << ls;
222 	}
223 	cfg.endArray();
224 	return list;
225 }
226 
loadServers(bool includeReadOnly)227 void ListServerModel::loadServers(bool includeReadOnly)
228 {
229 	beginResetModel();
230 	m_servers = listServers(includeReadOnly);
231 	endResetModel();
232 }
233 
234 
saveServers() const235 void ListServerModel::saveServers() const
236 {
237 	QSettings cfg;
238 	cfg.beginWriteArray("listservers", m_servers.size());
239 	for(int i=0;i<m_servers.size();++i) {
240 		const ListServer &s = m_servers.at(i);
241 		cfg.setArrayIndex(i);
242 
243 		cfg.setValue("name", s.name);
244 		cfg.setValue("url", s.url);
245 		cfg.setValue("description", s.description);
246 		cfg.setValue("icon", s.iconName);
247 		cfg.setValue("readonly", s.readonly);
248 		cfg.setValue("public", s.publicListings);
249 		cfg.setValue("private", s.privateListings);
250 	}
251 	cfg.endArray();
252 }
253 
254 }
255