Thank you for your donation!


Idea: Backup and restore configuration across versions
#21
(07-15-2020, 10:33 PM)swizzle Wrote: Backup / restore is fraught but it’d be interesting to store moode & mpd configuration files on something other than the sd card if available. The tricky part is those files aren’t necessarily compatible (e.g. the library db for mpd might change or the old moode config files might be missing data used by a new moode version. You can work around those kinds of things but it introduces more complexity and that makes life harder and programs more prone to bugs.

I'm only talking about saving and restoring those settings that the user makes from the GUI, not databases or system files.  Just download a configuration file.  Then upload it and moOde would read that file and making the necessary configuration changes.  When I say "configuration file," I mean something that does not exist anywhere on moOde today.  I'm talking about a file that would be created for the purpose of backing up and restoring settings.  Essentially, it would contain every setting that the user can make from the GUI, from WiFi SSID and password to Timezone to whether the HDMI port is on.

You'd push the "save config" button and a file download to your PC would occur.  When you hit "restore config," you'd pick a file and upload it.  If you wanted to validate the file, to be relatively sure the user had not tampered with it or selected a file that was not of the correct type, the file could contain an MD5 hash for all of the data beyond the MD5 hash line.

It's no harder to set something by reading it from a backup configuration file than it is to accept user input from the GUI.  moOde would obviously need changes to read from a file, but it's not fundamentally more difficult than accepting user input through the GUI.

Here's an example of what the file could look like to cover most of the System Config settings section:

Code:
:System Config:

:General:

Timezone: America/New_York
Host name: moode
Keyboard layout: us
Browser title: moOde Player

:System Modifications:

CPU governor: On-Demand
Kernel architecture: 32-bit
USB auto-mounter: Udisks-glue (Default)
Integrated WiFi adapter: On
Integrated BT adapter: On
HDMI port: On
LED0 (Activity): On
LED1 (Power): On
Wait for eth0 address: On
USB (UAC2) fix: Off

:Local Display:
Local UI display: Off
Mouse cursor: On
Screen blank: 10 Mins
Wake display on play: Off
Brightness: 255
Pixel aspect ratio: Default
Rotate screen: 0 Deg

That's the same stuff that appears on one of the GUI screens right now, minus the buttons to perform actions (like Expand filesystem or Clear browser cache).  There more further down, but I didn't want to spend the time entering it when the above gets the idea across.

This has another huge advantage:  If some user says that something doesn't work, it would be tremendously useful to say "download your configuration file and post it here so we can see what your settings are."  Obviously, they could be paranoid and hide their SSID, password, or other personal/security information if they so desired.
Reply
#22
We already have System info for a comprehensive dump of all the settings, component versions etc but it's rarely used in troubleshooting because the Moode log and other logs contain what we usually need to see. Heres the source https://github.com/moode-player/moode/bl...sysinfo.sh

I might have mentioned this before but one of the challenges with proposals to do backup/restore settings is that most of the settings have code behind them that modifies the underlying system files and resources. Much of this code for the settings exists in worker.php. Heres the source https://github.com/moode-player/moode/bl...worker.php

All the "settings" code would have to be gathered in some other module and then maintained in sync with any changes that happen to same code blocks in worker.php or the other PHP modules that perform settings updates. A tiny bit of this was already done to support the Auto-configure (moodecfg.txt) process.

The other challenge is along the lines of what @swizzle mentioned where the settings themselves change, are deleted or new ones added, or the underlying system files change which require the code behind the setting to change, etc. This has occurred many, many times over the years since moOde was released.

When you take all this into account what seems like a straight forward backup/restore of settings actually becomes a Settings Migration Process. This is exactly what in-place updates do. Heres an example of two in-place update scripts that performed settings migration.

The 6.3.0 to 6.4.0 update

Code:
#!/bin/bash
#
# moOde audio player (C) 2014 Tim Curtis
# http://moodeaudio.org
#
# This Program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This Program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# 2019-11-24 TC moOde 6.3.0
#

MOODEREL="6.3.0"
PKGDATE="2019-11-24"

readYnInput () {
    while true; do
        read -p "$1" YN
        case $YN in
            [y] ) break;;
            [n] ) break;;
            * ) echo "** Valid entries are y|n";;
        esac
    done
}

