commit 24bf5e042230103d55fe6918334fc53d4b9933db Author: a-Sansara Date: Thu Oct 19 02:24:35 2017 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d1d2a6 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# PrayTime + +PrayTime is a small vala program which find timings of Islamic Prayer +and play the adhan at the given time. + +PrayTime use the api on aladhan.com to retriew timings. + +## Prerequisites + +see meson.build + +``` +glib_dep = dependency('glib-2.0') +gobject_dep = dependency('gobject-2.0') +gio_dep = dependency('gio-2.0') +json_dep = dependency('json-glib-1.0') +gstreamer_dep = dependency('gstreamer-1.0') +``` + +## Install + +``` +meson --prefix=/usr ./ build +sudo ninja install -C build +``` + +## Configuration + +``` +[Params] +# see api.aladhan.com +city = Paris +country = FR +method = 3 +latitudeAdjustmentMethod = 3 + +[Volumes] +# you can use same volume for all prayer +default = 1.00 +# or adjust volume to specific prayer +fajr = 0.30 +dhuhr = 0.70 +asr = 0.40 +# maghrib use default volume +maghrib = +isha = 0.40 + +[Adhan] +# you can use same file for all prayer +default = /home/a-sansara/Dev/Vala/4006.mp3 +# or define specific file for each prayer +fajr = /home/a-sansara/Dev/Vala/Adhan-Fajr-Makkah-Sheikh-Ali-Ahmed-Mulla.ogg +dhuhr = /home/a-sansara/Dev/Vala/Filipino-Adhan.ogg +asr = /home/a-sansara/Dev/Vala/azan.ogg +maghrib = /home/a-sansara/Dev/Vala/adhan.ogg +# isha use default file +isha = + +[Cron] +# timings updating time +time = 00:00 +# cron file +path = /etc/cron.d/praytime +``` + +## Usage + +First step is to edit configuration file +``` +/usr/share/praytime.praytime.ini +``` + +and set your city & location, then add some adhan file. + +After that you can initialise the cron installation with + +``` +$ sudo praytime cron + + updating /etc/cron.d/praytime : ok +---------------------------------------------------------- + Paris FR - +0200 Thursday 19 October 2017 02:17:12 +---------------------------------------------------------- + Fajr : 06:32 + Dhuhr : 13:36 + Asr : 16:23 + Maghrib : 18:53 + Isha : 20:32 +``` + +you can test adhan with : + +``` +# Fajr or other prayer +praytime play Fajr +``` + +to see current timings simply do : +``` +$ praytime + +---------------------------------------------------------- + Paris FR - +0200 Thursday 19 October 2017 02:20:26 +---------------------------------------------------------- + Fajr : 06:32 + Dhuhr : 13:36 + Asr : 16:23 + Maghrib : 18:53 + Isha : 20:32 + +``` diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..adfc128 --- /dev/null +++ b/build/.gitkeep @@ -0,0 +1 @@ +# .gitkeep diff --git a/config/praytime.ini b/config/praytime.ini new file mode 100644 index 0000000..bc3f41d --- /dev/null +++ b/config/praytime.ini @@ -0,0 +1,34 @@ +[Params] +# see api.aladhan.com +city = Paris +country = FR +method = 3 +latitudeAdjustmentMethod = 3 + +[Volumes] +# you can use same volume for all prayer +default = 1.00 +# or adjust volume to specific prayer +fajr = 0.30 +dhuhr = 0.70 +asr = 0.40 +# maghrib use default volume +maghrib = +isha = 0.40 + +[Adhan] +# you can use same file for all prayer +default = /home/a-sansara/Dev/Vala/4006.mp3 +# or define specific file for each prayer +fajr = /home/a-sansara/Dev/Vala/Adhan-Fajr-Makkah-Sheikh-Ali-Ahmed-Mulla.ogg +dhuhr = /home/a-sansara/Dev/Vala/Filipino-Adhan.ogg +asr = /home/a-sansara/Dev/Vala/azan.ogg +maghrib = /home/a-sansara/Dev/Vala/adhan.ogg +# isha use default file +isha = + +[Cron] +# timings updating time +time = 00:00 +# cron file +path = /etc/cron.d/praytime diff --git a/main.vala.in b/main.vala.in new file mode 100644 index 0000000..3de3a83 --- /dev/null +++ b/main.vala.in @@ -0,0 +1,36 @@ +using GLib; +using Pluie; + +int main (string[] argv) +{ + var p = new PrayTime ("@DATA_PATH@", "@INSTALL_PATH@", "@VERSION@"); + + if (argv.length > 1) { + + switch (argv[1]) { + + case "cron" : + p.init_cron (); + break; + + case "timings" : + + break; + + case "play" : + if (argv.length > 2) { + p.play_adhan (argv[2]); + } + else { + stderr.printf ("missing pray parameter\n"); + return 1; + } + break; + } + } + else { + p.infos (); + } + stdout.printf ("\n"); + return 0; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..553cf8c --- /dev/null +++ b/meson.build @@ -0,0 +1,33 @@ +project('PrayTime', 'vala', 'c') + +glib_dep = dependency('glib-2.0') +gobject_dep = dependency('gobject-2.0') +gio_dep = dependency('gio-2.0') +json_dep = dependency('json-glib-1.0') +gstreamer_dep = dependency('gstreamer-1.0') + +version = '0.2.2' +bindir = join_paths(get_option('prefix'), get_option('bindir')) +datadir = join_paths(get_option('prefix'), get_option('datadir'), 'praytime') + +conf = configuration_data() +conf.set('VERSION' , version) +conf.set('INSTALL_PATH', bindir) +conf.set('DATA_PATH' , datadir) + +sources = [ + 'src/Pluie.PrayTime.vala', + 'build/main.vala' +] + +configure_file( + input: 'main.vala.in', + output: 'main.vala', + configuration: conf +) + +install_data('config/praytime.ini', install_dir : datadir) + +executable('praytime', sources, install : true, install_dir : bindir, + dependencies : [glib_dep, gobject_dep, gio_dep, json_dep, gstreamer_dep]) + diff --git a/src/Pluie.PrayTime.vala b/src/Pluie.PrayTime.vala new file mode 100644 index 0000000..f35e865 --- /dev/null +++ b/src/Pluie.PrayTime.vala @@ -0,0 +1,286 @@ +using GLib; +using Gst; + +class Pluie.PrayTime : GLib.Object +{ + const bool DEBUG = false; + const string SEP = "----------------------------------------------------------"; + const string[] PRAYLIST = { "Fajr", "Dhuhr", "Asr", "Maghrib", "Isha" }; + const string protocol = "http"; + const string hostname = "api.aladhan.com"; + const string uri = "timingsByCity?"; + + private string path; + private string version; + private string bin; + private GLib.KeyFile kf; + private GLib.MainLoop loop; + private Gst.Element playbin; + + + public PrayTime (string path, string bin, string version) + { + this.path = path; + this.version = version; + this.bin = bin; + this.kf = this.load_config ("praytime.ini"); + } + + + public void play_adhan (string pray) + { + if (pray in PrayTime.PRAYLIST) { + double volume = this.get_volume (pray.down ()); + string mp3 = this.get_mp3 (pray.down ()); + this.play (mp3, volume); + } + else { + this.on_error(@"invalid pray parameter '$pray'"); + } + } + + + public void infos() + { + KeyFile k = this.load_config ("praytime.daily.ini"); + var date = new GLib.DateTime.now_local (); + stdout.printf ( + "%s\n %s %s - %s\n%s\n", + PrayTime.SEP, + this.get_config ("city"), + this.get_config ("country"), + date.format ("%z %A %d %B %Y %T"), + PrayTime.SEP + ); + foreach (string pray in PrayTime.PRAYLIST) { + try { + stdout.printf (" %10s : %s\n", pray, k.get_string ("Praytime", pray.down ())); + } + catch (GLib.KeyFileError e) { + this.on_error (e.message); + } + } + } + + + public void init_cron () + { + try { + var parser = new Json.Parser (); + parser.load_from_data (this.get_timings ()); + var node = parser.get_root ().get_object ().get_object_member ("data"); + var timestamp = node.get_object_member ("date").get_string_member ("timestamp"); + var time = new GLib.DateTime.from_unix_utc (int.parse (timestamp)); + time = time.to_timezone (new GLib.TimeZone.local ()); + var results = node.get_object_member ("timings"); + this.write_timings (results, time); + this.set_cron (time); + this.infos (); + } + catch (Error e) { + this.on_error (e.message); + } + } + + + private void on_error (string msg) + { + if (PrayTime.DEBUG) { + message (msg); + } + stderr.printf (" Error : %s\n", msg); + } + + + private string get_config_file (string basename) + { + return Path.build_filename (this.path, basename); + } + + + private KeyFile load_config (string basename) + { + KeyFile f = new KeyFile (); + f.set_list_separator (','); + try { + f.load_from_file (this.get_config_file (basename), KeyFileFlags.NONE); + } + catch (KeyFileError e) { + this.on_error (e.message); + } + catch (FileError e) { + this.on_error (e.message); + } + return f; + } + + + private string get_mp3 (string key = "default") + { + string mp3 = this.get_config (key, "Adhan"); + if (mp3 == "" && key != "default") { + mp3 = this.get_config ("default", "Adhan"); + } + return mp3; + } + + + private double get_volume (string key = "default") + { + string volume = this.get_config (key, "Volumes"); + if (volume == "" && key != "default") { + volume = this.get_config ("default", "Volumes"); + } + return double.parse (volume); + } + + + private string get_config (string key, string group = "Params") + { + string v = ""; + try { + v = this.kf.get_string (group, key); + } + catch (GLib.KeyFileError e) { + this.on_error (e.message); + } + return v; + } + + + private string get_timings () + { + string url = "%s://%s/%scity=%s&country=%s&method=%s&latitudeAdjustmentMethod=%s".printf( + PrayTime.protocol, + PrayTime.hostname, + PrayTime.uri, + this.get_config ("city"), + this.get_config ("country"), + this.get_config ("method"), + this.get_config ("latitudeAdjustmentMethod") + ); + var f = File.new_for_uri (url); + var response = ""; + try { + // root user experience problem with that + // don't know why, use curl as alternative + // in get_alt_timings + FileInputStream fs = f.read (); + var dis = new DataInputStream (fs); + string line; + while ((line = dis.read_line (null)) != null) { + response += line + "\n"; + } + } + catch (GLib.Error e) { + this.on_error (e.message); + response = this.get_alt_timings (url); + } + return response; + } + + + private string get_alt_timings (string url) + { + stdout.printf(" trying alternate method to get timings\n"); + string response = ""; + string std_error; + int status; + try { + Process.spawn_command_line_sync ( + "curl "+url, + out response, + out std_error, + out status + ); + } catch (SpawnError e) { + stderr.printf ("%s\n", e.message); + } + return response; + } + + private void set_cron (GLib.DateTime date) + { + try { + string bin = "root "+this.bin; + string cron_path = this.get_config ("path", "Cron"); + string[] update = this.get_config ("time", "Cron").split (":", 2); + string content = "# %s\n%d %d * * * %s %s\n".printf (date.format ("%c"), int.parse (update[1]), int.parse(update[0]) , bin, "cron"); + KeyFile k = this.load_config ("praytime.daily.ini"); + foreach (string pray in PrayTime.PRAYLIST) { + string[] time = k.get_string ("Praytime", pray.down ()).split (":", 2); + content += "%s %s * * * %s %s %s\n".printf (time[1], time[0] , bin, "play", pray); + } + bool done = FileUtils.set_contents (cron_path, content); + stdout.printf ("\n updating %s : %s\n", cron_path, done ? "ok" : "ko"); + } + catch (GLib.KeyFileError e) { + this.on_error (e.message); + } + catch(GLib.FileError e) { + this.on_error (e.message); + } + } + + + private void write_timings (Json.Object results, GLib.DateTime date) + { + string data = "[Praytime]\n# %s\n%-10s = %s\n".printf (date.format ("%c"), "timestamp", date.to_unix ().to_string ()); + foreach (string pray in PrayTime.PRAYLIST) { + data += "%-10s = %s\n".printf (pray.down(), results.get_string_member (pray)); + } + try { + FileUtils.set_contents (this.get_config_file ("praytime.daily.ini"), data); + } + catch (GLib.FileError e) { + stderr.printf ("%s\n", e.message); + } + } + + + private void play (string f, double volume = 1.0, string[]? params = null) + { + try { + Gst.init (ref params); + this.loop = new GLib.MainLoop (); + var pipeline = Gst.parse_launch ("playbin uri=\"file://%s\"".printf (f)); + this.playbin = pipeline; + this.playbin.set_state (State.PLAYING); + this.playbin.set ("volume", volume); + this.playbin.get_bus ().add_watch (0, this.bus_callback); + this.loop.run (); + } + catch (FileError e) { + this.on_error (e.message); + } + catch (Error e) { + this.on_error (e.message); + } + } + + + private bool bus_callback (Gst.Bus bus, Gst.Message message) + { + switch (message.type) { + + case MessageType.ERROR: + GLib.Error err; + string debug; + message.parse_error (out err, out debug); + stdout.printf ("Error: %s\n", err.message); + loop.quit (); + break; + + case MessageType.EOS: + stdout.printf ("end of stream\n"); + this.playbin.set_state (State.NULL); + loop.quit (); + break; + + default: + break; + } + return true; + } + +}