aboutsummaryrefslogtreecommitdiffstats
path: root/src/kvilib/ext/KviMediaManager.cpp
blob: a84c92c54a69413f1f94c11a8e8103ffeffe7c2a (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
//=============================================================================
//
//   File : KviMediaManager.cpp
//   Creation date : Wed Dec 29 2010 00:37:56 CEST by Elvio basello
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2010 Elvio Basello (hellvis69 at gmail dot com)
//
//   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 2
//   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, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

//#define _KVI_DEBUG_CHECK_RANGE_

#include "KviMediaManager.h"
#include "KviConfigurationFile.h"
#include "KviFile.h"
#include "KviFileUtils.h"
#include "KviLocale.h"
#include "KviRegExp.h"
#include "kvi_debug.h"

#include <QDir>

#include <sys/types.h>
#include <sys/stat.h>

#if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW)
#include <unistd.h>
#include "KviMemory.h"
#endif

#ifndef S_ISDIR
#define S_ISDIR(__f) (__f & _S_IFDIR)
#endif

#ifndef S_ISFIFO
#define S_ISFIFO(__f) (__f & _S_IFIFO)
#endif

#ifndef S_ISREG
#define S_ISREG(__f) (__f & _S_IFREG)
#endif

#ifndef S_ISCHR
#define S_ISCHR(__f) (__f & _S_IFCHR)
#endif

#if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW)
#include <dirent.h>
#else
#include "KviMemory.h"
#endif

KviMediaManager::KviMediaManager()
    : KviMutex()
{
	m_pMediaTypeList = new KviPointerList<KviMediaType>;
	m_pMediaTypeList->setAutoDelete(true);
}

KviMediaManager::~KviMediaManager()
{
	delete m_pMediaTypeList;
}

KviMediaType * KviMediaManager::findMediaTypeByIanaType(const char * ianaType)
{
	KVI_ASSERT(locked());
	for(KviMediaType * mt = m_pMediaTypeList->first(); mt; mt = m_pMediaTypeList->next())
	{
		if(kvi_strEqualCI(mt->szIanaType.ptr(), ianaType))
			return mt;
	}

	return nullptr;
}

KviMediaType * KviMediaManager::findMediaTypeByFileMask(const char * filemask)
{
	KVI_ASSERT(locked());
	for(KviMediaType * mt = m_pMediaTypeList->first(); mt; mt = m_pMediaTypeList->next())
	{
		// FIXME: #warning "Should this be case sensitive ?"
		if(kvi_strEqualCI(mt->szFileMask.ptr(), filemask))
			return mt;
	}

	return nullptr;
}

void KviMediaManager::copyMediaType(KviMediaType * dst, KviMediaType * src)
{
	dst->szFileMask = src->szFileMask;
	dst->szMagicBytes = src->szMagicBytes;
	dst->szIanaType = src->szIanaType;
	dst->szDescription = src->szDescription;
	dst->szSavePath = src->szSavePath;
	dst->szCommandline = src->szCommandline;
	dst->szRemoteExecCommandline = src->szRemoteExecCommandline;
	dst->szIcon = src->szIcon;
}

void KviMediaManager::insertMediaType(KviMediaType * m)
{
	KVI_ASSERT(locked());
	int iWildCount = m->szFileMask.occurrences('*');
	int iNonWildCount = m->szFileMask.len() - iWildCount;

	// The masks with no wildcards go first in the list
	// then we insert the ones with more non-wild chars

	int index = 0;
	for(KviMediaType * mt = m_pMediaTypeList->first(); mt; mt = m_pMediaTypeList->next())
	{
		if(iWildCount)
		{
			// the new mask has wildcards... if the current one has none, skip it
			int iWildCountExisting = mt->szFileMask.occurrences('*');
			if(iWildCountExisting)
			{
				// the one in the list has wildcards too...
				// the ones with more non-wild chars go first...
				int iNonWildCountExisting = mt->szFileMask.len() - iWildCountExisting;
				if(iNonWildCountExisting < iNonWildCount)
				{
					// ok...the new one has more non-wildcards, insert
					m_pMediaTypeList->insert(index, m);
					return;
				}
				else
				{
					if(iNonWildCount == iNonWildCountExisting)
					{
						// the same number of non-wildcards
						// let the number of wildcards decide (it will be eventually equal)
						if(iWildCount < iWildCountExisting)
						{
							// the new one has less wildcards... goes first
							m_pMediaTypeList->insert(index, m);
							return;
						} // else the same number of wildcards and non-wildcards...skip
					}     // else the existing one has more non-wildcards...skip
				}
			} // else the current has no wildcards...skip
		}
		else
		{
			// the new mask has no wildcards....
			if(mt->szFileMask.contains('*'))
			{
				// current one has wildcards...insert
				m_pMediaTypeList->insert(index, m);
				return;
			}
			// the current one has no wildcards...
			// the longer masks go first....
			if(mt->szFileMask.len() < m->szFileMask.len())
			{
				// the current one is shorter than the new one...insert
				m_pMediaTypeList->insert(index, m);
				return;
			} // else current one is longer...skip
		}
		index++;
	}
	m_pMediaTypeList->append(m);
}

KviMediaType * KviMediaManager::findMediaType(const char * filename, bool bCheckMagic)
{
	// FIXME: This should be ported at least to QString....
	KVI_ASSERT(locked());

	KviCString szFullPath = filename;
	if(!KviFileUtils::isAbsolutePath(szFullPath.ptr()))
	{
		KviCString tmp = QDir::currentPath();
		tmp.ensureLastCharIs('/');
		szFullPath.prepend(tmp);
	}

	KviCString szFile = filename;
	szFile.cutToLast('/', true);

// first of all, lstat() the file
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
	struct _stat st;
	if(_stat(szFullPath.ptr(), &st) != 0)
#else
	struct stat st;
	if(lstat(szFullPath.ptr(), &st) != 0)
#endif
	{
		//qDebug("Problems while stating file %s",szFullPath.ptr());
		// We do just the pattern matching
		// it's better to avoid magic checks
		// if the file is a device, we would be blocked while attempting to read data
		return findMediaTypeForRegularFile(szFullPath.ptr(), szFile.ptr(), false);
	}
	else
	{
// If it is a link, stat() the link target
#if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW)
		if(S_ISLNK(st.st_mode))
		{
			if(stat(szFullPath.ptr(), &st) != 0)
			{
				qDebug("Problems while stating() target for link %s", szFullPath.ptr());
				// Same as above
				return findMediaTypeForRegularFile(szFullPath.ptr(), szFile.ptr(), false);
			}
		}
#endif
	}

#if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW)
	if(S_ISSOCK(st.st_mode))
	{
		// Socket : return default media type
		KviMediaType * mtd = findMediaTypeByIanaType("inode/socket");
		if(!mtd)
		{
			// Add it
			mtd = new KviMediaType;
			mtd->szIanaType = "inode/socket";
			mtd->szDescription = __tr("Socket");
			mtd->szIcon = "kvi_dbsocket.png"; // hardcoded ?
			insertMediaType(mtd);
		}
		return mtd;
	}
#endif

	if(S_ISFIFO(st.st_mode))
	{
		// Fifo: return default media type
		KviMediaType * mtd = findMediaTypeByIanaType("inode/fifo");
		if(!mtd)
		{
			// Add it
			mtd = new KviMediaType;
			mtd->szIanaType = "inode/fifo";
			mtd->szDescription = __tr("Fifo");
			mtd->szIcon = "kvi_dbfifo.png"; // hardcoded ?
			insertMediaType(mtd);
		}
		return mtd;
	}

#if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW)
	if(S_ISBLK(st.st_mode))
	{
		// Block device: return default media type
		KviMediaType * mtd = findMediaTypeByIanaType("inode/blockdevice");
		if(!mtd)
		{
			// Add it
			mtd = new KviMediaType;
			mtd->szIanaType = "inode/blockdevice";
			mtd->szDescription = __tr("Block device");
			mtd->szIcon = "kvi_dbblockdevice.png"; // hardcoded ?
			insertMediaType(mtd);
		}
		return mtd;
	}
#endif

	if(S_ISCHR(st.st_mode))
	{
		// Char device: return default media type
		KviMediaType * mtd = findMediaTypeByIanaType("inode/chardevice");
		if(!mtd)
		{
			// Add it
			mtd = new KviMediaType;
			mtd->szIanaType = "inode/chardevice";
			mtd->szDescription = __tr("Char device");
			mtd->szIcon = "kvi_dbchardevice.png"; // hardcoded ?
			insertMediaType(mtd);
		}
		return mtd;
	}

	// this is a regular file (or at least it looks like one)
	return findMediaTypeForRegularFile(szFullPath.ptr(), szFile.ptr(), bCheckMagic);
}

KviMediaType * KviMediaManager::findMediaTypeForRegularFile(const char * szFullPath, const char * szFileName, bool bCheckMagic)
{
	char buffer[17];
	int len = 0;

	if(bCheckMagic)
	{
		QString szTmp = QString::fromUtf8(szFullPath);
		KviFile f(szTmp);
		if(f.open(QFile::ReadOnly))
		{
			len = f.read(buffer, 16);
			if(len > 0)
			{
				buffer[len] = '\0';
				if(buffer[0] == 0)
					len = 0; // no way to match it
			}
			f.close();
		}
	}

	for(KviMediaType * m = m_pMediaTypeList->first(); m; m = m_pMediaTypeList->next())
	{
		// FIXME: #warning "Should this be case sensitive ?"
		if(kvi_matchWildExpr(m->szFileMask.ptr(), szFileName))
		{
			if(len && m->szMagicBytes.hasData())
			{
				KviRegExp re(m->szMagicBytes.ptr());
				// It looks like they can't decide the name for this function :D
				// ... well, maybe the latest choice is the best one.
				if(re.indexIn(buffer) > -1)
					return m; // matched!

				// else magic failed...not a match
			}
			else
				return m; // matched! (no magic check)
		}
	}

	KviMediaType * mtd = findMediaTypeByIanaType("application/octet-stream");
	if(!mtd)
	{
		// Add it
		mtd = new KviMediaType;
		mtd->szIanaType = "application/octet-stream";
		mtd->szDescription = __tr("Octet stream (unknown)");
		mtd->szCommandline = "editor.open $0";
		mtd->szIcon = "kvi_dbunknown.png"; // hardcoded ?
		insertMediaType(mtd);
	}

	return mtd;
}

// FIXME : add proper default handlers for Windows
// e.g. a KVS function which calls https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx

#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
static KviDefaultMediaType g_defMediaTypes[] = {
	{ "*.jpg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run cmd.exe /c start $0" },
	{ "*.jpeg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run cmd.exe /c start $0" },
	{ "*.png", "", "image/png", "PNG image", "run cmd.exe /c start $0" },
	{ "*.mp3", "", "audio/mpeg", "MPEG audio", "run cmd.exe /c start $0" },
	{ "*.gif", "", "image/gif", "GIF image", "run cmd.exe /c start $0" },
	{ "*.mpeg", "", "video/mpeg", "MPEG video", "run cmd.exe /c start $0" },
	{ "*.zip", "^PK\\0003\\0004", "application/zip", "ZIP archive", "run cmd.exe /c start $0" },
	{ "*.exe", "", "application/x-executable-file", "Executable file", "run $0" },
	{ "*.wav", "", "audio/wav", "Wave audio", "run cmd.exe /c start $0" },
	{ nullptr, nullptr, nullptr, nullptr, nullptr }
};
#else
#ifdef COMPILE_ON_MAC
static KviDefaultMediaType g_defMediaTypes[] = {
	{ "*.jpg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run open $0" },
	{ "*.jpeg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run open $0" },
	{ "*.png", "", "image/png", "PNG image", "run open $0" },
	{ "*.mp3", "", "audio/mpeg", "MPEG audio", "run open $0" },
	{ "*.gif", "", "image/gif", "GIF image", "run open $0" },
	{ "*.mpeg", "", "video/mpeg", "MPEG video", "run open $0" },
	{ "*.zip", "^PK\\0003\\0004", "application/zip", "ZIP archive", "run open $0" },
	{ "*.tar.gz", "", "application/x-gzip", "GZipped tarball", "run open $0" },
	{ "*.tar.bz2", "", "application/x-bzip2", "BZipped tarball", "run open $0" },
	{ "*.tgz", "", "application/x-gzip", "GZipped tarball", "run open $0" },
	{ "*.wav", "", "audio/wav", "Wave audio", "run open $0" },
	{ nullptr, nullptr, nullptr, nullptr, nullptr }
};
#else
static KviDefaultMediaType g_defMediaTypes[] = {
	{ "*.jpg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run xdg-open $0" },
	{ "*.jpeg", "^\\0330\\0377", "image/jpeg", "JPEG image", "run xdg-open $0" },
	{ "*.png", "", "image/png", "PNG image", "run xdg-open $0" },
	{ "*.mp3", "", "audio/mpeg", "MPEG audio", "run xdg-open $0" },
	{ "*.gif", "", "image/gif", "GIF image", "run xdg-open $0" },
	{ "*.mpeg", "", "video/mpeg", "MPEG video", "run xdg-open $0" },
	{ "*.zip", "^PK\\0003\\0004", "application/zip", "ZIP archive", "run xdg-open $0" },
	{ "*.tar.gz", "", "application/x-gzip", "GZipped tarball", "run xdg-open $0" },
	{ "*.tar.bz2", "", "application/x-bzip2", "BZipped tarball", "run xdg-open $0" },
	{ "*.tgz", "", "application/x-gzip", "GZipped tarball", "run xdg-open $0" },
	{ "*.wav", "", "audio/wav", "Wave audio", "run xdg-open $0" },
	{ nullptr, nullptr, nullptr, nullptr, nullptr }
};
#endif
#endif

void KviMediaManager::load(const QString & filename)
{
	KVI_ASSERT(locked());

	KviConfigurationFile cfg(filename, KviConfigurationFile::Read);
	cfg.setGroup("MediaTypes");
	unsigned int nEntries = cfg.readUIntEntry("NEntries", 0);
	for(unsigned int i = 0; i < nEntries; i++)
	{
		KviMediaType * m = new KviMediaType;
		KviCString tmp(KviCString::Format, "%dFileMask", i);
		m->szFileMask = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dMagicBytes", i);
		m->szMagicBytes = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dIanaType", i);
		m->szIanaType = cfg.readEntry(tmp.ptr(), "application/unknown");
		tmp.sprintf("%dDescription", i);
		m->szDescription = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dSavePath", i);
		m->szSavePath = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dCommandline", i);
		m->szCommandline = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dRemoteExecCommandline", i);
		m->szRemoteExecCommandline = cfg.readEntry(tmp.ptr(), "");
		tmp.sprintf("%dIcon", i);
		m->szIcon = cfg.readEntry(tmp.ptr(), "");
		insertMediaType(m);
	}

	for(int u = 0; g_defMediaTypes[u].filemask; u++)
	{
		if(!findMediaTypeByFileMask(g_defMediaTypes[u].filemask))
		{
			KviMediaType * m = new KviMediaType;
			m->szFileMask = g_defMediaTypes[u].filemask;
			m->szMagicBytes = g_defMediaTypes[u].magicbytes;
			m->szIanaType = g_defMediaTypes[u].ianatype;
			m->szDescription = g_defMediaTypes[u].description;
			m->szCommandline = g_defMediaTypes[u].commandline;
			insertMediaType(m);
		}
	}
}

void KviMediaManager::save(const QString & filename)
{
	KVI_ASSERT(locked());
	KviConfigurationFile cfg(filename, KviConfigurationFile::Write);

	cfg.clear();
	cfg.setGroup("MediaTypes");
	cfg.writeEntry("NEntries", m_pMediaTypeList->count());
	int index = 0;
	for(KviMediaType * m = m_pMediaTypeList->first(); m; m = m_pMediaTypeList->next())
	{
		KviCString tmp(KviCString::Format, "%dFileMask", index);
		cfg.writeEntry(tmp.ptr(), m->szFileMask.ptr());
		tmp.sprintf("%dMagicBytes", index);
		cfg.writeEntry(tmp.ptr(), m->szMagicBytes.ptr());
		tmp.sprintf("%dIanaType", index);
		cfg.writeEntry(tmp.ptr(), m->szIanaType.ptr());
		tmp.sprintf("%dDescription", index);
		cfg.writeEntry(tmp.ptr(), m->szDescription.ptr());
		tmp.sprintf("%dSavePath", index);
		cfg.writeEntry(tmp.ptr(), m->szSavePath.ptr());
		tmp.sprintf("%dCommandline", index);
		cfg.writeEntry(tmp.ptr(), m->szCommandline.ptr());
		tmp.sprintf("%dRemoteExecCommandline", index);
		cfg.writeEntry(tmp.ptr(), m->szRemoteExecCommandline.ptr());
		tmp.sprintf("%dIcon", index);
		cfg.writeEntry(tmp.ptr(), m->szIcon.ptr());
		++index;
	}
}