Subversion Repositories tpanel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 andreas 1
/*
21 andreas 2
 * Copyright (C) 2020, 2021 by Andreas Theofilu <andreas@theosys.at>
2 andreas 3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
18
#include "tconfig.h"
21 andreas 19
 
2 andreas 20
#include <fstream>
21
#include <vector>
22
#include <iterator>
23
#include <unistd.h>
21 andreas 24
#include <sys/stat.h>
25
#include <sys/types.h>
26
 
2 andreas 27
#include "terror.h"
28
 
29
using std::string;
30
using std::ifstream;
21 andreas 31
using std::ofstream;
2 andreas 32
using std::fstream;
33
using std::vector;
21 andreas 34
using std::cout;
35
using std::cerr;
36
using std::endl;
2 andreas 37
 
21 andreas 38
/**
39
 * @struct SETTINGS
40
 * @brief The SETTINGS struct bundles the configuration options.
41
 *
42
 * This structure contains variables for all possible configuration options.
43
 * It is used by the class TConfig. Through this class it's possible to
44
 * access all configuration options.
45
 */
2 andreas 46
struct SETTINGS
47
{
21 andreas 48
    string pname{"tpanel"};     //!< Name of the program (default "tpanel")
49
    string path;                //!< The path where the configuration file is located
50
    string name;                //!< The name of the configuration file
51
    string project;             //!< The path where the original project files are located
52
    string server;              //!< The name or IP address of the server to connect
53
    int system{0};              //!< The number of the AMX system
54
    int port{0};                //!< The port number
55
    int ID{0};                  //!< the panel ID (a number starting by 10000)
56
    string ptype;               //!< The type of the panel (android, ipad, iphone, ...)
57
    string version;             //!< The "firmware" version
58
    string logFile;             //!< Optional path and name of a logfile
59
    string logLevel;            //!< The log level(s).
60
    bool longformat{false};     //!< TRUE = long format
61
    bool noBanner{false};       //!< Startup without showing a banner on the command line.
62
    bool certCheck{false};      //!< TRUE = Check certificate for SSL connection
2 andreas 63
};
64
 
65
typedef struct SETTINGS settings_t;
21 andreas 66
static settings_t localSettings;    //!< Global defines settings used in class TConfig.
2 andreas 67
 
21 andreas 68
/**
69
 * @brief TConfig::TConfig constructor
70
 *
71
 * @param path  A path and name of a configuration file.
72
 */
2 andreas 73
TConfig::TConfig(const std::string& path)
74
	: mPath(path)
75
{
76
	if (findConfig())
77
		readConfig();
78
}
79
 
21 andreas 80
/**
81
 * @brief TConfig::setProgName Sets the name of the application.
82
 * @param pname The name of the application.
83
 */
2 andreas 84
void TConfig::setProgName(const std::string& pname)
85
{
86
	localSettings.pname = pname;
87
}
88
 
21 andreas 89
/**
90
 * @brief TConfig::getProgName Retrieves the prevously stored application name.
91
 * @return The name of this application.
92
 */
2 andreas 93
std::string & TConfig::getProgName()
94
{
95
	return localSettings.pname;
96
}
97
 
21 andreas 98
/**
99
 * @brief TConfig::getChannel returns the AMX channel to use.
100
 *
101
 * The AMX channels an AMX panel can use start at 10000. This method returns
102
 * the channel number found in the configuration file. If there was no
103
 * channel defination found, it returns the default channel 10001.
104
 *
105
 * @return The AMX channel number to use.
106
 */
2 andreas 107
int TConfig::getChannel()
108
{
109
	return localSettings.ID;
110
}
111
 
21 andreas 112
/**
113
 * @brief TConfig::getConfigFileName returns the name of the configuration file.
114
 *
115
 * @return The name of the configuration file.
116
 */
2 andreas 117
std::string& TConfig::getConfigFileName()
118
{
119
	return localSettings.name;
120
}
121
 
