Commit 0226fe14 authored by Vasilis Tsiligiannis's avatar Vasilis Tsiligiannis

Change maximum line length to 99 characters

Signed-off-by: Vasilis Tsiligiannis's avatarVasilis Tsiligiannis <acinonyx@openwrt.gr>
parent 7483548f
......@@ -11,9 +11,8 @@ import time
from validators.url import url
import satnogsclient.config
from satnogsclient.settings import (SATNOGS_API_TOKEN, DEFAULT_LOGGING,
SATNOGS_STATION_ID, SATNOGS_STATION_LAT,
SATNOGS_STATION_LON, SATNOGS_STATION_ELEV,
from satnogsclient.settings import (SATNOGS_API_TOKEN, DEFAULT_LOGGING, SATNOGS_STATION_ID,
SATNOGS_STATION_LAT, SATNOGS_STATION_LON, SATNOGS_STATION_ELEV,
SATNOGS_NETWORK_API_URL, GPSD_ENABLED)
from satnogsclient.scheduler.tasks import status_listener, exec_rigctld
from satnogsclient.locator import locator
......@@ -32,8 +31,7 @@ def main():
try:
url(SATNOGS_NETWORK_API_URL)
except ValueError:
raise Exception('Invalid SATNOGS_NETWORK_API_URL: {0}'.format(
SATNOGS_NETWORK_API_URL))
raise Exception('Invalid SATNOGS_NETWORK_API_URL: {0}'.format(SATNOGS_NETWORK_API_URL))
if not SATNOGS_STATION_ID:
raise Exception('SATNOGS_STATION_ID not configured.')
......
......@@ -26,24 +26,20 @@ class Locator(object):
if settings.GPSD_ENABLED is not True:
return
no_timeout = (self.timeout == 0)
if (settings.SATNOGS_STATION_LAT is None
or settings.SATNOGS_STATION_LON is None
if (settings.SATNOGS_STATION_LAT is None or settings.SATNOGS_STATION_LON is None
or settings.SATNOGS_STATION_ELEV is None):
no_timeout = True
LOGGER.info('No default coordinates, GPS timeout disabled')
else:
LOGGER.info('Last coordinates %f %f %d',
settings.SATNOGS_STATION_LAT,
settings.SATNOGS_STATION_LON,
settings.SATNOGS_STATION_ELEV)
LOGGER.info('Last coordinates %f %f %d', settings.SATNOGS_STATION_LAT,
settings.SATNOGS_STATION_LON, settings.SATNOGS_STATION_ELEV)
end_time = time.time() + self.timeout
try:
gpsd = gps.gps(mode=gps.WATCH_ENABLE)
gpsd.next()
LOGGER.info("Waiting for GPS")
while gpsd.fix.mode != gps.MODE_3D and (time.time() < end_time
or no_timeout):
while gpsd.fix.mode != gps.MODE_3D and (time.time() < end_time or no_timeout):
# self.show_location(gpsd)
self.timeout -= 1
gpsd.next()
......@@ -54,9 +50,7 @@ class Locator(object):
settings.SATNOGS_STATION_LAT = gpsd.fix.latitude
settings.SATNOGS_STATION_LON = gpsd.fix.longitude
settings.SATNOGS_STATION_ELEV = gpsd.fix.altitude
LOGGER.info('Updating coordinates %f %f %d',
settings.SATNOGS_STATION_LAT,
settings.SATNOGS_STATION_LON,
settings.SATNOGS_STATION_ELEV)
LOGGER.info('Updating coordinates %f %f %d', settings.SATNOGS_STATION_LAT,
settings.SATNOGS_STATION_LON, settings.SATNOGS_STATION_ELEV)
else:
LOGGER.info("GPS timeout, using last known coordinates")
......@@ -61,13 +61,11 @@ class Commsocket(object):
def connect(self):
try:
LOGGER.debug('Opening TCP socket: %s:%s', self.ip_address,
self.port)
LOGGER.debug('Opening TCP socket: %s:%s', self.ip_address, self.port)
self.sock.connect((self.ip_address, self.port))
self._connected = True
except socket.error:
LOGGER.error('Cannot connect to socket %s:%s', self.ip_address,
self.port)
LOGGER.error('Cannot connect to socket %s:%s', self.ip_address, self.port)
self._connected = False
return self.is_connected
......@@ -78,8 +76,7 @@ class Commsocket(object):
try:
self.sock.send(message.encode('ascii'))
except socket.error:
LOGGER.error('Cannot send to socket %s:%s', self.ip_address,
self.port)
LOGGER.error('Cannot send to socket %s:%s', self.ip_address, self.port)
response = self.sock.recv(self._tasks_buffer_size).decode('ascii')
LOGGER.debug('Received message: %s', response)
......@@ -111,8 +108,7 @@ class Commsocket(object):
try:
self.sock.bind((self._tcp_ip, self._tcp_port))
except socket.error:
LOGGER.error('Cannot bind socket %s:%s', self.ip_address,
self.port)
LOGGER.error('Cannot bind socket %s:%s', self.ip_address, self.port)
self.sock.close()
self._connected = False
self.bind()
......@@ -56,8 +56,7 @@ class Observer(object):
self.tracker_rot = None
self.script_name = None
def setup(self, observation_id, tle, observation_end, frequency, baud,
script_name):
def setup(self, observation_id, tle, observation_end, frequency, baud, script_name):
"""
Sets up required internal variables.
* returns True if setup is ok
......@@ -82,31 +81,34 @@ class Observer(object):
raw_file_extension = 'out'
encoded_file_extension = 'ogg'
waterfall_file_extension = 'dat'
self.observation_raw_file = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, not_completed_prefix,
self.observation_id, self.timestamp, raw_file_extension)
self.observation_ogg_file = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, completed_prefix,
self.observation_id, self.timestamp, encoded_file_extension)
self.observation_raw_file = '{0}/{1}_{2}_{3}.{4}'.format(settings.SATNOGS_OUTPUT_PATH,
not_completed_prefix,
self.observation_id,
self.timestamp,
raw_file_extension)
self.observation_ogg_file = '{0}/{1}_{2}_{3}.{4}'.format(settings.SATNOGS_OUTPUT_PATH,
completed_prefix,
self.observation_id,
self.timestamp,
encoded_file_extension)
self.observation_waterfall_file = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, receiving_waterfall_prefix,
self.observation_id, self.timestamp, waterfall_file_extension)
settings.SATNOGS_OUTPUT_PATH, receiving_waterfall_prefix, self.observation_id,
self.timestamp, waterfall_file_extension)
self.observation_waterfall_png = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, waterfall_prefix,
self.observation_id, self.timestamp, 'png')
settings.SATNOGS_OUTPUT_PATH, waterfall_prefix, self.observation_id, self.timestamp,
'png')
self.observation_receiving_decoded_data = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, receiving_decoded_data_prefix,
self.observation_id, self.timestamp, 'png')
settings.SATNOGS_OUTPUT_PATH, receiving_decoded_data_prefix, self.observation_id,
self.timestamp, 'png')
self.observation_done_decoded_data = '{0}/{1}_{2}_{3}.{4}'.format(
settings.SATNOGS_OUTPUT_PATH, decoded_data_prefix,
self.observation_id, self.timestamp, 'png')
self.observation_decoded_data = '{0}/{1}_{2}'.format(
settings.SATNOGS_OUTPUT_PATH, decoded_data_prefix,
self.observation_id)
settings.SATNOGS_OUTPUT_PATH, decoded_data_prefix, self.observation_id, self.timestamp,
'png')
self.observation_decoded_data = '{0}/{1}_{2}'.format(settings.SATNOGS_OUTPUT_PATH,
decoded_data_prefix,
self.observation_id)
return all([
self.observation_id, self.tle, self.timestamp,
self.observation_end, self.frequency, self.observation_raw_file,
self.observation_ogg_file, self.observation_waterfall_file,
self.observation_id, self.tle, self.timestamp, self.observation_end, self.frequency,
self.observation_raw_file, self.observation_ogg_file, self.observation_waterfall_file,
self.observation_waterfall_png, self.observation_decoded_data
])
......@@ -137,10 +139,11 @@ class Observer(object):
# start thread for rotctl
LOGGER.info('Start gnuradio thread.')
self._gnu_proc = gnuradio_handler.exec_gnuradio(
self.observation_raw_file, self.observation_waterfall_file,
self.frequency, self.baud, self.script_name,
self.observation_decoded_data)
self._gnu_proc = gnuradio_handler.exec_gnuradio(self.observation_raw_file,
self.observation_waterfall_file,
self.frequency, self.baud,
self.script_name,
self.observation_decoded_data)
LOGGER.info('Start rotctrl thread.')
self.run_rot()
# start thread for rigctl
......@@ -158,9 +161,7 @@ class Observer(object):
# PUT client version and metadata
base_url = urljoin(settings.SATNOGS_NETWORK_API_URL, 'observations/')
headers = {
'Authorization': 'Token {0}'.format(settings.SATNOGS_API_TOKEN)
}
headers = {'Authorization': 'Token {0}'.format(settings.SATNOGS_API_TOKEN)}
url = urljoin(base_url, str(self.observation_id))
if not url.endswith('/'):
url += '/'
......@@ -174,10 +175,8 @@ class Observer(object):
resp = requests.put(url,
headers=headers,
data={
'client_version':
satnogsclient.config.VERSION,
'client_metadata':
json.dumps(client_metadata)
'client_version': satnogsclient.config.VERSION,
'client_metadata': json.dumps(client_metadata)
},
verify=settings.SATNOGS_VERIFY_SSL,
stream=True,
......@@ -245,8 +244,7 @@ class Observer(object):
def rename_data_file(self):
if os.path.isfile(self.observation_receiving_decoded_data):
os.rename(self.observation_receiving_decoded_data,
self.observation_done_decoded_data)
os.rename(self.observation_receiving_decoded_data, self.observation_done_decoded_data)
LOGGER.info('Rename data file for uploading finished')
def plot_waterfall(self):
......
......@@ -42,13 +42,7 @@ class Worker(object):
observer_dict = {}
satellite_dict = {}
def __init__(self,
ip,
port,
time_to_stop=None,
frequency=None,
proc=None,
sleep_time=None):
def __init__(self, ip, port, time_to_stop=None, frequency=None, proc=None, sleep_time=None):
"""Initialize worker class."""
self._ip = ip
self._port = port
......@@ -154,12 +148,10 @@ class WorkerTrack(Worker):
observer.elevation = observer_dict["elev"]
observer.date = ephem.Date(start)
satellite = ephem.readtle(str(satellite_dict["tle0"]),
str(satellite_dict["tle1"]),
satellite = ephem.readtle(str(satellite_dict["tle0"]), str(satellite_dict["tle1"]),
str(satellite_dict["tle2"]))
timestamp_max = pytz.utc.localize(
ephem.Date(observer.next_pass(satellite)[2]).datetime())
timestamp_max = pytz.utc.localize(ephem.Date(observer.next_pass(satellite)[2]).datetime())
pin = pinpoint(observer_dict, satellite_dict, timestamp_max)
azi_max = pin["az"].conjugate() * 180 / math.pi
alt_max = pin["alt"].conjugate() * 180 / math.pi
......@@ -182,16 +174,14 @@ class WorkerTrack(Worker):
if timestamp >= midpoint_timestamp:
azi = midpoint_azi + (midpoint_azi - azi)
alt = midpoint_alt + (midpoint_alt - alt)
return (WorkerTrack.normalize_angle(azi),
WorkerTrack.normalize_angle(alt))
return (WorkerTrack.normalize_angle(azi), WorkerTrack.normalize_angle(alt))
return (azi, alt)
def trackobject(self, observer_dict, satellite_dict):
super(WorkerTrack, self).trackobject(observer_dict, satellite_dict)
if settings.SATNOGS_ROT_FLIP and settings.SATNOGS_ROT_FLIP_ANGLE:
self._midpoint = WorkerTrack.find_midpoint(observer_dict,
satellite_dict,
self._midpoint = WorkerTrack.find_midpoint(observer_dict, satellite_dict,
datetime.now(pytz.utc))
LOGGER.info("Antenna midpoint: AZ%.2f EL%.2f %s", *self._midpoint)
self._flip = (self._midpoint[1] >= settings.SATNOGS_ROT_FLIP_ANGLE)
......@@ -202,18 +192,16 @@ class WorkerTrack(Worker):
azi = pin['az'].conjugate() * 180 / math.pi
alt = pin['alt'].conjugate() * 180 / math.pi
if self._flip:
azi, alt = WorkerTrack.flip_coordinates(azi, alt,
datetime.now(pytz.utc),
azi, alt = WorkerTrack.flip_coordinates(azi, alt, datetime.now(pytz.utc),
self._midpoint)
self._azimuth = azi
self._altitude = alt
# read current position of rotator, [0] az and [1] el
position = sock.send("p\n").split('\n')
# if the need to move exceeds threshold, then do it
if (position[0].startswith("RPRT") or
abs(azi - float(position[0])) > settings.SATNOGS_ROT_THRESHOLD
or abs(alt - float(position[1])) >
settings.SATNOGS_ROT_THRESHOLD):
if (position[0].startswith("RPRT")
or abs(azi - float(position[0])) > settings.SATNOGS_ROT_THRESHOLD
or abs(alt - float(position[1])) > settings.SATNOGS_ROT_THRESHOLD):
msg = 'P {0} {1}\n'.format(azi, alt)
LOGGER.debug('Rotctld msg: %s', msg)
sock.send(msg)
......
......@@ -97,10 +97,8 @@ def post_data():
for fil in next(os.walk(settings.SATNOGS_OUTPUT_PATH))[2]:
file_path = os.path.join(*[settings.SATNOGS_OUTPUT_PATH, fil])
if (fil.startswith('receiving_satnogs')
or fil.startswith('receiving_waterfall')
or fil.startswith('receiving_data')
or os.stat(file_path).st_size == 0):
if (fil.startswith('receiving_satnogs') or fil.startswith('receiving_waterfall')
or fil.startswith('receiving_data') or os.stat(file_path).st_size == 0):
continue
if fil.startswith('satnogs'):
observation = {'payload': open(file_path, 'rb')}
......@@ -114,8 +112,7 @@ def post_data():
if '_' not in fil:
continue
observation_id = fil.split('_')[1]
LOGGER.info('Trying to PUT observation data for id: %s',
observation_id)
LOGGER.info('Trying to PUT observation data for id: %s', observation_id)
url = urljoin(base_url, observation_id)
if not url.endswith('/'):
url += '/'
......@@ -132,24 +129,21 @@ def post_data():
if response.status_code == 200:
LOGGER.info('Success: status code 200')
if settings.SATNOGS_COMPLETE_OUTPUT_PATH != "":
os.rename(
os.path.join(settings.SATNOGS_OUTPUT_PATH, fil),
os.path.join(settings.SATNOGS_COMPLETE_OUTPUT_PATH, fil))
os.rename(os.path.join(settings.SATNOGS_OUTPUT_PATH, fil),
os.path.join(settings.SATNOGS_COMPLETE_OUTPUT_PATH, fil))
else:
os.remove(os.path.join(settings.SATNOGS_OUTPUT_PATH, fil))
elif response.status_code == 404:
LOGGER.error('Bad status code: %s', response.status_code)
os.rename(
os.path.join(settings.SATNOGS_OUTPUT_PATH, fil),
os.path.join(settings.SATNOGS_INCOMPLETE_OUTPUT_PATH, fil))
os.rename(os.path.join(settings.SATNOGS_OUTPUT_PATH, fil),
os.path.join(settings.SATNOGS_INCOMPLETE_OUTPUT_PATH, fil))
else:
LOGGER.error('Bad status code: %s', response.status_code)
def get_jobs():
"""Query SatNOGS Network API to GET jobs."""
gps_locator = locator.Locator(settings.SATNOGS_NETWORK_API_QUERY_INTERVAL *
60)
gps_locator = locator.Locator(settings.SATNOGS_NETWORK_API_QUERY_INTERVAL * 60)
gps_locator.update_location()
LOGGER.info('Get jobs started')
url = urljoin(settings.SATNOGS_NETWORK_API_URL, 'jobs/')
......@@ -171,8 +165,7 @@ def get_jobs():
timeout=45)
if not response.status_code == 200:
raise Exception('Status code: {0} on request: {1}'.format(
response.status_code, url))
raise Exception('Status code: {0} on request: {1}'.format(response.status_code, url))
latest_jobs = [str(job['id']) for job in response.json()]
for job in SCHEDULER.get_jobs():
......@@ -200,12 +193,10 @@ def status_listener():
SCHEDULER.remove_all_jobs()
interval = settings.SATNOGS_NETWORK_API_QUERY_INTERVAL
SCHEDULER.add_job(get_jobs, 'interval', minutes=interval)
msg = 'Registering `get_jobs` periodic task ({0} min. interval)'.format(
interval)
msg = 'Registering `get_jobs` periodic task ({0} min. interval)'.format(interval)
LOGGER.info(msg)
interval = settings.SATNOGS_NETWORK_API_POST_INTERVAL
msg = 'Registering `post_data` periodic task ({0} min. interval)'.format(
interval)
msg = 'Registering `post_data` periodic task ({0} min. interval)'.format(interval)
LOGGER.info(msg)
SCHEDULER.add_job(post_data, 'interval', minutes=interval)
os.environ['GNURADIO_SCRIPT_PID'] = '0'
......
......@@ -17,18 +17,12 @@ def _cast_or_none(func, value):
# Ground station information
SATNOGS_API_TOKEN = environ.get('SATNOGS_API_TOKEN', None)
SATNOGS_PRE_OBSERVATION_SCRIPT = environ.get('SATNOGS_PRE_OBSERVATION_SCRIPT',
None)
SATNOGS_POST_OBSERVATION_SCRIPT = environ.get(
'SATNOGS_POST_OBSERVATION_SCRIPT', None)
SATNOGS_STATION_ID = _cast_or_none(int, environ.get('SATNOGS_STATION_ID',
None))
SATNOGS_STATION_LAT = _cast_or_none(float,
environ.get('SATNOGS_STATION_LAT', None))
SATNOGS_STATION_LON = _cast_or_none(float,
environ.get('SATNOGS_STATION_LON', None))
SATNOGS_STATION_ELEV = _cast_or_none(int,
environ.get('SATNOGS_STATION_ELEV', None))
SATNOGS_PRE_OBSERVATION_SCRIPT = environ.get('SATNOGS_PRE_OBSERVATION_SCRIPT', None)
SATNOGS_POST_OBSERVATION_SCRIPT = environ.get('SATNOGS_POST_OBSERVATION_SCRIPT', None)
SATNOGS_STATION_ID = _cast_or_none(int, environ.get('SATNOGS_STATION_ID', None))
SATNOGS_STATION_LAT = _cast_or_none(float, environ.get('SATNOGS_STATION_LAT', None))
SATNOGS_STATION_LON = _cast_or_none(float, environ.get('SATNOGS_STATION_LON', None))
SATNOGS_STATION_ELEV = _cast_or_none(int, environ.get('SATNOGS_STATION_ELEV', None))
GPSD_ENABLED = _cast_or_none(bool, environ.get('GPSD_ENABLED', None))
# Output paths
......@@ -45,13 +39,11 @@ for p in [
if p != "" and not os.path.exists(p):
os.mkdir(p)
SATNOGS_REMOVE_RAW_FILES = bool(
strtobool(environ.get('SATNOGS_REMOVE_RAW_FILES', 'True')))
SATNOGS_REMOVE_RAW_FILES = bool(strtobool(environ.get('SATNOGS_REMOVE_RAW_FILES', 'True')))
SATNOGS_VERIFY_SSL = bool(strtobool(environ.get('SATNOGS_VERIFY_SSL', 'True')))
DEFAULT_SQLITE_PATH = path.join(SATNOGS_APP_PATH, 'jobs.sqlite')
SATNOGS_SQLITE_URL = environ.get('SATNOGS_SQLITE_URL',
'sqlite:///' + DEFAULT_SQLITE_PATH)
SATNOGS_SQLITE_URL = environ.get('SATNOGS_SQLITE_URL', 'sqlite:///' + DEFAULT_SQLITE_PATH)
SATNOGS_NETWORK_API_URL = environ.get('SATNOGS_NETWORK_API_URL',
'https://network.satnogs.org/api/')
......@@ -89,8 +81,7 @@ RIG_SERIAL_SPEED = ""
# Common script parameters
SATNOGS_DOPPLER_CORR_PER_SEC = environ.get('SATNOGS_DOPPLER_CORR_PER_SEC',
None)
SATNOGS_DOPPLER_CORR_PER_SEC = environ.get('SATNOGS_DOPPLER_CORR_PER_SEC', None)
SATNOGS_LO_OFFSET = environ.get('SATNOGS_LO_OFFSET', None)
SATNOGS_PPM_ERROR = environ.get('SATNOGS_PPM_ERROR', None)
SATNOGS_IF_GAIN = environ.get('SATNOGS_IF_GAIN', None)
......@@ -100,8 +91,7 @@ SATNOGS_ANTENNA = environ.get('SATNOGS_ANTENNA', None)
SATNOGS_DEV_ARGS = environ.get('SATNOGS_DEV_ARGS', None)
ENABLE_IQ_DUMP = bool(strtobool(environ.get('ENABLE_IQ_DUMP', 'False')))
IQ_DUMP_FILENAME = environ.get('IQ_DUMP_FILENAME', None)
DISABLE_DECODED_DATA = bool(
strtobool(environ.get('DISABLE_DECODED_DATA', 'False')))
DISABLE_DECODED_DATA = bool(strtobool(environ.get('DISABLE_DECODED_DATA', 'False')))
# Waterfall settings
SATNOGS_WATERFALL_AUTORANGE = environ.get('SATNOGS_WATERFALL_AUTORANGE', True)
......
......@@ -37,15 +37,13 @@ def get_gnuradio_info():
client_metadata['radio']['version'] = 'invalid'
else:
if 'version' in gr_satnogs_info:
client_metadata['radio']['version'] = gr_satnogs_info[
'version']
client_metadata['radio']['version'] = gr_satnogs_info['version']
else:
client_metadata['radio']['version'] = 'unknown'
return client_metadata
def exec_gnuradio(observation_file, waterfall_file, freq, baud, script_name,
decoded_data):
def exec_gnuradio(observation_file, waterfall_file, freq, baud, script_name, decoded_data):
if not script_name:
script_name = client_settings.GNURADIO_SCRIPT_FILENAME
device = client_settings.SATNOGS_RX_DEVICE
......@@ -68,10 +66,7 @@ def exec_gnuradio(observation_file, waterfall_file, freq, baud, script_name,
] and baud > 0:
args += ['--baudrate=' + str(int(baud))]
if client_settings.SATNOGS_DOPPLER_CORR_PER_SEC:
args += [
'--doppler-correction-per-sec=' +
client_settings.SATNOGS_DOPPLER_CORR_PER_SEC
]
args += ['--doppler-correction-per-sec=' + client_settings.SATNOGS_DOPPLER_CORR_PER_SEC]
if client_settings.SATNOGS_LO_OFFSET:
args += ['--lo-offset=' + client_settings.SATNOGS_LO_OFFSET]
if client_settings.SATNOGS_RIG_PORT:
......@@ -89,10 +84,7 @@ def exec_gnuradio(observation_file, waterfall_file, freq, baud, script_name,
if client_settings.SATNOGS_DEV_ARGS:
args += ['--dev-args=' + client_settings.SATNOGS_DEV_ARGS]
if client_settings.ENABLE_IQ_DUMP:
args += [
'--enable-iq-dump=' +
str(int(client_settings.ENABLE_IQ_DUMP is True))
]
args += ['--enable-iq-dump=' + str(int(client_settings.ENABLE_IQ_DUMP is True))]
if client_settings.IQ_DUMP_FILENAME:
args += ['--iq-file-path=' + client_settings.IQ_DUMP_FILENAME]
if not client_settings.DISABLE_DECODED_DATA:
......
......@@ -37,9 +37,12 @@ console_scripts =
satnogs-client = satnogsclient:main
[flake8]
max-line-length = 180
max-line-length = 99
exclude = satnogsclient/_version.py,versioneer.py,doc,build,.tox
[yapf]
column_limit = 99
# See the docstring in versioneer.py for instructions. Note that you must
# re-run 'versioneer.py setup' after changing this section, and commit the
# resulting files.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment