Skip to content
Browse files

Logic to refuse to start if duplicate entries are present for firefix…

…/aurora.

Change 'debug' mode to 'parse' mode.
Colorized deamon a bit.
  • Loading branch information...
1 parent 38e8935 commit 13b1d534f0c8aeb12fa171d2a4134cdf3f1eb741 @graysky2 committed Dec 2, 2012
Showing with 176 additions and 93 deletions.
  1. +6 −0 CHANGELOG
  2. +41 −16 README-for_other_distros
  3. +76 −51 profile-sync-daemon
  4. +53 −26 psd.manpage
View
6 CHANGELOG
@@ -1,3 +1,9 @@
+v5.07
+02-Dec-2012
+Logic to refuse to start if duplicate entries are present for firefix/aurora. See man page for details.
+Change 'debug' mode to 'parse' mode.
+Colorized deamon a bit.
+
v5.06
28-Nov-2012
Fix kill_browsers - Only try to kill running browsers when PSNAME is not empty (xduugu).
View
57 README-for_other_distros
@@ -10,46 +10,48 @@ DESCRIPTION
Profile-sync-daemon (psd) is a tiny pseudo-daemon designed to manage your browser profile(s) in tmpfs and to periodically sync back to the physical disc (HDD/SSD). This is accomplished via a symlinking step and an innovative use of rsync to maintain back-up and synchronization between the two. One of the major design goals of psd is a completely transparent user experience.
Running this daemon is beneficial for two reasons:
- o Reduced wear to physical discs
+ o Reduced wear to physical discs (particularly SSDs)
o Speed
Since the profile(s), browser cache*, etc. are relocated into tmpfs (RAM disk), the corresponding onslaught of I/O associated with using the browser is also redirected from the physical disc to RAM, thus reducing wear to the physical disc and also greatly improving browser speed and responsiveness. For example, the access time of RAM is on the order of nanoseconds while the access time of physical discs is on the order of milliseconds. This is a difference of six orders of magnitude or 1,000,000 times faster.
*Note that some browsers such as Chromium, Google-chrome, and Midori, actually keeps their cache directories separately from their browser profile directory. It is not within the scope of profile-sync-daemon to modify this behavior; users are encouraged to modify this behavir. For example, refer to the following url for several work-arounds for the Chrome-based browsers: https://wiki.archlinux.org/index.php/Chromium_Tips_and_Tweaks#Cache_in_tmpfs
-
SETUP
User managed settings are defined in /etc/psd.conf which is included in the package. At a minimum, define which user(s) will have their profiles managed by psd and note that at least one user must be defined.
Example:
- USERS="bar foo"
+ USERS="bar foo"
o Optionally uncomment and define which browsers are to be managed in the BROWSERS array. Note that the default is all browsers unless otherwise defined.
o Optionally redefine the location of your distro's tmpfs. Do this in the VOLATILE variable. Note that for Arch Linux, the default value of "/tmp" should work just fine.
+PARSE MODE
+The parse option can be called to show users exactly what psd will do based on the /etc/psd.conf entered. Call it like so:
+
+ $ profile-sync-daemon parse
+
+As you will see in the output and as stated above, if no specific browser or subset of browsers are defined in the BROWSERS array, psd will sync ALL supported profiles that it finds for the given user(s).
+
GENERAL USAGE
-Do not call /usr/bin/profile-sync-daemon directly. Instead use the provided daemon files to interact with psd. The initial synchronization will occur when the daemon starts. Additionally, cron (if running on your system) will call it to sync or update once per hour. Finally, psd will sync back a final time when it is called to stop.
-
-USAGE FOR SYSTEMD USERS
+Do not call /usr/bin/profile-sync-daemon to sync or to unsync directly. Instead use the provided service file. The initial synchronization will occur when the daemon starts. Additionally, cron (if running on your system) will call it to sync or update once per hour. Finally, psd will sync back a final time when it is called to stop.
+
+FOR SYSTEMD USERS (OFFICIALLY SUPPORTED METHOD)
The provided daemon file should be used to interact with psd (/usr/lib/systemd/system/psd.service):
- # systemctl [option] psd.service
+ # systemctl [option] psd.service
Available options:
start Turn on daemon; make symlinks and actively manage targets in tmpfs.
stop Turn off daemon; remove symlinks and rotate tmpfs data back to disc.
enable Autostart daemon when system comes up.
disable Remove daemon from the list of autostart daemons.
-DEBUG MODE
-The debug option can be called to show users exactly what psd will do based on the /etc/psd.conf entered. Call it like so:
-
- $ profile-sync-daemon debug
-
-As stated above, if no specific browser or set of browsers are defined in the BROWSERS array, psd will sync ALL profiles that it finds for the given user.
+OTHER INITSCRIPTS
+Other init script may be used to manage the deamon, but they are unsupported by the author.
-SUPPORTED BROWSERS AND CAVEATS
- Currently, the following browsers are auto-detected and managed:
+SUPPORTED BROWSERS
+Currently, the following browsers are auto-detected and managed:
o Chromium
o Conkeror
o Firefox (stable,beta,aurora)
@@ -60,13 +62,36 @@ SUPPORTED BROWSERS AND CAVEATS
o Opera-Next
o QupZilla
- For more, see: https://wiki.archlinux.org/index.php/Profile-sync-daemon
+CAVEATS FOR FIREFOX AND HEFTIG AURORA USERS ONLY
+The way psd keeps track of browser profiles and sync targets requires users to have unique name as the last directory for all profiles in their respective $HOME/.mozilla/<browser>/profiles.ini files. Psd will check when it is called to run for this and refuse if you have not satisfied the condition.
+
+The following is an example of a BAD profile that will not pass the test. You can see that although each full path is unique, they both END in the same name! Again, you must modify the profiles.ini and the corresponding directory on the filesystem to correct this if you want to use psd.
+
+ $ cat ~/.mozilla/firefox/profiles.ini
+
+ [General]
+ StartWithLastProfile=1
+
+ [Profile0 for user facade]
+ Name=normal
+ IsRelative=0
+ Path=/mnt/data/docs/facade/mozilla/firefox/myprofile.abc
+ Default=1
+
+ [Profile1 for user happy]
+ Name=proxy
+ IsRelative=0
+ Path=/mnt/data/docs/happy/mozilla/firefox/myprofile.abc
CONTRIBUTE
Should you wish to contribute to this code, please fork and send a pull request. Source is freely available on github: https://github.com/graysky2/profile-sync-daemon
BUGS
It is known that on slow systems with large profiles, the sync'ing step sometimes take longer than the boot-up of the WM. Therefore, users can theoretically start their browser before the profile has been transitioned to tmpfs. This is particularly prevalent on systems with slow HDDs running systemd.
+ONLINE
+ o Wiki page: https://wiki.archlinux.org/index.php/Profile-sync-daemon
+ o Discussion thread: https://bbs.archlinux.org/viewtopic.php?id=131321
+
AUTHOR
graysky (graysky AT archlinux DOT us)
View
127 profile-sync-daemon
@@ -1,15 +1,11 @@
#!/bin/bash
# By graysky <graysky AT archlinux DOT us>
# Inspired by some code originally written by Colin Verot
-
-PSD_VERS="5.06"
-
-### Arrays
-# ProfileArray is transient used to store profile paths parsed from firefox from and aurora
-# DIRArray is a full path corrected for both relative and absolute paths
+export BLD="\e[01m" RED="\e[01;31m" GRN="\e[01;32m" YLW="\e[01;33m" NRM="\e[00m"
+VERS="5.07"
if [[ ! -f /etc/psd.conf ]]; then
- echo "Cannot find /etc/psd.conf so bailing. Reinstall package to use profile-sync-daemon." # nothing to do if there is no conf file
+ echo "Cannot find /etc/psd.conf so bailing. Reinstall package to use Profile-sync-daemon." # nothing to do if there is no conf file
exit 1
else
. /etc/psd.conf
@@ -38,14 +34,18 @@ root_check() {
}
set_which() {
+ ### Arrays
+ # profileArr is transient used to store profile paths parsed from firefox from and aurora
+ # DIRArr is a full path corrected for both relative and absolute paths
+
local user=$1
local browser=$2
homedir="$(getent passwd $user | cut -d: -f6)"
group="$(stat -c %G $homedir)"
# reset global variables and arrays
- unset ProfileArray DIRArray
+ unset profileArr DIRArr
PSNAME=
BACKUP=
DIR=
@@ -57,11 +57,11 @@ set_which() {
case "$browser" in
chromium|midori)
- DIRArray[0]="$homedir/.config/$browser"
+ DIRArr[0]="$homedir/.config/$browser"
PSNAME="$browser"
;;
google-chrome)
- DIRArray[0]="$homedir/.config/$browser"
+ DIRArr[0]="$homedir/.config/$browser"
PSNAME="chrome"
;;
firefox)
@@ -71,9 +71,9 @@ set_which() {
PSNAME="$browser"
for profileItem in ${profileArr[@]}; do
if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
- DIRArray[index]="$profileItem" # path is not relative
+ DIRArr[index]="$profileItem" # path is not relative
else
- DIRArray[index]="$homedir/.mozilla/firefox/$profileItem" # we need to append the default path to give a fully qualified path
+ DIRArr[index]="$homedir/.mozilla/firefox/$profileItem" # we need to append the default path to give a fully qualified path
fi
index=$index+1
done
@@ -87,20 +87,20 @@ set_which() {
PSNAME="aurora"
for profileItem in ${profileArr[@]}; do
if [[ $(echo $profileItem | cut -c1) = "/" ]]; then
- DIRArray[index]="$profileItem" # path is not relative
+ DIRArr[index]="$profileItem" # path is not relative
else
- DIRArray[index]="$homedir/.mozilla/aurora/$profileItem" # we need to append the default path to give a fully qualified path
+ DIRArr[index]="$homedir/.mozilla/aurora/$profileItem" # we need to append the default path to give a fully qualified path
fi
index=$index+1
done
fi
;;
opera|opera-next|qupzilla)
- DIRArray[0]="$homedir/.$browser"
+ DIRArr[0]="$homedir/.$browser"
PSNAME="$browser"
;;
conkeror.mozdev.org)
- DIRArray[0]="$homedir/.$browser"
+ DIRArr[0]="$homedir/.$browser"
PSNAME="xulrunner"
;;
*)
@@ -109,15 +109,15 @@ set_which() {
esac
}
-refuse_to_start() {
+running_check() {
# check for browsers running and refuse to start if so
# without this cannot guarantee profile integrity
local browser user
for user in $USERS; do
for browser in $BROWSERS; do
set_which "$user" "$browser"
if [[ -n "$PSNAME" ]] && pgrep -u "$user" "$PSNAME" &>/dev/null; then
- echo "Refusing to start since $browser is running by $user!"
+ echo "Refusing to start; $browser is running by $user!"
exit 1
else
/bin/true
@@ -126,8 +126,38 @@ refuse_to_start() {
done
}
+dup_check() {
+ # only for firefox and aurora
+ # the LAST directory in the profile MUST be unique
+ # make sure there are no duplicates in ~/.mozilla/<browser>/profiles.ini
+ local browser user
+ for user in $USERS; do
+ for browser in $BROWSERS; do
+ set_which "$user" "$browser"
+ if [[ "$browser" = "firefox" ]] || [[ "$browser" = "heftig-aurora" ]]; then
+ if [[ -z ${DIRArr[@]} ]]; then
+ /bin/true # nothing to check
+ else
+ # browser is on system so check profiles
+ #
+ # check that the LAST DIRECTORY in the full path is unique
+ unique_count=$(echo ${DIRArr[@]##*/} | sed 's/ /\n/g' | sort | uniq | wc -l)
+ if [[ ${#DIRArr[@]##*/} -eq $unique_count ]]; then
+ /bin/true # no problems so do nothing
+ else
+ echo -e " ${RED}Error: ${NRM}${BLD}dup profile for ${GRN}$browser${NRM}${BLD} detected. See psd manpage, correct, and try again."${NRM}
+ [[ "$browser" = "heftig-aurora" ]] && browser="${browser##*-}" # clip of the 'heftig-' to give correct path
+ echo -e " ${BLD}Must have unique last directories in ${YLW}$homedir/.mozilla/$browser/profiles.ini${NRM}${BLD} to use psd."${NRM}
+ exit 1
+ fi
+ fi
+ fi
+ done
+ done
+}
+
kill_browsers() {
- # check for browsers running and kill them to safely sync
+ # check for browsers running and kill them to safely sync/unsync
# without this cannot guarantee profile integrity
local browser user
for user in $USERS; do
@@ -146,40 +176,36 @@ kill_browsers() {
### Do we need a secondary, more powerful method of killing if the first fails?
done
done
- sleep 2s # pause allowing things to settle on slow machines
}
-debug() {
- echo -en "profile-sync-daemon \E[31m::DEBUG MODE::"
- tput sgr0
- echo
- echo "version: $PSD_VERS"
+parse_conf_file() {
+ echo -e "${RED}Profile-sync-daemon v$VERS${NRM}"
echo
- echo "/etc/psd.conf will make profile-sync-daemon manage the following:"
+ echo -e "${BLD}Psd will manage the following per ${YLW}/etc/psd.conf${BLD} settings${NRM}${BLD}:"${NRM}
echo
local browser user
for user in $USERS; do
for browser in $BROWSERS; do
set_which "$user" "$browser"
- for item in ${DIRArray[@]}; do
+ for item in ${DIRArr[@]}; do
DIR="$item"
BACKUP="$item-backup"
[[ "$browser" = "firefox" ]] || [[ "$browser" = "heftig-aurora" ]] && suffix="-${item##*/}" || suffix=
if [[ -d "$DIR" ]]; then
- echo "browser:psname $browser:$PSNAME"
- echo "owner/group: $user:$group"
- echo "sync target: $DIR"
- echo "backup target: $BACKUP"
- echo "tmpfs dir: $VOLATILE/$user-$browser$suffix"
+ echo -e "${BLD}browser:psname $browser:$PSNAME"${NRM}
+ echo -e "${BLD}owner/group: $user:$group"${NRM}
+ echo -e "${BLD}sync target: ${YLW}$DIR"${NRM}
+ echo -e "${BLD}backup target: ${YLW}$BACKUP"${NRM}
+ echo -e "${BLD}tmpfs dir: ${RED}$VOLATILE/$user-$browser$suffix"${NRM}
echo
fi
done
done
done
}
-check() {
+ungraceful_state_check() {
# if the machine was ungracefully shutdown then the backup will be on the filesystem
# and the link to tmpfs will be on the filesystem but the contents will be empty
# we need to simply remove the link and rotate the backup into place
@@ -189,7 +215,7 @@ check() {
for user in $USERS; do
for browser in $BROWSERS; do
set_which "$user" "$browser"
- for item in ${DIRArray[@]}; do
+ for item in ${DIRArr[@]}; do
DIR="$item"
BACKUP="$item-backup"
[[ "$browser" = "firefox" ]] || [[ "$browser" = "heftig-aurora" ]] && suffix="-${item##*/}" || suffix=
@@ -205,15 +231,15 @@ check() {
done
}
-sync() {
+do_sync() {
root_check
touch "$DAEMON_FILE"
local browser user
for user in $USERS; do
for browser in $BROWSERS; do
set_which "$user" "$browser"
- for item in ${DIRArray[@]}; do
+ for item in ${DIRArr[@]}; do
DIR="$item"
BACKUP="$item-backup"
[[ "$browser" = "firefox" ]] || [[ "$browser" = "heftig-aurora" ]] && suffix="-${item##*/}" || suffix=
@@ -242,15 +268,15 @@ sync() {
done
}
-unsync() {
+do_unsync() {
root_check
rm -f "$DAEMON_FILE"
local browser user
for user in $USERS; do
for browser in $BROWSERS; do
set_which "$user" "$browser"
- for item in ${DIRArray[@]}; do
+ for item in ${DIRArr[@]}; do
DIR="$item"
BACKUP="$item-backup"
[[ "$browser" = "firefox" ]] || [[ "$browser" = "heftig-aurora" ]] && suffix="-${item##*/}" || suffix=
@@ -270,29 +296,28 @@ unsync() {
}
case "$1" in
- debug)
- debug
+ p|P|Parse|parse|debug)
+ dup_check && parse_conf_file
;;
sync)
- [[ ! -f $DAEMON_FILE ]] && refuse_to_start && check
- sync
+ [[ ! -f $DAEMON_FILE ]] && dup_check && running_check && ungraceful_state_check
+ do_sync
;;
resync)
- [[ -f $DAEMON_FILE ]] && sync
+ [[ -f $DAEMON_FILE ]] && do_sync
;;
unsync)
- [[ -f $DAEMON_FILE ]] && sync && kill_browsers && unsync
+ [[ -f $DAEMON_FILE ]] && do_sync && kill_browsers
+ do_unsync
;;
*)
- echo -en "profile-sync-daemon \E[31m::USAGE::"
- tput sgr0
+ echo -e "${RED}Profile-sync-daemon v$VERS${NRM}"
echo
- echo "version: $PSD_VERS"
+ echo -e " ${BLD}$0 ${NRM}${GRN}{parse|sync|unsync}${NRM}"
echo
- echo "$0 {sync|unsync|debug}"
- echo " sync) Force a manual sync."
- echo "unsync) Force a manual unsync."
- echo " debug) Parse config file to see which profiles will be managed."
+ echo -e " ${BLD}parse) ${NRM}${GRN}Parse${NRM}${BLD} config file (${NRM}${YLW}/etc/psd.conf${NRM}${BLD}) to see which profiles will be managed."${NRM}
+ echo -e " ${BLD}sync) Force a manual ${NRM}${GRN}sync${NRM}${BLD}. Must be run as root user and NOT recommended."${NRM}
+ echo -e " ${BLD}unsync) Force a manual ${NRM}${GRN}unsync${NRM}${BLD}. Must be run as root user and NOT recommended."${NRM}
;;
esac
exit 0
View
79 psd.manpage
@@ -1,5 +1,5 @@
.\" Text automatically generated by txt2man
-.TH profile-sync-daemon 1 "27 November 2012" "" ""
+.TH profile-sync-daemon 1 "02 December 2012" "" ""
.SH NAME
\fBprofile-sync-daemon \fP- Symlinks and syncs browser profile dirs to RAM thus reducing HDD/SDD calls and speeding-up browsers.
\fB
@@ -20,40 +20,46 @@ Profile-sync-daemon (\fIpsd\fP) is a tiny pseudo-daemon designed to manage your
Running this daemon is beneficial for two reasons:
.RS
.IP \(bu 3
-Reduced wear to physical discs
+Reduced wear to physical discs (particularly SSDs)
.IP \(bu 3
Speed
.RE
.PP
Since the \fBprofile\fP(s), browser cache*, etc. are relocated into tmpfs (RAM disk), the corresponding onslaught of I/O associated with using the browser is also redirected from the physical disc to RAM, thus reducing wear to the physical disc and also greatly improving browser speed and responsiveness. For example, the access time of RAM is on the order of nanoseconds while the access time of physical discs is on the order of milliseconds. This is a difference of six orders of magnitude \fBor\fP 1,000,000 times faster.
*Note that some browsers such as Chromium, Google-chrome, and Midori, actually keeps their cache directories separately from their browser profile directory. It is not within the scope of profile-sync-daemon to modify this behavior; users are encouraged to modify this behavir. For example, refer to the following url for several work-arounds for the Chrome-based browsers: https://wiki.archlinux.org/index.php/Chromium_Tips_and_Tweaks#Cache_in_tmpfs
-.RE
-.PP
-
.SH SETUP
User managed settings are defined in /etc/psd.conf which is included in the package. At a minimum, define which \fBuser\fP(s) will have their profiles managed by \fIpsd\fP and note that at least one user must be defined.
.PP
Example:
.PP
.nf
.fam C
- USERS="bar foo"
+ USERS="bar foo"
+
+ o Optionally uncomment and define which browsers are to be managed in the BROWSERS array. Note that the default is all browsers unless otherwise defined.
+ o Optionally redefine the location of your distro's tmpfs. Do this in the VOLATILE variable. Note that for Arch Linux, the default value of "/tmp" should work just fine.
.fam T
.fi
-.RS
-.IP \(bu 3
-Optionally uncomment and define which browsers are to be managed in the BROWSERS array. Note that the default is all browsers unless otherwise defined.
-.IP \(bu 3
-Optionally redefine the location of your distro's tmpfs. Do this in the VOLATILE variable. Note that for Arch Linux, the default value of "/tmp" should work just fine.
+.SH PARSE MODE
+The parse option can be called to show users exactly what \fIpsd\fP will do based on the /etc/psd.conf entered. Call it like so:
+.PP
+.nf
+.fam C
+ $ profile-sync-daemon parse
+
+.fam T
+.fi
+As you will see in the output and as stated above, if no specific browser \fBor\fP subset of browsers are defined in the BROWSERS array, \fIpsd\fP will sync ALL supported profiles that it finds for the given \fBuser\fP(s).
.SH GENERAL USAGE
-Do not call /usr/bin/profile-sync-daemon directly. Instead use the provided daemon files to interact with \fIpsd\fP. The initial synchronization will occur when the daemon starts. Additionally, cron (if running on your system) will call it to sync \fBor\fP update once per hour. Finally, \fIpsd\fP will sync back a final time when it is called to stop.
-.SH USAGE FOR SYSTEMD USERS
+Do not call /usr/bin/profile-sync-daemon to sync \fBor\fP to unsync directly. Instead use the provided service file. The initial synchronization will occur when the daemon starts. Additionally, cron (if running on your system) will call it to sync \fBor\fP update once per hour. Finally, \fIpsd\fP will sync back a final time when it is called to stop.
+.PP
+FOR SYSTEMD USERS (OFFICIALLY SUPPORTED METHOD)
The provided daemon file should be used to interact with \fIpsd\fP (/usr/lib/systemd/system/psd.service):
.PP
.nf
.fam C
- # systemctl [option] psd.service
+ # systemctl [option] psd.service
.fam T
.fi
@@ -75,18 +81,11 @@ Autostart daemon when system comes up.
.B
disable
Remove daemon from the list of autostart daemons.
-.SH DEBUG MODE
-The debug option can be called to show users exactly what \fIpsd\fP will do based on the /etc/psd.conf entered. Call it like so:
-.PP
-.nf
-.fam C
- $ profile-sync-daemon debug
-
-.fam T
-.fi
-As stated above, if no specific browser \fBor\fP set of browsers are defined in the BROWSERS array, \fIpsd\fP will sync ALL profiles that it finds for the given user.
-.SH SUPPORTED BROWSERS AND CAVEATS
+.SH OTHER INITSCRIPTS
+Other init script may be used to manage the deamon, but they are unsupported by the author.
+.SH SUPPORTED BROWSERS
Currently, the following browsers are auto-detected and managed:
+.RS
.IP \(bu 3
Chromium
.IP \(bu 3
@@ -105,11 +104,39 @@ Opera
Opera-Next
.IP \(bu 3
QupZilla
+.SH CAVEATS FOR FIREFOX AND HEFTIG AURORA USERS ONLY
+The way \fIpsd\fP keeps track of browser profiles and sync targets requires users to have unique name as the last directory for all profiles in their respective $HOME/.mozilla/<browser>/profiles.ini files. Psd will check when it is called to run for this and refuse if you have not satisfied the condition.
+.PP
+The following is an example of a BAD profile that will not pass the test. You can see that although each full path is unique, they both END in the same name! Again, you must modify the profiles.ini and the corresponding directory on the filesystem to correct this if you want to use \fIpsd\fP.
.PP
-For more, see: https://wiki.archlinux.org/index.php/Profile-sync-daemon
+.nf
+.fam C
+ $ cat ~/.mozilla/firefox/profiles.ini
+
+ [General]
+ StartWithLastProfile=1
+
+ [Profile0 for user facade]
+ Name=normal
+ IsRelative=0
+ Path=/mnt/data/docs/facade/mozilla/firefox/myprofile.abc
+ Default=1
+
+ [Profile1 for user happy]
+ Name=proxy
+ IsRelative=0
+ Path=/mnt/data/docs/happy/mozilla/firefox/myprofile.abc
+
+.fam T
+.fi
.SH CONTRIBUTE
Should you wish to contribute to this code, please fork and send a pull request. Source is freely available on github: https://github.com/graysky2/profile-sync-daemon
.SH BUGS
It is known that on slow systems with large profiles, the sync'ing step sometimes take longer than the boot-up of the WM. Therefore, users can theoretically start their browser before the profile has been transitioned to tmpfs. This is particularly prevalent on systems with slow HDDs running systemd.
+.SH ONLINE
+.IP \(bu 3
+Wiki page: https://wiki.archlinux.org/index.php/Profile-sync-daemon
+.IP \(bu 3
+Discussion thread: https://bbs.archlinux.org/viewtopic.php?id=131321
.SH AUTHOR
graysky (graysky AT archlinux DOT us)

0 comments on commit 13b1d53

Please sign in to comment.
Something went wrong with that request. Please try again.