21 andreas 122
/**
123
 * @brief TConfig::getConfigPath returns the path configuration file.
124
 *
125
 * The path was defined on the command line or found by searching the standard
126
 * directories.
127
 *
128
 * @return The path of the configuration file.
129
 */
2 andreas 130
std::string& TConfig::getConfigPath()
131
{
132
	return localSettings.path;
133
}
134
 
21 andreas 135
/**
136
 * @brief TConfig::getController returns the network name or IP address of the AMX controller.
137
 *
138
 * The network name or the IP address was read from the configuration file.
139
 *
140
 * @return The network name of the AMX controller.
141
 */
2 andreas 142
std::string& TConfig::getController()
143
{
144
	return localSettings.server;
145
}
146
 
21 andreas 147
/**
148
 * @brief TConfig::getSystem return the AMX system number.
149
 *
150
 * This number was read from the configuration file. If there was no system
151
 * number defined in the configuration file, then the default number 0 is
152
 * returned.
153
 *
154
 * @return The AMX system number.
155
 */
11 andreas 156
int TConfig::getSystem()
157
{
158
    return localSettings.system;
159
}
160
 
21 andreas 161
/**
162
 * @brief TConfig::getFirmVersion returns the version of the firmware.
163
 *
164
 * This option was read from the configuration file. There can be any version
165
 * number defined. But you must keep in mind, that the AMX controller may not
166
 * accept any number. If there was no version number defined, the standard
167
 * version number 1.0 is returned.
168
 *
169
 * @return The firmware version of this panel.
170
 */
2 andreas 171
std::string& TConfig::getFirmVersion()
172
{
173
	return localSettings.version;
174
}
175
 
21 andreas 176
/**
177
 * @brief TConfig::getLogFile the path and name of a logfile.
178
 *
179
 * If there is a logfile name defined in the configuration file, it is used
180
 * to write messages there. It depends on the _log level_ what is logged.
181
 *
182
 * @return The path and name of a logfile.
183
 */
2 andreas 184
std::string& TConfig::getLogFile()
185
{
186
	return localSettings.logFile;
187
}
188
 
21 andreas 189
/**
190
 * @brief TConfig::getLogLevel returns the defined log level.
191
 *
192
 * The loglevel can be one of the following values:
193
 *
194
 *     NONE         Logs nothing (default for Android)
195
 *     INFO         Logs only informations
196
 *     WARNING      Logs only warnings
197
 *     ERROR        Logs onls errors
198
 *     TRACE        Logs only trace messages
199
 *     DEBUG        Logs only debug messages
200
 *     PROTOCOL     Logs only INFO and ERROR (default if NOT Android)
201
 *     ALL          Logs everything
202
 *
203
 * All log levels can be combined by concatenating them with the | symbol.
204
 *
205
 * @return The log level(s) as a string.
206
 */
2 andreas 207
string& TConfig::getLogLevel()
208
{
209
	return localSettings.logLevel;
210
}
211
 
21 andreas 212
/**
213
 * @brief TConfig::getPanelType the AMX type name of the panel.
214
 *
215
 * The type name of the panel is defined in the configuration file. If this
216
 * option was not defined, the default panel _android_ is returned.
217
 *
218
 * @return The type name of the panel.
219
 */
2 andreas 220
std::string& TConfig::getPanelType()
221
{
222
	return localSettings.ptype;
223
}
224
 
21 andreas 225
/**
226
 * @brief TConfig::getPort returnes the AMX port number to connect to.
227
 *
228
 * The port number can be defined in the configuration file. If there is no
229
 * configuration the default number 1319 is returned.
230
 *
231
 * @return The AMX network port number.
232
 */
2 andreas 233
int TConfig::getPort()
234
{
235
	return localSettings.port;
236
}
237
 
21 andreas 238
/**
239
 * @brief TConfig::getProjectPath returns the path to the AMX configuration files.
240
 *
241
 * The path was read from the configuration file. This path contains all the
242
 * files needed to display the elements of the surface.
243
 *
244
 * @return The path to the AMX configuration files.
245
 */
