Compare commits

...

5 Commits

5 changed files with 266 additions and 134 deletions

View File

@ -1,13 +1,13 @@
# PrayTime
PrayTime is a small vala program which find timings of Islamic Prayer
PrayTime is a small program written in vala 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
valac curl meson ninja glib gobject json-glib gstreamer
valac curl meson ninja glib gobject json-glib gstreamer pluie-echo
see meson.build
@ -17,12 +17,22 @@ 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')
echo_dep = dependency('pluie-echo-0.2')
```
on debian or debian like you can do :
```
$ sudo apt-get install valac libjson-glib-dev libgstreamer1.0-dev meson ninja-build
$ sudo apt-get install valac libjson-glib-dev libgstreamer1.0-dev libgstreamer0.10-dev meson ninja-build
```
there is not yet a package for pluie-echo dependency, but you can install it with :
```
cd /tmp/
git clone https://github.com/pluie-org/libpluie-echo.git --branch latest --single-branch
cd libpluie-echo
meson --prefix=/usr ./ build
sudo ninja install -C build
```
## Install
@ -67,35 +77,28 @@ isha =
[Cron]
# timings updating time
time = 00:00
# cron file
path = /etc/cron.d/praytime
time = 02:22
```
## Usage
![praytime usage](https://www.meta-tech.academy/img/praytime-usage.png?tmp=2)
First step is to edit configuration file
```
/usr/share/praytime.praytime.ini
```
and set your city & location, then add some adhan file.
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
$ praytime cron
```
![praytime cron](https://www.meta-tech.academy/img/praytime-cron.png?tmp=1)
you can test adhan with :
@ -103,18 +106,14 @@ you can test adhan with :
# Fajr or other prayer
praytime play Fajr
```
![praytime play adhan](https://www.meta-tech.academy/img/praytime-play.png?tmp=1)
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
```
![praytime timings](https://www.meta-tech.academy/img/praytime-timings.png?tmp=1)
the red star indicates coming prayers

View File

@ -29,6 +29,4 @@ isha =
[Cron]
# timings updating time
time = 00:00
# cron file
path = /etc/cron.d/praytime
time = 02:22

View File

@ -3,34 +3,55 @@ using Pluie;
int main (string[] argv)
{
int done = 0;
var nostate = false;
Echo.init (bool.parse("@DEBUG@"));
Dbg.in (Log.METHOD);
var p = new PrayTime ("@DATA_PATH@", "@INSTALL_PATH@", "@VERSION@");
if (argv.length > 1) {
switch (argv[1]) {
if (argv[1] == "version") {
stdout.printf (p.version);
nostate = true;
}
else {
case "cron" :
p.init_cron ();
break;
of.title ("PrayTime", p.version, "a-sansara");
case "timings" :
switch (argv[1]) {
break;
case "cron" :
p.init_cron ();
break;
case "play" :
if (argv.length > 2) {
p.play_adhan (argv[2]);
}
else {
stderr.printf ("missing pray parameter\n");
return 1;
}
break;
case "play" :
if (argv.length > 2) {
done = p.play_adhan (argv[2]);
}
else {
of.error ("missing pray parameter");
done = 1;
}
break;
default :
of.warn ("invalid command %s".printf (argv[1]));
p.usage();
done = 1;
break;
}
}
}
else {
p.infos ();
}
stdout.printf ("\n");
return 0;
of.echo ();
if (!nostate) {
of.rs (done == 0);
of.echo ();
}
Dbg.out (Log.METHOD);
return done;
}

View File

@ -1,12 +1,13 @@
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')
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')
echo_dep = dependency('pluie-echo-0.2')
version = '0.2.2'
version = '0.2.6'
bindir = join_paths(get_option('prefix'), get_option('bindir'))
datadir = join_paths(get_option('prefix'), get_option('datadir'), 'praytime')
@ -14,6 +15,7 @@ conf = configuration_data()
conf.set('VERSION' , version)
conf.set('INSTALL_PATH', bindir)
conf.set('DATA_PATH' , datadir)
conf.set('DEBUG' , 'false')
sources = [
'src/Pluie.PrayTime.vala',
@ -29,5 +31,5 @@ configure_file(
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])
dependencies : [glib_dep, gobject_dep, gio_dep, json_dep, gstreamer_dep, echo_dep])

View File

@ -3,68 +3,89 @@ 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;
private const string[] PRAYLIST = { "Fajr", "Dhuhr", "Asr", "Maghrib", "Isha" };
private const string protocol = "http";
private const string hostname = "api.aladhan.com";
private const string uri = "timingsByCity?";
private Sys.Cmd cmd;
private string path;
public 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)
{
Dbg.in (Log.METHOD, "path:'%s':bin:'%s':version:'%s'".printf (path, bin, version), Log.LINE, Log.FILE);
this.path = path;
this.version = version;
this.bin = bin;
this.kf = this.load_config ("praytime.ini");
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
}
public void play_adhan (string pray)
public int play_adhan (string pray)
{
Dbg.in (Log.METHOD, "pray:'%s'".printf (pray), Log.LINE, Log.FILE);
var done = 0;
if (pray in PrayTime.PRAYLIST) {
double volume = this.get_volume (pray.down ());
string mp3 = this.get_mp3 (pray.down ());
of.action ("Playing Adhan", "%s time".printf (pray));
this.play (mp3, volume);
}
else {
this.on_error(@"invalid pray parameter '$pray'");
of.error (@"invalid pray parameter '$pray'");
done = 1;
}
Dbg.out (Log.METHOD, "done ? %d".printf (done), Log.LINE, Log.FILE);
return done;
}
public void infos()
public void infos (bool bypass_title = false)
{
KeyFile k = this.load_config ("praytime.daily.ini");
Dbg.in (Log.METHOD, null, Log.LINE, Log.FILE);
bool done = true;
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
KeyFile k = this.load_config ("praytime.daily.ini", true);
if (!bypass_title) {
of.title ("PrayTime", this.version, "a-sansara");
}
of.action (
"Retriew timings for",
"%s %s\n".printf (this.get_config ("city"), this.get_config ("country"))
);
of.echo (date.format ("%z %A %d %B %Y"), false);
of.echo (date.format ("%T"), true, false, ECHO.OPTION_SEP);
of.echo ();
int t = int.parse(date.format ("%H%M"));
var s = "";
var p = "";
foreach (string pray in PrayTime.PRAYLIST) {
try {
stdout.printf (" %10s : %s\n", pray, k.get_string ("Praytime", pray.down ()));
p = k.get_string ("Praytime", pray.down ());
s = (int.parse(p.substring (0, 2) + p.substring (3, 2))) > t ? "*" : " ";
of.keyval(pray, "%s %s".printf( p, of.c (ECHO.MICROTIME).s (s)));
}
catch (GLib.KeyFileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
done = false;
}
}
of.state (done);
Dbg.out (Log.METHOD, "done:%d".printf ((int)done), Log.LINE, Log.FILE);
}
public void init_cron ()
{
Dbg.in (Log.METHOD, null, Log.LINE, Log.FILE);
try {
var parser = new Json.Parser ();
parser.load_from_data (this.get_timings ());
@ -75,81 +96,94 @@ class Pluie.PrayTime : GLib.Object
var results = node.get_object_member ("timings");
this.write_timings (results, time);
this.set_cron (time);
this.infos ();
this.infos (true);
}
catch (Error e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
}
private void on_error (string msg)
private string get_config_file (string basename, bool tmp)
{
if (PrayTime.DEBUG) {
message (msg);
}
stderr.printf (" Error : %s\n", msg);
return Path.build_filename (!tmp ? this.path : Environment.get_tmp_dir (), basename);
}
private string get_config_file (string basename)
{
return Path.build_filename (this.path, basename);
}
private KeyFile load_config (string basename)
private KeyFile load_config (string basename, bool tmp = false)
{
Dbg.in (Log.METHOD, null, Log.LINE, Log.FILE);
KeyFile f = new KeyFile ();
f.set_list_separator (',');
try {
f.load_from_file (this.get_config_file (basename), KeyFileFlags.NONE);
f.load_from_file (this.get_config_file (basename, tmp), KeyFileFlags.NONE);
}
catch (KeyFileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
catch (FileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return f;
}
private string get_mp3 (string key = "default")
{
Dbg.in (Log.METHOD, "key:'%s'".printf (key), Log.LINE, Log.FILE);
string mp3 = this.get_config (key, "Adhan");
if (mp3 == "" && key != "default") {
mp3 = this.get_config ("default", "Adhan");
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return mp3;
}
private double get_volume (string key = "default")
{
Dbg.in (Log.METHOD, "key:'%s'".printf (key), Log.LINE, Log.FILE);
string volume = this.get_config (key, "Volumes");
if (volume == "" && key != "default") {
volume = this.get_config ("default", "Volumes");
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return double.parse (volume);
}
private string get_config (string key, string group = "Params")
{
Dbg.in (Log.METHOD, "key:'%s':group:'%s'".printf (key, group), Log.LINE, Log.FILE);
string v = "";
try {
v = this.kf.get_string (group, key);
}
catch (GLib.KeyFileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return v;
}
private int spawn_cmd (string cmd, out string response)
{
Dbg.in (Log.METHOD, "cmd:'%s'".printf (cmd), Log.LINE, Log.FILE);
if (this.cmd == null) this.cmd = new Sys.Cmd();
this.cmd.run (false, cmd);
response = this.cmd.output;
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return this.cmd.status;
}
private string get_timings ()
{
Dbg.in (Log.METHOD, null, Log.LINE, Log.FILE);
of.action ("Loading timings");
string url = "%s://%s/%scity=%s&country=%s&method=%s&latitudeAdjustmentMethod=%s".printf(
PrayTime.protocol,
PrayTime.hostname,
@ -159,87 +193,151 @@ class Pluie.PrayTime : GLib.Object
this.get_config ("method"),
this.get_config ("latitudeAdjustmentMethod")
);
of.echo (url, true, false, ECHO.OPTION_SEP);
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";
}
of.state (true);
}
catch (GLib.Error e) {
this.on_error (e.message);
of.state (false);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
response = this.get_alt_timings (url);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
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);
}
Dbg.in (Log.METHOD, "url:'%s'".printf (url), Log.LINE, Log.FILE);
of.action ("Trying alternate method to load timings");
string response;
var status = this.spawn_cmd ("curl %s".printf (url), out response);
of.state (status == 0);
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return response;
}
private int get_user_crontab_content (string user, out string response)
{
return this.spawn_cmd ("crontab -l -u %s".printf (user), out response);
}
private int install_user_crontab (string user, string path)
{
string response;
return this.spawn_cmd ("crontab %s -u %s".printf (path, user), out response);
}
private string? get_user_crontab (ref string user, ref string content)
{
Dbg.in (Log.METHOD, "user:'%s'".printf (user), Log.LINE, Log.FILE);
string data = "";
string udata;
try {
if (this.get_user_crontab_content (user, out udata) == 0) {
var regex = new Regex (Path.build_filename (this.bin, "praytime").escape (null));
foreach (string line in udata.split ("\n")) {
if (!regex.match (line) && line != "") {
data += line + "\n";
}
}
data += "\n" + content + "\n";
}
}
catch (RegexError e) {
data = null;
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return data;
}
private string get_user ()
{
Dbg.in (Log.METHOD, null, Log.LINE, Log.FILE);
string? user = Environment.get_variable ("SUDO_USER");
if (user == null) {
user = Environment.get_variable ("USER");
}
if (user == null) {
user = Environment.get_variable ("LOGNAME");
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
return user;
}
private void set_cron (GLib.DateTime date)
{
Dbg.in (Log.METHOD, "date:'%s'".printf (date.format ("%F %Hh%Mm%S")), Log.LINE, Log.FILE);
try {
string bin = "root "+Path.build_filename(this.bin, "praytime");
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");
string user = this.get_user();
of.action ("Setting crontab for user", user);
KeyFile k = this.load_config ("praytime.daily.ini", true);
string[] update = this.get_config ("time", "Cron").split (":", 2);
string[] time = null;
string bin = Path.build_filename (this.bin, "praytime");
string cron_path = Path.build_filename (Environment.get_tmp_dir (), "praytime.crontab");
string content = "# > autogenerated by %s %s\n".printf (bin, date.format ("%c")) +
"%02d %02d * * * %s cron\n".printf (int.parse (update[1]), int.parse (update[0]) , bin);
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);
time = k.get_string ("Praytime", pray.down ()).split (":", 2);
content += "%s %s * * * sh -c \"DISPLAY=:0 %s play %s\"\n".printf (time[1], time[0] , bin, pray);
}
content += "# < autogenerated by %s\n".printf(bin);
content = this.get_user_crontab (ref user, ref content);
if (content != null) {
if (FileUtils.set_contents (cron_path, content)) {
int status = this.install_user_crontab (user, cron_path);
of.state (status == 0);
}
}
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);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
catch(GLib.FileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
}
private void write_timings (Json.Object results, GLib.DateTime date)
{
Dbg.in (Log.METHOD, "results:...:date:'%s'".printf (date.format ("%F %Hh%Mm%S")), Log.LINE, Log.FILE);
of.action ("Saving timings");
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);
var done = FileUtils.set_contents (this.get_config_file ("praytime.daily.ini", true), data);
of.state (done);
}
catch (GLib.FileError e) {
stderr.printf ("%s\n", e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
}
private void play (string f, double volume = 1.0, string[]? params = null)
{
Dbg.in (Log.METHOD, "f:'%s':volume:%0.1f".printf (f, volume), Log.LINE, Log.FILE);
try {
Gst.init (ref params);
this.loop = new GLib.MainLoop ();
@ -251,11 +349,12 @@ class Pluie.PrayTime : GLib.Object
this.loop.run ();
}
catch (FileError e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
catch (Error e) {
this.on_error (e.message);
Dbg.error (e.message, Log.METHOD, Log.LINE, Log.FILE);
}
Dbg.out (Log.METHOD, null, Log.LINE, Log.FILE);
}
@ -267,12 +366,12 @@ class Pluie.PrayTime : GLib.Object
GLib.Error err;
string debug;
message.parse_error (out err, out debug);
stdout.printf ("Error: %s\n", err.message);
of.error (err.message);
loop.quit ();
break;
case MessageType.EOS:
stdout.printf ("end of stream\n");
of.echo ("end of stream");
this.playbin.set_state (State.NULL);
loop.quit ();
break;
@ -283,4 +382,17 @@ class Pluie.PrayTime : GLib.Object
return true;
}
public void usage ()
{
of.echo ("\nUsage :", true, true, ECHO.VAL);
of.usage_command("praytime", "cron" , "" , "%s\n%s\n%s".printf (
"# update user crontab",
"# before installing please check config file ",
"# %s/praytime.ini".printf (this.path)
));
of.usage_command("praytime", "version", "" , "# display program version");
of.usage_command("praytime", "play" , "PRAYER_NAME", "# play adhan (Fajr, Dhuhr, Asr, Maghrib, Isha)");
of.usage_command("praytime", "" , "" , "# display prayer timings");
}
}