Commit 7792a233 authored by Aral Balkan's avatar Aral Balkan

Update winston logger dependency to 2.3.1. Apply API changes

parent f564aa2e
......@@ -33,6 +33,8 @@ CleanCSS = require 'clean-css'
class App
instance = null
log: null
homeDirectory: null
privateDirectory: null
config: null
......@@ -104,16 +106,15 @@ class App
# Set up logging.
logFile = path.join @logsDirectory, 'App.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info' , formatter: (options) -> return options.message})
new (winston.transports.File)({ filename: logFile, level: 'debug'})
]
logger.extend @
@debug "App initiated. Logs at #{logFile}"
@log.debug "App initiated. Logs at #{logFile}"
@info "\n▲❤ Better is initialising.\n"
@log.info "\n▲❤ Better is initialising.\n"
# Load the configuration
......@@ -185,7 +186,7 @@ class App
nameDetails: trackerNameDetails,
formattedLink: formattedLink
.catch (e) =>
@error "Fatal: Refresh content error.\n\n#{e}"
@log.error "Fatal: Refresh content error.\n\n#{e}"
console.trace()
process.exit 1
......@@ -234,11 +235,11 @@ class App
for fileName, lastModifiedDate of themeCachePreviousModifiedTimes
if lastModifiedDate != themeCacheCurrentModifiedTimes[fileName]
# There are changes, generate themes.
@info "\t✓ Change to theme found in #{fileName}, generating theme."
@log.info "\t✓ Change to theme found in #{fileName}, generating theme."
@themeHasNotChanged = false
return true
@info "\t✓ No changes to theme; not generating theme."
@log.info "\t✓ No changes to theme; not generating theme."
return false
......@@ -247,7 +248,7 @@ class App
Promise.try =>
startTime = new Date
if @weNeedToGenerateThemes()
@info "\t✓ Generating theme…"
@log.info "\t✓ Generating theme…"
fs.removeAsync @themeForAppDataDirectory
.then => fs.removeAsync @themeForSiteDataDirectory
......@@ -257,15 +258,15 @@ class App
.then => fs.copyAsync @siteThemeDirectory, @themeForSiteDataDirectory
.then => @generateStylesForThemeAtDirectory @themeForAppDataDirectory
.then => @generateStylesForThemeAtDirectory @themeForSiteDataDirectory
.then => endTime = new Date; @info "\t✓ Theme generated. (#{endTime - startTime} ms)"
.then => endTime = new Date; @log.info "\t✓ Theme generated. (#{endTime - startTime} ms)"
.catch (e) =>
@error "Fatal: Could not generate themes.\n\n#{e}"
@log.error "Fatal: Could not generate themes.\n\n#{e}"
console.trace()
process.exit 1
else
@info "\t✓ Theme unchanged; not re-generating."
@log.info "\t✓ Theme unchanged; not re-generating."
#endTime = new Date
#@info "Took: #{endTime - startTime} ms"
#@log.info "Took: #{endTime - startTime} ms"
return true
......@@ -279,7 +280,7 @@ class App
@generateThemes().then =>
@debug 'Loading themes.'
@log.debug 'Loading themes.'
appTemplatesDirectory = path.join @appThemeDirectory, 'templates'
siteTemplatesDirectory = path.join @siteThemeDirectory, 'templates'
......
......@@ -46,6 +46,8 @@ require './StringExtensions' # for String::trim()
class Blockdown
log: null
site: "site"
app: "app"
......@@ -103,19 +105,17 @@ class Blockdown
# Set up logging.
logFile = path.join app.logsDirectory, 'Blockdown.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info'})
new (winston.transports.File)({ filename: logFile, level: 'debug' })
]
logger.extend @
# Decide cache policy
@decideCachePolicy()
# @debug "Blockdown initiated. Logs at #{logFile}"
# @log.debug "Blockdown initiated. Logs at #{logFile}"
#
......@@ -152,7 +152,7 @@ class Blockdown
# This is a template, check if it has changed
if lastModifiedDate != themeCacheCurrentModifiedTimes[fileName]
# There are changes to a theme template, do not use the cache.
@info "\t✓ Theme template changed (#{fileName}), *not* using cache."
@log.info "\t✓ Theme template changed (#{fileName}), *not* using cache."
@useContentCache = false
break
......@@ -178,7 +178,7 @@ class Blockdown
# Render data for site.
#
renderDataForSite: =>
# @debug "Rendering data for site."
# @log.debug "Rendering data for site."
@isRenderingDataForSite = true
@isRenderingDataForApp = false
......@@ -206,7 +206,7 @@ class Blockdown
#console.log "\t✓ Using cache"
return true
else
# @info "Not using cache: returning promise to delete the data for site directory from Promise.try"
# @log.info "Not using cache: returning promise to delete the data for site directory from Promise.try"
return fs.removeAsync "#{app.dataForSiteDirectory}/*"
.then =>
@renderBlockdown app.contentDirectory, app.dataForSiteDirectory, app.siteTheme
......@@ -246,7 +246,7 @@ class Blockdown
# Render data for apps.
#
renderDataForApp: =>
# @debug "Rendering data for apps."
# @log.debug "Rendering data for apps."
@isRenderingDataForSite = false
@isRenderingDataForApp = true
......@@ -271,10 +271,10 @@ class Blockdown
Promise.try =>
# Clean the data directory before starting only if we’re not using the cache.
if @useContentCache
# @info "Using cache: returning true from Promise.try"
# @log.info "Using cache: returning true from Promise.try"
return true
else
# @info "Not using cache: returning promise to delete the data for app directory from Promise.try"
# @log.info "Not using cache: returning promise to delete the data for app directory from Promise.try"
return fs.removeAsync "#{app.dataForAppDirectory}/*"
.then =>
@renderBlockdown app.contentDirectory, app.dataForAppDirectory, app.appTheme
......@@ -356,7 +356,7 @@ class Blockdown
# Copies assets to the destination
#
copyAssets: (assets, destinationPath) =>
# @info "Copying assets: #{assets.path} to #{destinationPath}"
# @log.info "Copying assets: #{assets.path} to #{destinationPath}"
(globAsync "#{assets.path}/**/*@(#{assets.extensions})")
.series (file) =>
......@@ -369,7 +369,7 @@ class Blockdown
destinationPathForDirectory = path.join destinationPath, dirName
destinationPathForFile = path.join destinationPathForDirectory, baseName
# @info "Copying asset: #{file}"
# @log.info "Copying asset: #{file}"
fs.ensureDirAsync destinationPathForDirectory
.then =>
......@@ -394,7 +394,7 @@ class Blockdown
#
removeDeletedFiles: =>
# @info "Looking for deleted files in the content…"
# @log.info "Looking for deleted files in the content…"
# Only relevant for content at the moment (as we do a complete rebuild
# whenever the theme changes and this will not even get called).
......@@ -431,7 +431,7 @@ class Blockdown
#
pathToDelete = path.join pathToDelete, file
# @info "About to delete #{pathToDelete} from the build folder (data)."
# @log.info "About to delete #{pathToDelete} from the build folder (data)."
fs.removeSync pathToDelete
......@@ -456,7 +456,7 @@ class Blockdown
(globAsync "#{contentPath}/**/*.md", {})
.series (file) =>
# @info "Reading blockdown file: #{file}"
# @log.info "Reading blockdown file: #{file}"
#
# Check cache
......@@ -465,9 +465,9 @@ class Blockdown
parsedFilePath = (path.parse file)
filePathKey = path.join parsedFilePath.dir.split('/')[5..].join('/'), parsedFilePath.base
# @info "File path key: #{filePathKey}"
# @info "Current: #{@contentCurrentModifiedTimes[filePathKey]}"
# @info "Previous: #{@contentPreviousModifiedTimes[filePathKey]}"
# @log.info "File path key: #{filePathKey}"
# @log.info "Current: #{@contentCurrentModifiedTimes[filePathKey]}"
# @log.info "Previous: #{@contentPreviousModifiedTimes[filePathKey]}"
if @contentCurrentModifiedTimes[filePathKey] == @contentPreviousModifiedTimes[filePathKey]
......@@ -477,14 +477,14 @@ class Blockdown
#
################################################################################
# @info "Cache: Using existing file."
# @log.info "Cache: Using existing file."
#
# Add the rule partial to the rules array
#
rulePartialFilePath = path.join app.cacheDirectory, 'rule-partials', "#{filePathKey}.rule.json"
# @info "Rule partial file path: #{rulePartialFilePath}"
# @log.info "Rule partial file path: #{rulePartialFilePath}"
if fs.existsSync rulePartialFilePath
rulePartialString = fs.readFileSync rulePartialFilePath, 'utf-8'
......@@ -594,8 +594,8 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
titleOfPageClean: titleOfPageClean
listItem: listItem
# @info "Index cache file: #{indexCacheFile}"
# @info "Index cache object:"
# @log.info "Index cache file: #{indexCacheFile}"
# @log.info "Index cache object:"
# console.log indexCacheObject
fs.outputJSONSync indexCacheFile, indexCacheObject
......@@ -723,7 +723,7 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
@renderHomePageStatistics().then (homePageStatisticsHTML) =>
@renderPage 'home', dataPath, '/index.html', {statistics: homePageStatisticsHTML}, 'home'
.then =>
#@info "Indices:"
#@log.info "Indices:"
#console.log indices
return indices
......@@ -874,13 +874,13 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
# Hook into the marked Markdown renderer for the Blockdown MSON code sections.
#
codeRenderer: (code, language) =>
# @info "Language: #{language}, Code: >#{code}<"
# @log.info "Language: #{language}, Code: >#{code}<"
originalCode = code
if language is 'mson'
# @info "Rendering Blockdown MSON in #{@pathOfTheFileCurrentlyBeingRendered}"
# @log.info "Rendering Blockdown MSON in #{@pathOfTheFileCurrentlyBeingRendered}"
# Create the dictionary entry in the table of files currently being rendered
# to their rule partials if one does not already exist.
......@@ -1018,7 +1018,7 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
# using specialised templates.
#
listItemRenderer: (text) =>
# @info "List item: #{text}"
# @log.info "List item: #{text}"
# Trackers badge.
if text.startsWith '(Trackers)' or text.startsWith '(trackers)' then return @renderTrackersBadgeListItem text
......@@ -1058,7 +1058,7 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
#
renderStaticBadgeListItem: (title, explanation) =>
# @info "About to render static badge with title: #{title}"
# @log.info "About to render static badge with title: #{title}"
title = title.replace('(', '').replace(')', '')
......@@ -1146,7 +1146,7 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
return listItem
else
# Corrupted content
@error "Corrupted content on #{@pathOfTheFileCurrentlyBeingRendered}: list item: #{text}. Skipping Blockdown render and defaulting to Markdown."
@log.error "Corrupted content on #{@pathOfTheFileCurrentlyBeingRendered}: list item: #{text}. Skipping Blockdown render and defaulting to Markdown."
return @markdownRenderer.listitem text
#
......@@ -1315,7 +1315,7 @@ Better is a Safari content blocker for <a href='https://itunes.apple.com/us/app/
#
# Make a note on the page itself but don’t stop parsing.
#
@error e
@log.error e
indexOfContentFragment = @pathOfTheFileCurrentlyBeingRendered.indexOf('/content/')
filePathFragment = @pathOfTheFileCurrentlyBeingRendered[indexOfContentFragment..].replace('/content/', '')
urlPrefixToTheSource = 'https://source.ind.ie/better/content/blob/master/'
......
......@@ -40,6 +40,8 @@ WebkitContentBlockerRulesToEasyList = require './WebkitContentBlockerRulesToEasy
class Builder
instance = null
log: null
# This is the authorisation object we send
authorisation: null
......@@ -54,13 +56,12 @@ class Builder
# Set up logging.
logFile = path.join app.logsDirectory, 'Builder.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info' , formatter: (options) -> return options.message})
new (winston.transports.File)({ filename: logFile, level: 'debug' })
]
logger.extend @
# Convenience: important paths
@sshPath = path.join app.privateDirectory, 'ssh'
......@@ -72,7 +73,7 @@ class Builder
@authorisation =
callbacks:
credentials: (url, userName) =>
@info "Providing credentials…"
@log.info "Providing credentials…"
NodeGit.Cred.sshKeyNew userName, @publicSSHKeyPath, @privateSSHKeyPath, ''
# certificateCheck: () ->
# return 1
......@@ -92,7 +93,7 @@ class Builder
.then @renderBlockdown
.then @updateRepositories
.then =>
@info "\t✓ Blockdown content ready."
@log.info "\t✓ Blockdown content ready."
# If the app is running in development, fire off a
# notification to let people know that rendering is done.
......@@ -108,7 +109,7 @@ class Builder
# notification to let people know that rendering is done.
exec "osascript -e 'display notification \"✗ Error\" with title \"Better\" sound name \"Basso\"'"
@error "Build error.\n\n#{e}"
@log.error "Build error.\n\n#{e}"
console.trace()
process.exit 1
......@@ -118,7 +119,7 @@ class Builder
updateCache: =>
Promise.try =>
if app.isRunningInProduction
@info "\t✓ App is running in production, not creating cache."
@log.info "\t✓ App is running in production, not creating cache."
else
(new Cache).build()
......@@ -129,20 +130,20 @@ class Builder
startTime = new Date
# @info 'Rendering content…'
# @log.info 'Rendering content…'
blockdown = new Blockdown
easyListConverter = new WebkitContentBlockerRulesToEasyList
blockdown.renderDataForSite()
.then =>
@info "\t✓ Rendered content for site."
@log.info "\t✓ Rendered content for site."
blockdown.renderDataForApp()
.then =>
@info "\t✓ Rendered content for data."
@log.info "\t✓ Rendered content for data."
easyListConverter.convert()
.then =>
@info "\t✓ Rendered EasyList version."
@log.info "\t✓ Rendered EasyList version."
duration = ((new Date) - startTime)
durationUnit = "ms"
......@@ -150,7 +151,7 @@ class Builder
duration = duration / 1000
durationUnit = "seconds"
@info "\t✓ Render complete. (#{duration} #{durationUnit})"
@log.info "\t✓ Render complete. (#{duration} #{durationUnit})"
#
......@@ -158,7 +159,7 @@ class Builder
# (Fetches and merges the latest changes from the content repository.)
#
pullContentRepository: =>
@debug 'Pulling content repository.'
@log.debug 'Pulling content repository.'
return @pullRepository app.contentDirectory
.then ->
app.refreshContentIndex()
......@@ -190,7 +191,7 @@ class Builder
# Merge
repository.mergeBranches 'master', 'origin/master'
.catch (e) =>
@error "Build error.\n\n#{e}"
@log.error "Build error.\n\n#{e}"
console.trace()
process.exit 1
......@@ -199,13 +200,13 @@ class Builder
#
updateRepositories: =>
# @info 'Updating the data repositories for the app and the site…'
# @log.info 'Updating the data repositories for the app and the site…'
@updateRepository app.dataForSiteDirectory
.then =>
@updateRepository app.dataForAppDirectory
.then =>
@info "\t✓ Data repository updates complete."
@log.info "\t✓ Data repository updates complete."
#
......@@ -222,11 +223,11 @@ class Builder
NodeGit.Repository.open repositoryPath
.then (repositoryResult) =>
@debug "Update: got repository open result for #{repositoryPath}"
@log.debug "Update: got repository open result for #{repositoryPath}"
repository = repositoryResult
repository.index()
.then (indexResult) =>
@debug 'Update: got index result'
@log.debug 'Update: got index result'
index = indexResult
index.read 1 # Force a read of the index into memory.
# The ADD_CHECK_PATHSPEC should make addAll function like
......@@ -234,12 +235,12 @@ class Builder
index.addAll '*', NodeGit.Index.ADD_OPTION.ADD_CHECK_PATHSPEC
# DEBUG: Dry run
# index.addAll '*', NodeGit.Index.ADD_OPTION.ADD_CHECK_PATHSPEC, (path, matchedPathSpec, payload) =>
# @info "Path:"
# @info path
# @info "matchedPathSpec:"
# @info matchedPathSpec
# @info "payload:"
# @info payload
# @log.info "Path:"
# @log.info path
# @log.info "matchedPathSpec:"
# @log.info matchedPathSpec
# @log.info "payload:"
# @log.info payload
# return 1
.then (result) =>
......@@ -269,9 +270,9 @@ class Builder
if repositoryPath == app.dataForSiteDirectory
@pushToSite repository
.then =>
@debug "Repository update complete for: #{repositoryPath}"
@log.debug "Repository update complete for: #{repositoryPath}"
.catch ((e) =>
@error "Git error: #{e}")
@log.error "Git error: #{e}")
#
......@@ -284,7 +285,7 @@ class Builder
NodeGit.Remote.lookup repository, 'site'
.then (site) =>
siteURL = site.url()
@debug "Got site: #{siteURL}"
@log.debug "Got site: #{siteURL}"
# NodeGit.Remote.initCallbacks @authorisation
site.push ['refs/heads/master:refs/heads/master'], @authorisation
......@@ -292,7 +293,7 @@ class Builder
# repository.defaultSignature()
# 'Push to master'
.then (pushResult) =>
@debug "Site push result: #{pushResult}"
@log.debug "Site push result: #{pushResult}"
# If we pushed locally, we must manually trigger the post-receive hook.
# (Nodegit — actually libgit2 — doesn’t trigger web hooks but, according to
......@@ -303,19 +304,19 @@ class Builder
if siteURL[0] == '/'
siteLocalPostReceiveHooksPath = path.join app.homeDirectory, '.private', 'site-local', 'data.git', 'hooks'
siteLocalPostReceiveHookPath = path.join siteLocalPostReceiveHooksPath, 'post-receive'
@debug "We pushed locally, about to manually trigger the post-receive hook at #{siteLocalPostReceiveHookPath}"
@log.debug "We pushed locally, about to manually trigger the post-receive hook at #{siteLocalPostReceiveHookPath}"
spawn siteLocalPostReceiveHookPath, [], {cwd:siteLocalPostReceiveHooksPath}
.progress (childProcess) =>
@debug "Child process running with ID: #{childProcess.pid}"
@log.debug "Child process running with ID: #{childProcess.pid}"
childProcess.stdout.on 'data', (data) =>
@debug "Child stdout: #{data.toString()}"
@log.debug "Child stdout: #{data.toString()}"
childProcess.stderr.on 'data', (data) =>
@debug "Child stderr: #{data.toString()}"
@log.debug "Child stderr: #{data.toString()}"
.then =>
@debug "Local push complete."
@log.debug "Local push complete."
.fail (error) =>
@error "Local push failed with error: #{error}"
@log.error "Local push failed with error: #{error}"
@debug 'Completed update.'
@log.debug 'Completed update.'
module.exports = Builder
\ No newline at end of file
......@@ -34,6 +34,8 @@ class GitlabWebhookServer
instance = null
log: null
express: null
server: null
......@@ -47,14 +49,14 @@ class GitlabWebhookServer
# Set up logging.
logFile = path.join app.logsDirectory, 'GitlabWebhookServer.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info' , formatter: (options) -> return options.message})
new (winston.transports.File)({ filename: logFile, level: 'debug' })
]
logger.extend @
@debug "GitlabWebhookServer session started. Logs at #{logFile}"
@log.debug "GitlabWebhookServer session started. Logs at #{logFile}"
# Start the server.
@start()
......@@ -65,7 +67,7 @@ class GitlabWebhookServer
start: =>
@debug 'Starting the Gitlab webhook server…'
@log.debug 'Starting the Gitlab webhook server…'
#
# Set up Express
......@@ -87,7 +89,7 @@ class GitlabWebhookServer
#
express.post '/build/:token', (request, response) =>
@debug "Got request with token #{request.params.token}…"
@log.debug "Got request with token #{request.params.token}…"
if typeof request.params.token == 'undefined' || request.params.token != app.token
response.status(403).end()
......@@ -108,7 +110,7 @@ class GitlabWebhookServer
@server = http.createServer express
@server.listen port, () =>
@info "❤ Gitlab Webhook Server runnning at http://localhost:#{port}\n"
@log.info "❤ Gitlab Webhook Server runnning at http://localhost:#{port}\n"
@express = express
......
......@@ -36,6 +36,8 @@ Promise = require 'thrush'
class WebkitContentBlockerRulesToBindDNS
log: null
constructor: ->
@domainPrefixRegex = '^[^:]+://+([^:/]+\\.)?'
@blockerListPath = path.join app.dataForAppDirectory, 'blockerList.json'
......@@ -43,13 +45,12 @@ class WebkitContentBlockerRulesToBindDNS
# Set up logging.
logFile = path.join app.logsDirectory, 'WebkitContentBlockerRulesToBindDNS.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info' , formatter: (options) -> return options.message})
new (winston.transports.File)({ filename: logFile, level: 'debug' })
]
logger.extend @
convert: =>
......@@ -91,7 +92,7 @@ class WebkitContentBlockerRulesToBindDNS
#
domainsToSkip = ['google.com', 'yimg.com', 'yandex.ru', 'yahoo.com', 'pinterest.com', 'msn.com', 'mail.ru', 'amazonaws.com', 'linkedin.com', 'gstatic.com', 'google.se', 'googleapis.com', 'bing.com', 'bildstatic.de', 'baidu.com', 'adobedtm.com', 'theatlantic.com', 'googlecode.com']
# @info "#{action['type']}, #{trigger['load-type'][0]}"
# @log.info "#{action['type']}, #{trigger['load-type'][0]}"
#
# We only translate block rules for third-party domains.
......@@ -115,7 +116,7 @@ class WebkitContentBlockerRulesToBindDNS
bindDNSComment = "\n; Block https://better.fyi/trackers/#{domain}\n"
else
# @info "Skipping domain: #{domain} (#{domainToSkip})"
# @log.info "Skipping domain: #{domain} (#{domainToSkip})"
numberOfRulesSkipped += 1
continue
else
......@@ -165,16 +166,16 @@ class WebkitContentBlockerRulesToBindDNS
stats += "Rules skipped: #{numberOfRulesSkipped}\n"
# Debug – log the list to console.
# @info bindDNS
# @log.info bindDNS
# Debug – show stats.
@info stats
@log.info stats
# Save the EasyList version.
fs.outputFileSync @bindDNSSiteOutputPath, bindDNS
# Debug – log the output path.
@info "Bind DNS response-policy file is at: #{@bindDNSSiteOutputPath}"
@log.info "Bind DNS response-policy file is at: #{@bindDNSSiteOutputPath}"
return true
......
......@@ -30,6 +30,8 @@ Promise = require 'thrush'
class WebkitContentBlockerRulesToEasyList
log: null
constructor: ->
@domainPrefixRegex = '^[^:]+://+([^:/]+\\.)?'
@blockerListPath = path.join app.dataForAppDirectory, 'blockerList.json'
......@@ -37,13 +39,12 @@ class WebkitContentBlockerRulesToEasyList
# Set up logging.
logFile = path.join app.logsDirectory, 'WebkitContentBlockerRulesToEasyList.log'
logger = new winston.Logger
@log = new winston.Logger
transports:
[
new (winston.transports.Console)({ level: 'info' , formatter: (options) -> return options.message})
new (winston.transports.File)({ filename: logFile, level: 'debug' })
]
logger.extend @
convert: =>
......@@ -205,10 +206,10 @@ class WebkitContentBlockerRulesToEasyList
stats += "Rules constrained by domain: #{numberOfRulesConstrainedByDomain}\n"
# Debug – log the list to console.
# @info easyList
# @log.info easyList
# Debug – show stats.
# @info stats
# @log.info stats
# Save the EasyList version.
fs.outputFileSync @easyListSiteOutputPath, easyList
......
......@@ -32,6 +32,6 @@
"should": "13.1.0",
"thrush": "0.0.7",
"touch": "3.1.0",
"winston": "1.1.1"
"winston": "2.3.1"
}
}
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