2 andreas 246
std::string& TConfig::getProjectPath()
247
{
248
	return localSettings.project;
249
}
250
 
21 andreas 251
/**
252
 * @brief TConfig::isLongFormat defines the format in the logfile.
253
 *
254
 * If this returns `true` the format in the logfile is a long format. This
255
 * means, that in front of each message is an additional timestamp.
256
 *
257
 * @return `true` = long format, `false` = short format (default).
258
 */
2 andreas 259
bool TConfig::isLongFormat()
260
{
261
	return localSettings.longformat;
262
}
263
 
21 andreas 264
/**
265
 * @brief TConfig::showBanner defines whether the banner should be showed or not.
266
 *
267
 * If this method returns `false` the banner on startup is not displayed.
268
 *
269
 * @return `true` = display the banner (default), `false` = show no banner.
270
 */
2 andreas 271
bool TConfig::showBanner()
272
{
273
	return !localSettings.noBanner;
274
}
275
 
21 andreas 276
/**
277
 * @brief TConfig::certCheck check the certificate if the connection is encrypted.
278
 *
279
 * Currently not implemented!
280
 *
281
 * @return `true` = check the certificate, `false` = accept any certificate (default).
282
 */
283
bool TConfig::certCheck()
284
{
285
    return localSettings.certCheck;
286
}
287
 
288
/**
289
 * @brief TConfig::findConfig search for the location of the configuration file.
290
 *
291
 * If there was no configuration file given on the command line, this method
292
 * searches for a configuration file on a few standard directories. This are:
293
 *
294
 *     /etc/tpanel.conf
295
 *     /etc/tpanel/tpanel.conf
296
 *     /usr/etc/tpanel.conf
297
 *     $HOME/.tpanel.conf
298
 *
299
 * On macOS the following additional directories are searched:
300
 *
301
 *     /opt/local/etc/tpanel.conf
302
 *     /opt/local/etc/tpanel/tpanel.conf
303
 *     /opt/local/usr/etc/tpanel.conf
304
 *
305
 * @return On success `true`, otherwise `false`.
306
 */
2 andreas 307
bool TConfig::findConfig()
308
{
309
	char *HOME = nullptr;
310
	string sFileName;
311
 
312
	if (!mPath.empty())
313
	{
314
		size_t pos = mPath.find_last_of("/");
315
 
316
		if (pos != string::npos)
317
		{
318
			localSettings.path = mPath.substr(0, pos);
319
			localSettings.name = mPath.substr(pos+1);
320
			mCFile = mPath;
321
			return !mCFile.empty();
322
		}
323
 
324
		localSettings.name = mPath;
325
		mCFile = mPath;
326
		return !mCFile.empty();
327
	}
328
 
329
	localSettings.name = "tpanel.conf";
21 andreas 330
#ifdef __ANDROID__
331
    char *androidData = getenv("ANDROID_DATA");
332
    char *androidRoot = getenv("ANDROID_ROOT");
2 andreas 333
 
21 andreas 334
    if (!androidData)
335
    {
336
        TError::setError();
337
        cerr << "Missing environment variable ANDROID_DATA!" << endl;
338
        TError::setErrorMsg(TERRERROR, "Missing environment variable ANDROID_DATA!");
339
        return false;
340
    }
341
 
342
    if (!androidRoot)
343
    {
344
        TError::setError();
345
        cerr << "Missing environment variable ANDROID_ROOT!" << endl;
346
        TError::setErrorMsg(TERRERROR, "Missing environment variable ANDROID_ROOT!");
347
        return false;
348
    }
349
 
350
    localSettings.path = androidData;
351
    mRoot = androidRoot;
352
 
353
    if (access(androidData, F_OK) == -1)    // Does the configuration file exist?
354
    {                                       // No, than create it and also the directory structure
355
        try
356
        {
357
            sFileName = localSettings.path + "/" + localSettings.name;
358
            ofstream cfg(sFileName);
359
 
360
            string content = "LogFile=" + localSettings.path + "/tpanel.log\n";
361
            content += "LogLevel=PROTOCOL\n";
362
            content += "ProjectPath=" + localSettings.path + "/tpanel\n";
363
            content += "LongFormat=false\n";
364
            content += "Address=0.0.0.0\n";
365
            content += "Port=1319\n";
366
            content += "Channel=10001\n";
367
            content += "PanelType=Android\n";
368
            content += "Firmware=1.0.0\n";
369
            cfg.write(content.c_str(), content.size());
370
            cfg.close();
371
 
372
            string path = localSettings.path + "/tpanel";
373
            mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
374
            string syspath = path + "/fonts";
375
            mkdir(syspath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
376
            syspath = path + "/images";
377
            mkdir(syspath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
378
            syspath = path + "/sounds";
379
            mkdir(syspath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
380
            syspath = path + "/__system";
381
            mkdir(syspath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
382
        }
383
        catch (std::exception& e)
384
        {
385
            cerr << "Error: " << e.what() << endl;
386
            TError::setErrorMsg(TERRERROR, string("Error: ") + e.what());
387
            return false;
388
        }
389
    }
390
#else
2 andreas 391
	if (!(HOME = getenv("HOME")))
392
		std::cerr << "TConfig::findConfig: No environment variable HOME!" << std::endl;
393
 
394
	if (!access("/etc/tpanel.conf", R_OK))
395
	{
396
		sFileName = "/etc/tpanel.conf";
397
		localSettings.path = "/etc";
398
	}
399
	else if (!access("/etc/tpanel/tpanel.conf", R_OK))
400
	{
401
		sFileName = "/etc/tpanel/tpanel.conf";
402
		localSettings.path = "/etc/tpanel";
403
	}
404
	else if (!access("/usr/etc/tpanel.conf", R_OK))
405
	{
406
		sFileName = "/usr/etc/tpanel.conf";
407
		localSettings.path = "/usr/etc";
408
	}
409
#ifdef __APPLE__
410
	if (!access("/opt/local/etc/tpanel.conf", R_OK))
411
	{
412
		sFileName = "/opt/local/etc/tpanel.conf";
413
		localSettings.path = "/opt/local/etc";
414
	}
415
	else if (!access("/opt/local/etc/tpanel/tpanel.conf", R_OK))
416
	{
417
		sFileName = "/opt/local/etc/tpanel/tpanel.conf";
418
		localSettings.path = "/opt/local/etc/tpanel";
419
	}
420
	else if (!access("/opt/local/usr/etc/tpanel.conf", R_OK))
421
	{
422
		sFileName = "/opt/local/usr/etc/tpanel.conf";
423
		localSettings.path = "/opt/local/usr/etc";
424
	}
425
#endif
426
	else if (HOME)
427
	{
428
		sFileName = HOME;
429
		sFileName += "/.amxpanel.conf";
430
		localSettings.path = HOME;
431
 
432
		if (access(sFileName.data(), R_OK))
433
		{
434
			std::cerr << "TConfig::findConfig: Can't find any configuration file!" << std::endl;
435
			TError::setError();
436
			sFileName.clear();
437
			localSettings.name.clear();
438
		}
439
		else
440
			localSettings.name = ".amxpanel.conf";
441
	}
442
	else
443
	{
444
		sFileName.clear();
445
		localSettings.name.clear();
446
		localSettings.path.clear();
447
	}
21 andreas 448
#endif
2 andreas 449
	mCFile = sFileName;
450
	return !sFileName.empty();
451
}
452
 
21 andreas 453
/**
454
 * @brief TConfig::readConfig reads a config file.
455
 *
456
 * This method reads a config file and stores the known options into the
457
 * struct localSettings.
458
 *
459
 * @return `true` on success.\n
460
 * Returns `false` on error and sets the internal error.
461
 */
2 andreas 462
bool TConfig::readConfig()
463
{
464
	ifstream fs;
465
 
466
	// First initialize the defaults
467
	localSettings.ID = 0;
468
	localSettings.port = 1397;
469
	localSettings.ptype = "android";
470
	localSettings.version = "1.0";
471
	localSettings.longformat = false;
472
 
473
	// Now get the settings from file
474
	try
475
	{
476
		fs.open(mCFile.c_str(), fstream::in);
477
	}
478
	catch (const fstream::failure e)
479
	{
480
		std::cerr << "TConfig::readConfig: Error on file " << mCFile << ": " << e.what() << std::endl;
481
		TError::setError();
482
		return false;
483
	}
484
 
485
	for (string line; getline(fs, line);)
486
	{
487
		size_t pos;
488
 
489
		if ((pos = line.find("#")) != string::npos)
490
		{
491
			if (pos == 0)
492
				line.clear();
493
			else
494
				line = line.substr(0, pos);
495
		}
496
 
497
		if (line.empty() || line.find("=") == string::npos)
498
			continue;
499
 
500
		vector<string> parts = split(line, "=", true);
501
 
502
		if (parts.size() == 2)
503
		{
504
			string left = parts[0];
505
			string right = ltrim(parts[1]);
506
 
507
			if (caseCompare(left, "PORT") == 0 && !right.empty())
508
				localSettings.port = atoi(right.c_str());
509
			else if (caseCompare(left, "LOGFILE") == 0 && !right.empty())
510
			{
511
				localSettings.logFile = right;
512
				TStreamError::setLogFile(right);
513
			}
514
			else if (caseCompare(left, "LOGLEVEL") == 0 && !right.empty())
515
			{
516
				TStreamError::setLogLevel(right);
517
				localSettings.logLevel = right;
518
			}
519
			else if (caseCompare(left, "ProjectPath") == 0 && !right.empty())
520
				localSettings.project = right;
11 andreas 521
            else if (caseCompare(left, "System") == 0 && !right.empty())
522
                localSettings.system = atoi(right.c_str());
523
            else if (caseCompare(left, "PanelType") == 0 && !right.empty())
2 andreas 524
				localSettings.ptype = right;
525
			else if (caseCompare(left, "Address") == 0 && !right.empty())
526
				localSettings.server = right;
527
			else if (caseCompare(left, "Firmware") == 0 && !right.empty())
528
				localSettings.version = right;
529
			else if (caseCompare(left, "LongFormat") == 0 && !right.empty())
530
			{
531
				if (caseCompare(right, "1") == 0 || caseCompare(right, "yes") == 0 ||
532
					caseCompare(right, "true") == 0)
533
					localSettings.longformat = true;
534
				else
535
					localSettings.longformat = false;
536
			}
537
			else if (caseCompare(left, "NoBanner") == 0 && !right.empty())
538
			{
539
				if (caseCompare(right, "1") == 0 || caseCompare(right, "yes") == 0 ||
540
					caseCompare(right, "true") == 0)
541
					localSettings.noBanner = true;
542
				else
543
					localSettings.noBanner = false;
544
			}
21 andreas 545
			else if (caseCompare(left, "CertCheck") == 0 && !right.empty())
546
            {
547
                if (caseCompare(right, "1") == 0 || caseCompare(right, "yes") == 0 ||
548
                    caseCompare(right, "true") == 0 || caseCompare(right, "on") == 0)
549
                    localSettings.certCheck = true;
550
                else
551
                    localSettings.certCheck = false;
552
            }
553
            else if (caseCompare(left, "Channel") == 0 && !right.empty())
2 andreas 554
			{
555
				localSettings.ID = atoi(right.c_str());
556
 
557
				if (localSettings.ID < 10000 || localSettings.ID >= 11000)
558
				{
559
					std::cerr << "TConfig::readConfig: Invalid port number " << right << std::endl;
560
					localSettings.ID = 0;
561
				}
562
			}
563
		}
564
	}
565
 
566
	fs.close();
567
 
568
	if (TStreamError::checkFilter(LOG_DEBUG))
569
	{
570
		MSG_INFO("Selected Parameters:");
571
		MSG_INFO("    Path to cfg.: " << localSettings.path);
572
		MSG_INFO("    Name of cfg.: " << localSettings.name);
573
		MSG_INFO("    Logfile:      " << localSettings.logFile);
574
		MSG_INFO("    LogLevel:     " << localSettings.logLevel);
575
		MSG_INFO("    Long format:  " << (localSettings.longformat ? "YES" : "NO"));
576
		MSG_INFO("    Project path: " << localSettings.project);
577
		MSG_INFO("    Show banner:  " << (localSettings.noBanner ? "NO" : "YES"));
578
		MSG_INFO("    Controller:   " << localSettings.server);
579
		MSG_INFO("    Port:         " << localSettings.port);
580
		MSG_INFO("    Channel:      " << localSettings.ID);
581
		MSG_INFO("    Panel type:   " << localSettings.ptype);
582
		MSG_INFO("    Firmware ver. " << localSettings.version);
583
	}
584
 
585
	return true;
586
}
587
 
21 andreas 588
/**
589
 * @brief TConfig::split splitts a string into parts.
590
 *
591
 * The method splitts a string into parts separated by \p seps. It puts the
592
 * parts into a vector array.
593
 *
594
 * @param str           The string to split
595
 * @param seps          The separator(s)
596
 * @param trimEmpty     `true` = trum the parts.
597
 *
598
 * @return A vector array containing the parts of the string \p str.
599
 */
2 andreas 600
vector<string> TConfig::split(const string& str, const string& seps, const bool trimEmpty)
601
{
602
	size_t pos = 0, mark = 0;
603
	vector<string> parts;
604
 
605
	for (auto it = str.begin(); it != str.end(); ++it)
606
	{
607
		for (auto sepIt = seps.begin(); sepIt != seps.end(); ++sepIt)
608
		{
609
			if (*it == *sepIt)
610
			{
611
				size_t len = pos - mark;
612
				parts.push_back(str.substr(mark, len));
613
				mark = pos + 1;
614
				break;
615
			}
616
		}
617
 
618
		pos++;
619
	}
620
 
621
	parts.push_back(str.substr(mark));
622
 
623
	if (trimEmpty)
624
	{
625
		vector<string> nparts;
626
 
627
		for (auto it = parts.begin(); it != parts.end(); ++it)
628
		{
629
			if (it->empty())
630
				continue;
631
 
632
			nparts.push_back(*it);
633
		}
634
 
635
		return nparts;
636
	}
637
 
638
	return parts;
639
}
640
 
21 andreas 641
/**
642
 * @brief TConfig::caseCompare compares 2 strings
643
 *
644
 * This method compares 2 strings case insensitive. This means that it ignores
645
 * the case of the letters. For example:
646
 *
647
 *     BLAME
648
 *     blame
649
 *     Blame
650
 *
651
 * are all the same and would return 0, which means _equal_.
652
 *
653
 * @param str1  1st string to compare
654
 * @param str2  2nd string to compare
655
 *
656
 * @return 0 if the strings are equal\n
657
 * less than 0 if the byte of \p str1 is bigger than the byte of \p str2\n
658
 * grater than 0 if the byte of \p str1 is smaller than the byte of \p str2.
659
 */
2 andreas 660
int TConfig::caseCompare(const string& str1, const string& str2)
661
{
662
	size_t i = 0;
663
 
664
	if (str1.length() != str2.length())
665
		return ((str1.length() < str2.length()) ? -1 : 1);
666
 
667
	for (auto it = str1.begin(); it != str1.end(); ++it)
668
	{
669
		if (tolower(*it) != tolower(str2.at(i)))
670
			return (int)(*it - str2.at(i));
671
 
672
		i++;
673
	}
674
 
675
	return 0;
676
}