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