Use the key-file backend for writes in tests for non-writeable backends.
[platform/upstream/folks.git] / folks / debug.vala
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  *
4  * This library is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 2.1 of the License, or
7  * (at your option) any later version.
8  *
9  * This library 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 Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors:
18  *       Philip Withnall <philip.withnall@collabora.co.uk>
19  */
20
21 using GLib;
22 using Gee;
23
24 /* We have to declare our own wrapping of g_log so that it doesn't have the
25  * [Diagnostics] attribute, which would cause valac to add the Vala source file
26  * name to the message format, which we don't want. This is used in
27  * Debug.print_line(). */
28 [PrintfFormat]
29 private extern void g_log (string? log_domain,
30     LogLevelFlags log_level,
31     string format,
32     ...);
33
34 /**
35  * Manage debug output and status reporting for all folks objects. All GLib
36  * debug logging calls are passed through a log handler in this class, which
37  * allows debug domains to be outputted according to whether they've been
38  * enabled by being passed to {@link Debug.dup}.
39  *
40  * @since 0.5.1
41  */
42 public class Folks.Debug : Object
43 {
44   private enum Domains {
45     /* Zero is used for "no debug spew" */
46     CORE = 1 << 0,
47     TELEPATHY_BACKEND = 1 << 1,
48     KEY_FILE_BACKEND = 1 << 2
49   }
50
51   private static weak Debug _instance; /* needs to be locked when accessed */
52   private HashSet<string> _domains; /* needs to be locked when accessed */
53   private bool _all = false; /* needs _domains to be locked when accessed */
54
55   /* The current indentation level, in spaces */
56   private uint _indentation = 0;
57   private string _indentation_string = "";
58
59   private bool _colour_enabled = true;
60
61   /*
62    * Whether colour output is enabled. If true, debug output may include
63    * terminal colour escape codes. Disabled by the environment variable
64    * FOLKS_DEBUG_NO_COLOUR being set to anything except “0”.
65    *
66    * This property is thread-safe.
67    *
68    * @since 0.5.1
69    */
70   public bool colour_enabled
71     {
72       get
73         {
74           lock (this._colour_enabled)
75             {
76               return this._colour_enabled;
77             }
78         }
79
80       set
81         {
82           lock (this._colour_enabled)
83             {
84               this._colour_enabled = value;
85             }
86         }
87     }
88
89   private bool _debug_output_enabled = true;
90
91   /**
92    * Whether debug output is enabled. This is orthogonal to the set of enabled
93    * debug domains; filtering of debug output as a whole is done after filtering
94    * by enabled domains.
95    *
96    * @since 0.5.1
97    */
98   public bool debug_output_enabled
99     {
100       get
101         {
102           lock (this._debug_output_enabled)
103             {
104               return this._debug_output_enabled;
105             }
106         }
107
108       set
109         {
110           lock (this._debug_output_enabled)
111             {
112               this._debug_output_enabled = value;
113             }
114         }
115     }
116
117   /**
118    * Signal emitted in the main thread whenever objects should print their
119    * current status. All significant objects in the library should connect
120    * to this and print their current status in some suitable format when it's
121    * emitted.
122    *
123    * Client processes should emit this signal by calling
124    * {@link Debug.emit_print_status}.
125    *
126    * @since 0.5.1
127    */
128   public signal void print_status ();
129
130   /**
131    * Log domain for the status messages logged as a result of calling
132    * {@link Debug.emit_print_status}.
133    *
134    * This could be used in conjunction with a log handler to redirect the
135    * status information to a debug window or log file, for example.
136    *
137    * @since 0.5.1
138    */
139   public const string STATUS_LOG_DOMAIN = "folks-status";
140
141   private void _print_status_log_handler_cb (string? log_domain,
142       LogLevelFlags log_levels,
143       string message)
144     {
145       /* Print directly to stdout without any adornments */
146       GLib.stdout.printf ("%s\n", message);
147     }
148
149   private void _log_handler_cb (string? log_domain,
150       LogLevelFlags log_levels,
151       string message)
152     {
153       if (this.debug_output_enabled == false)
154         {
155           /* Don't output anything if debug output is disabled, even for
156            * enabled debug domains. */
157           return;
158         }
159
160       /* Otherwise, pass through to the default log handler */
161       Log.default_handler (log_domain, log_levels, message);
162     }
163
164   /* turn off debug output for the given domain unless it was in the FOLKS_DEBUG
165    * environment variable (or 'all' was set) */
166   internal void _register_domain (string domain)
167     {
168       lock (this._domains)
169         {
170           if (this._all || this._domains.contains (domain.down ()))
171             {
172               Log.set_handler (domain, LogLevelFlags.LEVEL_MASK,
173                   this._log_handler_cb);
174               return;
175             }
176         }
177
178       /* Install a log handler which will blackhole the log message.
179        * Other log messages will be printed out by the default log handler. */
180       Log.set_handler (domain, LogLevelFlags.LEVEL_DEBUG,
181           (domain_arg, flags, message) => {});
182     }
183
184   /**
185    * Create or return the singleton {@link Debug} class instance.
186    * If the instance doesn't exist already, it will be created with no debug
187    * domains enabled.
188    *
189    * This function is thread-safe.
190    *
191    * @return  Singleton {@link Debug} instance
192    * @since 0.5.1
193    */
194   public static Debug dup ()
195     {
196       lock (Debug._instance)
197         {
198           var retval = Debug._instance;
199
200           if (retval == null)
201             {
202               /* use an intermediate variable to force a strong reference */
203               retval = new Debug ();
204               Debug._instance = retval;
205             }
206
207           return retval;
208         }
209     }
210
211   /**
212    * Create or return the singleton {@link Debug} class instance.
213    * If the instance doesn't exist already, it will be created with the given
214    * set of debug domains enabled. Otherwise, the existing instance will have
215    * its set of enabled domains changed to the provided set.
216    *
217    * @param debug_flags A comma-separated list of debug domains to enable, or
218    * null to disable debug output
219    * @param colour_enabled Whether debug output should be coloured using
220    * terminal escape sequences
221    * @return Singleton {@link Debug} instance
222    * @since 0.5.1
223    */
224   public static Debug dup_with_flags (string? debug_flags,
225       bool colour_enabled)
226     {
227       var retval = Debug.dup ();
228
229       lock (retval._domains)
230         {
231           retval._all = false;
232           retval._domains = new HashSet<string> (str_hash, str_equal);
233
234           if (debug_flags != null && debug_flags != "")
235             {
236               var domains_split = debug_flags.split (",");
237               foreach (var domain in domains_split)
238                 {
239                   var domain_lower = domain.down ();
240
241                   if (GLib.strcmp (domain_lower, "all") == 0)
242                     retval._all = true;
243                   else
244                     retval._domains.add (domain_lower);
245                 }
246             }
247         }
248
249       retval.colour_enabled = colour_enabled;
250
251       return retval;
252     }
253
254   private Debug ()
255     {
256       /* Private constructor for singleton */
257
258       /* Install a log handler for log messages emitted as a result of
259        * Debug.print-status being emitted. */
260       Log.set_handler (Debug.STATUS_LOG_DOMAIN, LogLevelFlags.LEVEL_MASK,
261           this._print_status_log_handler_cb);
262     }
263
264   ~Debug ()
265     {
266       /* Manually clear the singleton _instance */
267       lock (Debug._instance)
268         {
269           Debug._instance = null;
270         }
271     }
272
273   /**
274    * Causes all significant objects in the library to print their current
275    * status to standard output, obeying the options set on this
276    * {@link Debug} instance for colouring and other formatting.
277    *
278    * @since 0.5.1
279    */
280   public void emit_print_status ()
281     {
282       print ("Dumping status information…\n");
283       this.print_status ();
284     }
285
286   /**
287    * Increment the indentation level used when printing output through the
288    * object.
289    *
290    * This is intended to be used by backend libraries only.
291    *
292    * @since 0.5.1
293    */
294   public void indent ()
295     {
296       /* We indent in increments of two spaces */
297       this._indentation++;
298       this._indentation_string = string.nfill (this._indentation * 2, ' ');
299     }
300
301   /**
302    * Decrement the indentation level used when printing output through the
303    * object.
304    *
305    * This is intended to be used by backend libraries only.
306    *
307    * @since 0.5.1
308    */
309   public void unindent ()
310     {
311       this._indentation--;
312       this._indentation_string = string.nfill (this._indentation * 2, ' ');
313     }
314
315   /**
316    * Print a debug line with the current indentation level for the specified
317    * debug domain.
318    *
319    * This is intended to be used by backend libraries only.
320    *
321    * @param domain The debug domain name
322    * @param level A set of log level flags for the message
323    * @param format A printf-style format string for the heading
324    * @param ... Arguments for the format string
325    * @since 0.5.1
326    */
327   [PrintfFormat ()]
328   public void print_line (string domain,
329       LogLevelFlags level,
330       string format,
331       ...)
332     {
333       /* FIXME: store the va_list temporarily to work around bgo#638308 */
334       var valist = va_list ();
335       string output = format.vprintf (valist);
336       g_log (domain, level, "%s%s", this._indentation_string, output);
337     }
338
339   /**
340    * Print a debug line as a heading. It will be coloured according to the
341    * current indentation level so that different levels of headings stand out.
342    *
343    * This is intended to be used by backend libraries only.
344    *
345    * @param domain The debug domain name
346    * @param level A set of log level flags for the message
347    * @param format A printf-style format string for the heading
348    * @param ... Arguments for the format string
349    * @since 0.5.1
350    */
351   [PrintfFormat ()]
352   public void print_heading (string domain,
353       LogLevelFlags level,
354       string format,
355       ...)
356     {
357       /* Colour the heading according to the current indentation level.
358        * ANSI terminal colour codes. */
359       const int[] heading_colours =
360         {
361           31, /* red */
362           32, /* green */
363           34 /* blue */
364         };
365
366       var wrapper_format = "%s";
367       if (this.colour_enabled == true)
368         {
369           var indentation =
370               this._indentation.clamp (0, heading_colours.length - 1);
371           wrapper_format =
372               "\033[1;%im%%s\033[0m".printf (heading_colours[indentation]);
373         }
374
375       /* FIXME: store the va_list temporarily to work around bgo#638308 */
376       var valist = va_list ();
377       string output = format.vprintf (valist);
378       this.print_line (domain, level, wrapper_format, output);
379     }
380
381   /*
382    * Format a potentially null string for printing; if the string is null,
383    * “(null)” will be outputted. If coloured output is enabled, this output
384    * will be coloured brown. */
385   private string _format_nullable_string (string? input)
386     {
387       if (this.colour_enabled == true && input == null)
388         {
389           return "\033[1;36m(null)\033[0m"; /* cyan */
390         }
391       else if (input == null)
392         {
393           return "(null)";
394         }
395
396       return input;
397     }
398
399   struct KeyValuePair
400     {
401       string key;
402       string? val;
403     }
404
405   /**
406    * Print a set of key–value pairs in a table. The width of the key column is
407    * automatically set to the width of the longest key. The keys and values
408    * must be provided as a null-delimited list of alternating key–value varargs.
409    * Values may be null but keys may not.
410    *
411    * This is intended to be used by backend libraries only.
412    *
413    * The table will be printed at the current indentation level plus one.
414    *
415    * @param domain The debug domain name
416    * @param level A set of log level flags for the message
417    * @param ... Alternating keys and values, terminated with null
418    * @since 0.5.1
419    */
420   public void print_key_value_pairs (string domain,
421       LogLevelFlags level,
422       ...)
423     {
424       var valist = va_list ();
425       KeyValuePair[] lines = {};
426       uint max_key_length = 0;
427
428       /* Read in the arguments and calculate the longest key for alignment
429        * purposes */
430       while (true)
431         {
432           string? key = valist.arg ();
433           if (key == null)
434             {
435               break;
436             }
437
438           string? val = valist.arg ();
439
440           /* Keep track of the longest key we've seen */
441           max_key_length = uint.max (key.length, max_key_length);
442
443           lines += KeyValuePair ()
444             {
445               key = key,
446               val = val
447             };
448         }
449
450       this.indent ();
451
452       /* Print out the lines */
453       foreach (var line in lines)
454         {
455           var padding = string.nfill (max_key_length - line.key.length, ' ');
456           this.print_line (domain, level, "%s: %s%s", line.key, padding,
457               this._format_nullable_string (line.val));
458         }
459
460       this.unindent ();
461     }
462 }