cancelUpdate () {
    if [ $# -gt 0 ] ; then
        messageLog "$1"
    fi
    messageLog "** Exiting update"
    exit 1
}

messageLog () {
    echo "$1"
    TIME=$(date +'%Y%m%d %H%M%S')
    echo "$TIME updater: $1" >> /var/log/moode.log
}

echo
echo "****************************************************************"
echo "**"
echo "**  This package updates moOde $MOODEREL and contains important"
echo "**  bug fixes and improvements."
echo "**"
echo "**  WARNING: This update is only supported on non-modfied builds"
echo "**  and ISO images of moOde $MOODEREL"
echo "**"
echo "**  NOTE: Reboot after the update completes."
echo "**"
echo "****************************************************************"
echo

messageLog "Start $PKGDATE update for moOde $MOODEREL"

messageLog "** Version check"
REL=$(awk '/Release: /{print $2;}' /var/www/footer.php | sed 's/,//')
if [[ $REL != $MOODEREL ]] ; then
    cancelUpdate "** Error: this update will only run on moOde $MOODEREL"
fi

echo "** File system check"
if [ -f /var/local/moode.sqsh ] ; then
    cancelUpdate "** Error: this update will only run on un-squashed /var/www"
fi

cd /var/local/www

messageLog "** Step 1-9: Update SQL tables"
# cfg_hash
sqlite3 /var/local/www/db/moode-sqlite3.db "DROP TRIGGER ro_columns" 2> /dev/null
sqlite3 /var/local/www/db/moode-sqlite3.db "DELETE FROM cfg_hash"
sqlite3 /var/local/www/db/moode-sqlite3.db -csv ".import update/cfg_hash.csv cfg_hash"
sqlite3 /var/local/www/db/moode-sqlite3.db "CREATE TRIGGER ro_columns BEFORE UPDATE OF param, value, [action] ON cfg_hash FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'read only'); END;"
# HifiBerry DAC/DAC+ Zero
sqlite3 /var/local/www/db/moode-sqlite3.db "UPDATE cfg_audiodev SET name='HiFiBerry DAC/DAC+ Zero' WHERE name='HiFiBerry DAC Zero'"
# Max ALSA volume
sqlite3 /var/local/www/db/moode-sqlite3.db "UPDATE cfg_system SET param='alsavolume_max', value='100' WHERE param='RESERVED_34'"
# FEAT_BLUETOOTH
sqlite3 /var/local/www/db/moode-sqlite3.db "UPDATE cfg_system SET value='31679' WHERE param='feat_bitmask'"
# Wake display
sqlite3 /var/local/www/db/moode-sqlite3.db "INSERT INTO cfg_system VALUES (128, 'wake_display', '0')"

messageLog "** Step 2-9: Update radio stations"
# Jazz24
sqlite3 /var/local/www/db/moode-sqlite3.db "UPDATE cfg_radio SET station='http://live.wostreaming.net/direct/ppm-jazz24aac256-ibc1' WHERE name='Jazz24'"
cp update/mpd/RADIO/Jazz24.pls /var/lib/mpd/music/RADIO
cp "update/mpd/playlists/Default Playlist.m3u" /var/lib/mpd/playlists

messageLog "** Step 3-9: Install packages"

# NOTE: This bumps to Buster 10.2
messageLog "**  pkg 1-3: Update and upgrade"
apt-get update
DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical apt-get -q -y -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" upgrade
apt-get clean

messageLog "**  pkg 2-3: Install xfsprogs"
apt-get -y install xfsprogs
messageLog "**  pkg 3-3: Install libmosquitto-dev"
apt-get -y install libmosquitto-dev

messageLog "** Step 4-9: Install binaries and scripts"
messageLog "**  bin 1-4: Install MPD 0.21.16"
mv update/other/mpd/mpd-0.21.16 /usr/local/bin/mpd
messageLog "**  bin 2-4: Install shairport-sync 3.3.5 w/mqtt"
mv update/other/shairport-sync/shairport-sync-3.3.5-b1a5056 /usr/local/bin/shairport-sync
messageLog "**  bin 3-4: Install rx 0.4"
mv update/other/trx/rx /usr/local/bin
messageLog "**  bin 4-4: Install tx 0.4"
mv update/other/trx/tx /usr/local/bin
messageLog "**  scr 1-2: Install alsa-capabilities 2.0.1"
mv update/usr/local/bin/alsa-capabilities /usr/local/bin
messageLog "**  scr 2-2: Install moodeutl 1.3.1"
mv update/usr/local/bin/moodeutl /usr/local/bin

messageLog "** Step 5-9: Update bluez-alsa to version 2.0.0"
cp update/other/bluetooth/bluez-alsa-master-2.0.0-4af3ebb.zip ./
unzip -q bluez-alsa-master-2.0.0-4af3ebb.zip
cd bluez-alsa-master
autoreconf --install
mkdir build
cd build
../configure --disable-hcitop --with-alsaplugindir=/usr/lib/arm-linux-gnueabihf/alsa-lib
make
make install
cd ../..
rm -rf bluez-alsa-master*
usermod -a -G audio mpd
cp update/usr/etc/alsa/conf.d/20-bluealsa.conf /usr/etc/alsa/conf.d

messageLog "** Step 6-9: Update moOde sources and configs"
# -- Standard --
cp -r update/etc/* /etc
cp -r update/var/local/www/* /var/local/www
# Save radio logos including usr added
cp -r /var/www/images/radio-logos/ ./
# Purge /var/www
find /var/www/* -delete
# Install new
cp -r update/www/* /var/www
# Restore radio logos and remove temp file
cp -r ./radio-logos/* /var/www/images/radio-logos/
rm -rf ./radio-logos
# Permissions
chmod -R 0755 /var/www
chmod -R 0755 /usr/local/bin
# Update browser title in header.php
TITLE=$(sqlite3 /var/local/www/db/moode-sqlite3.db "SELECT value FROM cfg_system WHERE param='browsertitle'")
/var/www/command/util.sh chg-name browsertitle "moOde Player" "$TITLE"
# -- Extra configs --
# HDMI blanking
sed '/hdmi_drive=2/ a hdmi_blanking=1' /boot/config.txt
# Disable swapfile service
dphys-swapfile swapoff
dphys-swapfile uninstall
systemctl disable dphys-swapfile

KERNEL=4.19.83
BUILD=1277
messageLog "** Step 7-9: Install Linux kernel $KERNEL build $BUILD"

# NOTE: This resets from the apt update/upgrade which installs 4.19.75 but does not update the .firmware_revision file :-0
rm /boot/.firmware_revision

echo "y" | sudo PRUNE_MODULES=1 rpi-update d86586926cfca6aa158cab13888194d53ae23d1b
rm -rf /lib/modules.bak
rm -rf /boot.bak
apt-get clean

messageLog "** Step 8-9: Install drivers for Allo USBridge Signature"
# WiFi driver (Comfast CF-912AC, MrEngman stock)
mv update/other/allo/usbridge_sig/$KERNEL-v7+/8812au.ko /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/wireless
mv update/other/allo/usbridge_sig/$KERNEL-v7+/8812au.conf /etc/modprobe.d/
chmod 0644 /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/wireless/8812au.ko
chmod 0644 /etc/modprobe.d/*.conf
# Eth/USB driver v0.1.4 (Allo enhanced)
mv update/other/allo/usbridge_sig/$KERNEL-v7+/ax88179_178a.ko /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/usb
depmod $KERNEL-v7+

messageLog "** Step 9-9: Sync changes to disk"
# Flush cached writes
sync

cd ~/

The 6.5.2 to 6.6.0 update

Code:
#!/bin/bash
#
# moOde audio player (C) 2014 Tim Curtis
# http://moodeaudio.org
#
# This Program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This Program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# 2020-07-09 TC moOde 6.5.2
#

MOODEREL="6.5.2"
PKGDATE="2020-07-09"

readYnInput () {
    while true; do
        read -p "$1" YN
        case $YN in
            [y] ) break;;
            [n] ) break;;
            * ) echo "** Valid entries are y|n";;
        esac
    done
}

cancelUpdate () {
    if [ $# -gt 0 ] ; then
        messageLog "$1"
    fi
    messageLog "** Exiting update"
    exit 1
}

messageLog () {
    echo "$1"
    TIME=$(date +'%Y%m%d %H%M%S')
    echo "$TIME updater: $1" >> /var/log/moode.log
}

echo
echo "****************************************************************"
echo "**"
echo "**  This package updates moOde $MOODEREL and contains important"
echo "**  bug fixes and improvements."
echo "**"
echo "**  WARNING: This update is only supported on non-modfied builds"
echo "**  and ISO images of moOde $MOODEREL"
echo "**"
echo "**  NOTE: Reboot after the update completes."
echo "**"
echo "****************************************************************"
echo

messageLog "Start $PKGDATE update for moOde $MOODEREL"

messageLog "** Version check"
REL=$(awk '/Release: /{print $2}' /var/www/footer-plain.php | cut -d"<" -f 1)

if [[ $REL != $MOODEREL ]] ; then
    cancelUpdate "** Error: this update will only run on moOde $MOODEREL"
fi

echo "** File system check"
if [ -f /var/local/moode.sqsh ] ; then
    cancelUpdate "** Error: this update will only run on un-squashed /var/www"
fi

cd /var/local/www

messageLog "** Step 1-10: Update SQL tables"
# cfg_hash
sqlite3 /var/local/www/db/moode-sqlite3.db "DROP TRIGGER ro_columns" 2> /dev/null
sqlite3 /var/local/www/db/moode-sqlite3.db "DELETE FROM cfg_hash"
sqlite3 /var/local/www/db/moode-sqlite3.db -csv ".import update/cfg_hash.csv cfg_hash"
sqlite3 /var/local/www/db/moode-sqlite3.db "CREATE TRIGGER ro_columns BEFORE UPDATE OF param, value, [action] ON cfg_hash FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'read only'); END;"
# cfg_system
sqlite3 /var/local/www/db/moode-sqlite3.db "INSERT INTO cfg_system VALUES (138, 'library_thumbnail_columns', '6/2 (Default)')"
sqlite3 /var/local/www/db/moode-sqlite3.db "INSERT INTO cfg_system VALUES (139, 'library_encoded_at', '9')"
sqlite3 /var/local/www/db/moode-sqlite3.db "INSERT INTO cfg_system VALUES (140, 'first_use_help', 'y,y')"
# cfg_radio
sqlite3 /var/local/www/db/moode-sqlite3.db "DROP TABLE cfg_radio"
sqlite3 /var/local/www/db/moode-sqlite3.db "CREATE TABLE cfg_radio (id INTEGER PRIMARY KEY, station CHAR (128), name CHAR (128), type CHAR (8), logo CHAR (128), genre CHAR (32), broadcaster CHAR (32), language CHAR (32), country CHAR (32), region CHAR (32), bitrate CHAR (32), format CHAR (32))"
sqlite3 /var/local/www/db/moode-sqlite3.db -csv ".import update/cfg_radio.csv cfg_radio"
#cfg_audiodev
sqlite3 /var/local/www/db/moode-sqlite3.db "DELETE FROM cfg_audiodev"
sqlite3 /var/local/www/db/moode-sqlite3.db -csv ".import update/cfg_audiodev.csv cfg_audiodev"

messageLog "** Step 2-10: Install station PLS files"
rm /var/lib/mpd/music/RADIO/*.pls
cp update/mpd/RADIO/*.pls /var/lib/mpd/music/RADIO

messageLog "** Step 3-10: Install packages"
messageLog "**  Pkg 1-2: Install zip"
apt-get -y install zip
messageLog "**  Pkg 2-2: Install id3v2"
apt-get -y install id3v2

messageLog "** Step 4-10: Install binaries"
messageLog "**  Bin 1-1: Install MPD 0.21.24"
mpc stop
mv update/other/mpd/mpd-0.21.24 /usr/local/bin/mpd
sqlite3 /var/local/www/db/moode-sqlite3.db "UPDATE cfg_system SET value='0.21.24' WHERE param='mpdver'"

messageLog "** Step 5-10: Build bluez-alsa 2.1.0-49ad348"
cp update/other/bluetooth/bluez-alsa-master-2.1.0-49ad348.zip ./
unzip -q ./bluez-alsa-master-2.1.0-49ad348.zip
cd bluez-alsa-master
# NOTE: Ignore warnings from autoreconf and configure
autoreconf --install
mkdir build
cd build
../configure --disable-hcitop --with-alsaplugindir=/usr/lib/arm-linux-gnueabihf/alsa-lib
make
make install
cd ../..
rm -rf bluez-alsa-master*
cp update/etc/systemd/system/bluealsa-aplay@.service /etc/systemd/system
cp update/lib/systemd/system/bthelper@.service /lib/systemd/system

messageLog "** Step 6-10: Build upmpdcli 1.4.12"
messageLog "**  Src 1-5: Compile libnpupnp 4.0.6"
cp update/other/upmpdcli/libnpupnp-4.0.6.tar.gz ./
tar xfz ./libnpupnp-4.0.6.tar.gz
cd libnpupnp-4.0.6
./configure --prefix=/usr --sysconfdir=/etc
make
make install
cd ..
rm -rf ./libnpupnp-4.0.6
rm libnpupnp-4.0.6.tar.gz
messageLog "**  Src 2-5: Compile libupnpp 0.19.1"
cp update/other/upmpdcli/libupnpp-0.19.1.tar.gz ./
tar xfz ./libupnpp-0.19.1.tar.gz
cd libupnpp-0.19.1
./configure --prefix=/usr --sysconfdir=/etc
make
make install
cd ..
rm -rf ./libupnpp-0.19.1
rm libupnpp-0.19.1.tar.gz
messageLog "**  Src 3-5: Compile upmpdcli 1.4.12-7ea91f5d"
cp update/other/upmpdcli/upmpdcli-1.4.12-master-7ea91f5d.tar.gz ./
tar xfz ./upmpdcli-1.4.12-master-7ea91f5d.tar.gz
cd upmpdcli-master
./autogen.sh
./configure --prefix=/usr --sysconfdir=/etc --disable-spotify
make
make install
cd ..
rm -rf ./upmpdcli-master
rm upmpdcli-1.4.12-master-7ea91f5d.tar.gz
#useradd upmpdcli
#cp update/lib/systemd/system/upmpdcli.service /lib/systemd/system
#cp update/etc/upmpdcli.conf /etc
#chmod 0644 /etc/upmpdcli.conf
systemctl daemon-reload
systemctl disable upmpdcli
messageLog "**  Src 4-5: Compile libupnpp-samples (upexplorer)"
cp update/other/upmpdcli/libupnpp-samples-master.zip ./
unzip -q ./libupnpp-samples-master.zip
cd libupnpp-samples-master
./autogen.sh
./configure
make
make install
cd ..
rm -rf ./libupnpp-samples-master
rm libupnpp-samples-master.zip

messageLog "**  Src 5-5: Install patch for gmusic plugin"
# IS THIS PATCH STILL NEEDED?
cp update/other/upmpdcli/session.py /usr/share/upmpdcli/cdplugins/gmusic

KERNEL=5.4.49
BUILD=1323
messageLog "** Step 7-10: Install Linux kernel $KERNEL build $BUILD"

# Ensure rpi-update runs
rm /boot/.firmware_revision

echo "y" | sudo PRUNE_MODULES=1 rpi-update da3752a358a86014cdcce5fc3be5b18d7ec074c4
rm -rf /lib/modules.bak
rm -rf /boot.bak
apt-get clean

messageLog "** Step 8-10: Install drivers for Allo USBridge SIG"
# 32-bit
# WiFi driver (Comfast CF-912AC, MrEngman stock)
mv update/other/allo/usbridge_sig/$KERNEL-v7+/8812au.ko /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/wireless
mv update/other/allo/usbridge_sig/$KERNEL-v7+/8812au.conf /etc/modprobe.d/
chmod 0644 /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/wireless/8812au.ko
chmod 0644 /etc/modprobe.d/*.conf
# ASIX Eth/USB driver v2.0.0 (Allo enhanced)
mv update/other/allo/usbridge_sig/$KERNEL-v7+/ax88179_178a.ko /lib/modules/$KERNEL"-v7+"/kernel/drivers/net/usb
depmod $KERNEL-v7+
# 64-bit
# WiFi driver (Comfast CF-912AC, MrEngman stock)
mv update/other/allo/usbridge_sig/$KERNEL-v8+/8812au.ko /lib/modules/$KERNEL"-v8+"/kernel/drivers/net/wireless
chmod 0644 /lib/modules/$KERNEL"-v8+"/kernel/drivers/net/wireless/8812au.ko
# ASIX Eth/USB driver v2.0.0 (Allo enhanced)
mv update/other/allo/usbridge_sig/$KERNEL-v8+/ax88179_178a.ko /lib/modules/$KERNEL"-v8+"/kernel/drivers/net/usb
depmod $KERNEL-v8+

messageLog "** Step 9-10: Update moOde sources and configs"
# -- Standard --
# Update /etc
cp -r update/etc/* /etc
# Update /var/local/www
rm /var/local/www/db/moode-sqlite3.db.default
rm /var/local/www/db/moode-sqlite3.db.schema
cp -r update/var/local/www/* /var/local/www
# Update /var/www
find /var/www/* -delete
cp -r update/www/* /var/www
# Permissions
chmod -R 0755 /var/www
chmod -R 0755 /usr/local/bin
# Update browser title in header.php
TITLE=$(sqlite3 /var/local/www/db/moode-sqlite3.db "SELECT value FROM cfg_system WHERE param='browsertitle'")
/var/www/command/util.sh chg-name browsertitle "moOde Player" "$TITLE"
# -- Extras --
# Delete Library tag cache
rm /var/local/www/libcache.json

messageLog "** Step 10-10: Sync changes to disk"
# Flush cached writes
sync

cd ~/

If you examine the scripts you will see changes to setting names and values and the addition of new settings. What this means for example is that a backup of settings from 6.3.0 that were restored to a 6.4.0 image would cause breakage because the old value and meaning of cfg_system param='RESERVED_34 and param='feat_bitmask' would overwrite the new ones.

I'm not trying to rain on the parade but rather to show the complexity and challenge of maintaining settings migration across software versions. 

I you think there is a simpler way to do it then I'm all ears.
Reply
#23
Tim, thanks for that detailed and well thought out reply.

I wasn't really trying to restart the topic, but I did want to clear up some confusion with regards to what I was proposing (a separate, text-based settings file rather than backing up a series of potentially incompatible system files).

The only way that I could see this working is to utilize version numbers.  So if you imported settings from 6.3.0 into 6.4.0, then the 6.4.0 import function would see something like "Settings File Version: 6.3.0" in the uploaded file and know that some parameters could not be imported or would need to be modified by the import process.  It might even spit out something generic like "Some settings could not be imported. Please review and correct all settings prior to restarting moOde."  Then people could complain about not being told which settings.  
Smile

I'd also expect it to refuse to even try to import settings from a later version into an earlier one (for obvious reasons).

I know that you used the examples of 6.3.0 and 6.4.0 to illustrate your point, but I want to make sure that others don't think we are talking about retrofitting the feature into those older versions.

I understand why you aren't ready to jump on this undertaking and make it a priority or even committing to ever doing anything like it.  That's cool.  Just setting the record straight with regards to what looked like another poster misunderstanding my proposal.
Reply


Forum Jump: