1/*
2 * Copyright 2015, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "Conditions.h"
8
9#include <stdio.h>
10
11#include <Entry.h>
12#include <File.h>
13#include <ObjectList.h>
14#include <Message.h>
15#include <Path.h>
16#include <StringList.h>
17
18#include "NetworkWatcher.h"
19#include "Utility.h"
20
21
22class ConditionContainer : public Condition {
23protected:
24								ConditionContainer(const BMessage& args);
25								ConditionContainer();
26
27public:
28			void				AddCondition(Condition* condition);
29
30	virtual	bool				IsConstant(ConditionContext& context) const;
31
32protected:
33			void				AddConditionsToString(BString& string) const;
34
35protected:
36			BObjectList<Condition> fConditions;
37};
38
39
40class AndCondition : public ConditionContainer {
41public:
42								AndCondition(const BMessage& args);
43								AndCondition();
44
45	virtual	bool				Test(ConditionContext& context) const;
46	virtual	BString				ToString() const;
47};
48
49
50class OrCondition : public ConditionContainer {
51public:
52								OrCondition(const BMessage& args);
53
54	virtual	bool				Test(ConditionContext& context) const;
55	virtual	bool				IsConstant(ConditionContext& context) const;
56
57	virtual	BString				ToString() const;
58};
59
60
61class NotCondition : public ConditionContainer {
62public:
63								NotCondition(const BMessage& args);
64								NotCondition();
65
66	virtual	bool				Test(ConditionContext& context) const;
67	virtual	BString				ToString() const;
68};
69
70
71class SafeModeCondition : public Condition {
72public:
73	virtual	bool				Test(ConditionContext& context) const;
74	virtual	bool				IsConstant(ConditionContext& context) const;
75
76	virtual	BString				ToString() const;
77};
78
79
80class ReadOnlyCondition : public Condition {
81public:
82								ReadOnlyCondition(const BMessage& args);
83
84	virtual	bool				Test(ConditionContext& context) const;
85	virtual	bool				IsConstant(ConditionContext& context) const;
86
87	virtual	BString				ToString() const;
88
89private:
90			BString				fPath;
91	mutable	bool				fIsReadOnly;
92	mutable	bool				fTestPerformed;
93};
94
95
96class FileExistsCondition : public Condition {
97public:
98								FileExistsCondition(const BMessage& args);
99
100	virtual	bool				Test(ConditionContext& context) const;
101	virtual	BString				ToString() const;
102
103private:
104			BStringList			fPaths;
105};
106
107
108class NetworkAvailableCondition : public Condition {
109public:
110	virtual	bool				Test(ConditionContext& context) const;
111	virtual	bool				IsConstant(ConditionContext& context) const;
112
113	virtual	BString				ToString() const;
114};
115
116
117class SettingCondition : public Condition {
118public:
119								SettingCondition(const BMessage& args);
120
121	virtual	bool				Test(ConditionContext& context) const;
122
123	virtual	BString				ToString() const;
124
125private:
126			BPath				fPath;
127			BString				fField;
128			BString				fValue;
129};
130
131
132static Condition*
133create_condition(const char* name, const BMessage& args)
134{
135	if (strcmp(name, "and") == 0 && !args.IsEmpty())
136		return new AndCondition(args);
137	if (strcmp(name, "or") == 0 && !args.IsEmpty())
138		return new OrCondition(args);
139	if (strcmp(name, "not") == 0 && !args.IsEmpty())
140		return new NotCondition(args);
141
142	if (strcmp(name, "safemode") == 0)
143		return new SafeModeCondition();
144	if (strcmp(name, "read_only") == 0)
145		return new ReadOnlyCondition(args);
146	if (strcmp(name, "file_exists") == 0)
147		return new FileExistsCondition(args);
148	if (strcmp(name, "network_available") == 0)
149		return new NetworkAvailableCondition();
150	if (strcmp(name, "setting") == 0)
151		return new SettingCondition(args);
152
153	return NULL;
154}
155
156
157// #pragma mark -
158
159
160Condition::Condition()
161{
162}
163
164
165Condition::~Condition()
166{
167}
168
169
170bool
171Condition::IsConstant(ConditionContext& context) const
172{
173	return false;
174}
175
176
177// #pragma mark -
178
179
180ConditionContainer::ConditionContainer(const BMessage& args)
181	:
182	fConditions(10, true)
183{
184	char* name;
185	type_code type;
186	int32 count;
187	for (int32 index = 0; args.GetInfo(B_MESSAGE_TYPE, index, &name, &type,
188			&count) == B_OK; index++) {
189		BMessage message;
190		for (int32 messageIndex = 0; args.FindMessage(name, messageIndex,
191				&message) == B_OK; messageIndex++) {
192			AddCondition(create_condition(name, message));
193		}
194	}
195}
196
197
198ConditionContainer::ConditionContainer()
199	:
200	fConditions(10, true)
201{
202}
203
204
205void
206ConditionContainer::AddCondition(Condition* condition)
207{
208	if (condition != NULL)
209		fConditions.AddItem(condition);
210}
211
212
213/*!	A single constant failing condition makes this constant, too, otherwise,
214	a single non-constant condition makes this non-constant as well.
215*/
216bool
217ConditionContainer::IsConstant(ConditionContext& context) const
218{
219	bool fixed = true;
220	for (int32 index = 0; index < fConditions.CountItems(); index++) {
221		const Condition* condition = fConditions.ItemAt(index);
222		if (condition->IsConstant(context)) {
223			if (!condition->Test(context))
224				return true;
225		} else
226			fixed = false;
227	}
228	return fixed;
229}
230
231
232void
233ConditionContainer::AddConditionsToString(BString& string) const
234{
235	string += "[";
236
237	for (int32 index = 0; index < fConditions.CountItems(); index++) {
238		if (index != 0)
239			string += ", ";
240		string += fConditions.ItemAt(index)->ToString();
241	}
242	string += "]";
243}
244
245
246// #pragma mark - and
247
248
249AndCondition::AndCondition(const BMessage& args)
250	:
251	ConditionContainer(args)
252{
253}
254
255
256AndCondition::AndCondition()
257{
258}
259
260
261bool
262AndCondition::Test(ConditionContext& context) const
263{
264	for (int32 index = 0; index < fConditions.CountItems(); index++) {
265		Condition* condition = fConditions.ItemAt(index);
266		if (!condition->Test(context))
267			return false;
268	}
269	return true;
270}
271
272
273BString
274AndCondition::ToString() const
275{
276	BString string = "and ";
277	ConditionContainer::AddConditionsToString(string);
278	return string;
279}
280
281
282// #pragma mark - or
283
284
285OrCondition::OrCondition(const BMessage& args)
286	:
287	ConditionContainer(args)
288{
289}
290
291
292bool
293OrCondition::Test(ConditionContext& context) const
294{
295	if (fConditions.IsEmpty())
296		return true;
297
298	for (int32 index = 0; index < fConditions.CountItems(); index++) {
299		Condition* condition = fConditions.ItemAt(index);
300		if (condition->Test(context))
301			return true;
302	}
303	return false;
304}
305
306
307/*!	If there is a single succeeding constant condition, this is constant, too.
308	Otherwise, it is non-constant if there is a single non-constant condition.
309*/
310bool
311OrCondition::IsConstant(ConditionContext& context) const
312{
313	bool fixed = true;
314	for (int32 index = 0; index < fConditions.CountItems(); index++) {
315		const Condition* condition = fConditions.ItemAt(index);
316		if (condition->IsConstant(context)) {
317			if (condition->Test(context))
318				return true;
319		} else
320			fixed = false;
321	}
322	return fixed;
323}
324
325
326BString
327OrCondition::ToString() const
328{
329	BString string = "or ";
330	ConditionContainer::AddConditionsToString(string);
331	return string;
332}
333
334
335// #pragma mark - or
336
337
338NotCondition::NotCondition(const BMessage& args)
339	:
340	ConditionContainer(args)
341{
342}
343
344
345NotCondition::NotCondition()
346{
347}
348
349
350bool
351NotCondition::Test(ConditionContext& context) const
352{
353	for (int32 index = 0; index < fConditions.CountItems(); index++) {
354		Condition* condition = fConditions.ItemAt(index);
355		if (condition->Test(context))
356			return false;
357	}
358	return true;
359}
360
361
362BString
363NotCondition::ToString() const
364{
365	BString string = "not ";
366	ConditionContainer::AddConditionsToString(string);
367	return string;
368}
369
370
371// #pragma mark - safemode
372
373
374bool
375SafeModeCondition::Test(ConditionContext& context) const
376{
377	return context.IsSafeMode();
378}
379
380
381bool
382SafeModeCondition::IsConstant(ConditionContext& context) const
383{
384	return true;
385}
386
387
388BString
389SafeModeCondition::ToString() const
390{
391	return "safemode";
392}
393
394
395// #pragma mark - read_only
396
397
398ReadOnlyCondition::ReadOnlyCondition(const BMessage& args)
399	:
400	fPath(args.GetString("args")),
401	fIsReadOnly(false),
402	fTestPerformed(false)
403{
404}
405
406
407bool
408ReadOnlyCondition::Test(ConditionContext& context) const
409{
410	if (fTestPerformed)
411		return fIsReadOnly;
412
413	if (fPath.IsEmpty() || fPath == "/boot")
414		fIsReadOnly = context.BootVolumeIsReadOnly();
415	else
416		fIsReadOnly = Utility::IsReadOnlyVolume(fPath);
417
418	fTestPerformed = true;
419
420	return fIsReadOnly;
421}
422
423
424bool
425ReadOnlyCondition::IsConstant(ConditionContext& context) const
426{
427	return true;
428}
429
430
431BString
432ReadOnlyCondition::ToString() const
433{
434	BString string = "readonly ";
435	string << fPath;
436	return string;
437}
438
439
440// #pragma mark - file_exists
441
442
443FileExistsCondition::FileExistsCondition(const BMessage& args)
444{
445	for (int32 index = 0;
446			const char* path = args.GetString("args", index, NULL); index++) {
447		fPaths.Add(Utility::TranslatePath(path));
448	}
449}
450
451
452bool
453FileExistsCondition::Test(ConditionContext& context) const
454{
455	for (int32 index = 0; index < fPaths.CountStrings(); index++) {
456		BEntry entry;
457		if (entry.SetTo(fPaths.StringAt(index)) != B_OK
458			|| !entry.Exists())
459			return false;
460	}
461	return true;
462}
463
464
465BString
466FileExistsCondition::ToString() const
467{
468	BString string = "file_exists [";
469	for (int32 index = 0; index < fPaths.CountStrings(); index++) {
470		if (index != 0)
471			string << ", ";
472		string << fPaths.StringAt(index);
473	}
474	string += "]";
475	return string;
476}
477
478
479// #pragma mark - network_available
480
481
482bool
483NetworkAvailableCondition::Test(ConditionContext& context) const
484{
485	return NetworkWatcher::NetworkAvailable(false);
486}
487
488
489bool
490NetworkAvailableCondition::IsConstant(ConditionContext& context) const
491{
492	return false;
493}
494
495
496BString
497NetworkAvailableCondition::ToString() const
498{
499	return "network_available";
500}
501
502
503// #pragma mark - setting
504
505
506SettingCondition::SettingCondition(const BMessage& args)
507{
508	fPath.SetTo(Utility::TranslatePath(args.GetString("args", 0, NULL)));
509	fField = args.GetString("args", 1, NULL);
510	fValue = args.GetString("args", 2, NULL);
511}
512
513
514bool
515SettingCondition::Test(ConditionContext& context) const
516{
517	BFile file(fPath.Path(), B_READ_ONLY);
518	if (file.InitCheck() != B_OK)
519		return false;
520
521	BMessage settings;
522	if (settings.Unflatten(&file) == B_OK) {
523		type_code type;
524		int32 count;
525		if (settings.GetInfo(fField, &type, &count) == B_OK) {
526			switch (type) {
527				case B_BOOL_TYPE:
528				{
529					bool value = settings.GetBool(fField);
530					bool expect = fValue.IsEmpty();
531					if (!expect) {
532						expect = fValue == "true" || fValue == "yes"
533							|| fValue == "on" || fValue == "1";
534					}
535					return value == expect;
536				}
537				case B_STRING_TYPE:
538				{
539					BString value = settings.GetString(fField);
540					if (fValue.IsEmpty() && !value.IsEmpty())
541						return true;
542
543					return fValue == value;
544				}
545			}
546		}
547	}
548	// TODO: check for driver settings, too?
549
550	return false;
551}
552
553
554BString
555SettingCondition::ToString() const
556{
557	BString string = "setting file ";
558	string << fPath.Path() << ", field " << fField;
559	if (!fValue.IsEmpty())
560		string << ", value " << fValue;
561
562	return string;
563}
564
565
566// #pragma mark -
567
568
569/*static*/ Condition*
570Conditions::FromMessage(const BMessage& message)
571{
572	return create_condition("and", message);
573}
574
575
576/*static*/ Condition*
577Conditions::AddNotSafeMode(Condition* condition)
578{
579	AndCondition* andCondition = dynamic_cast<AndCondition*>(condition);
580	if (andCondition == NULL)
581		andCondition = new AndCondition();
582	if (andCondition != condition && condition != NULL)
583		andCondition->AddCondition(condition);
584
585	NotCondition* notCondition = new NotCondition();
586	notCondition->AddCondition(new SafeModeCondition());
587
588	andCondition->AddCondition(notCondition);
589	return andCondition;
590}
591