Burger King has long been known as the chief rival to McDonalds and home to one of the scariest and creepiest mascots in advertising history. But for me, the local Burger King is an amazing anomaly in lessons on running a business.
See, this business has managed to consistently deliver poor service over the years while still staying in business. It became a joke in my family that ordering from the local business was a game in Russian Roulette. Orders never seemed to come out right. There was a streak where I think I had 5 visits and, without fail, they failed to get the order correct. Once I went there and ordered just a soda.
One soda.
They gave me the wrong one.
Really? I ordered one lousy soft drink and you still screwed it up?
At this point most reasonable people would just say, "Don't go there anymore."
To them I say, "No shit?" Because that's what I did. If I was asked where to go for a fast food outing, the local BK was definitely on the bottom of that list. I told people I knew that I despised that place and it was a den of incompetence.
My son is still young enough to not care about such things. "Good enough" meant he had his cheeseburger or croissant sandwich. I'm not sure why he adores the food there. Perhaps part of it is not having to pay actual money to be inconvenienced by details such as, "Do I really want to get up and return this thing I didn't order and ask for my actual order?"
They've gotten better,...but at this point I don't really care.
Sin one: they rarely got my order right. Even simple ones. Like for a single drink.
The other day he was begging to go there for breakfast. It served as a source of more bafflement because I had their sausage, egg and cheese croissan'wich and the sausage tasted burned. Not just grilled...kind of charred on the surface of the patty. But at least it wasn't a piece of charcoal through and through. Edible, but the taste left something to be desired.
Sin two: the food prep doesn't seem very...consistent?
Many years ago...my wife says it was around 2007...my son was with his grandparents when he had an incident at this restaurant. This Burger King has their own play area, with slides and climby parts and...I don't know what else. But my son somehow had a matchbox car drop into a gap in the play area jungle-gym-ish climbing area.
He reached into the gap to retrieve the toy and the plastic pieces pinched together, entrapping him. I found out after everything was said and done and the fire department had left that my son was okay, just a little rattled. I don't think he ever went into the play area again. Yeah...the workers were clueless about what to do, and the fire department helped free him.
He managed to get stuck despite being supervised in a public restaurant playground area. And it was good he was being watched...I shudder to think of what happened if the play area had shifted just right that it could have crunched his arm.
There are certain risks to playing in a playground, and we accept that. But this was more of a maintenance issue. And it created a safety issue. I wanted to contact Burger King and let them know that maybe they might want to be careful about this...you know...inspect their playground equipment once in awhile.
Sin three: child-eating playground equipment.
Contacting Burger King was a challenge, to say the least. They had no Twitter account (Twitter launched in 2006, after all...); I could find no contact on their web page, there was no sign of an email address. The rest of the world had some form of electronic contact. Burger King gave customers an electronic middle finger.
Not kidding. A quick check on the Internet Archive at http://web.archive.org/web/20071006000851/http://burgerking.com/companyinfo/contactus.aspx (late 2007) came right out and said "E-mail communication is not accepted." In 2007. What the hell?
Sin four: about as tech savvy as a three year old, or perhaps K-Mart.
I did something that really just made me angry. I wrote them a letter. An actual, dead-tree, ink on paper letter. I mean, it was printed...I'm not a luddite and I know how to use a word processor. But it still irritated me that asking them (or their franchisees) to have some standard of not smooshing children in their playground equipment really shouldn't cost me money.
What I wanted was an acknowledgement that they'd do something to keep other kids from getting eaten by equipment. What I got was bupkis. I never heard back from Burger King. Not even some offhanded blame against their franchisee, pretending they have no control over how their image and name is represented. Nada. Zip.
My letter went into some great abyss of customer fuck-offs.
Sin five: at least have the decency to acknowledge something like this. There was a fire department called, you bastards.
What got me musing on the myriad reasons I avoid this BK when at all possible is my son's recent insistence...begging, really...that his special day out include a trip to Burger King for morning breakfast. I opened a web browser and it connected to my employer's website. I noticed that there was an oddly shaded and content-empty bar along the bottom of my web page.
Viewing the source showed some issues with loading something from an advertisement link. Which was strange, since my employer doesn't shove any weirdball ads like this at their users (I think it was blanked out because of an ad-block extension active in my browser at the time, rendering whatever their ad bar was supposed to be, blank, instead.)
I opened a new browser tab and navigated to my employer again, but this time used the https: link. That time the advertiser disappeared. See, using an encrypted point-to-point connection makes it difficult to inject extra HTML into my browsing session without me knowing...
...which meant Burger King, the company that for so many years makes it hard to actually contact them with any feedback and displayed a stunning lack of customer interaction and tech savviness, was injecting unwanted ads (and probably monitoring) my web browsing over their wifi.
Sin six: You're monitoring and injecting content into my browsing? Really?
I'm assuming that there was something about this buried in the legalese agreed upon when clicking to use their wifi, although to be honest, I don't remember clicking on anything in order to get on their wifi. And when it comes to public wifi, there's always the danger of your session being hijacked if you're not using a VPN or some other form of encryption. But still...another reason to dislike you?
I'm not sure how you stay in business other than being convenient for people in an area that doesn't have a lot of income. You're close to a retirement home, and getting to the Dunkin' Donuts would mean crossing the street in an area that has horrible crosswalk support. Maybe the Walmart model is at play; cheap food is cheap, so who cares about customer experience?
I even made a passive aggressive complaint on Twitter about their monitoring of web traffic and injecting code into the session (believe it or not, BK is on Twitter now...they joined in 2010. Yeah...little late to the party.) They said nothing. I've made irritated remarks about Dell and Time Warner and had them reply to me without even trying to talk to them. BK doesn't give a damn.
Sin seven: you monitor customer web traffic but not your mentions in Twitter. Get with the program.
Last I went to their website to contact them about the web-jacking. I clicked on the contact us page. It redirects to a "tellusaboutus.com" website.
They outsourced the "contact us."
A totally outside company handles "contact us" for Burger King.
Sin eight: ...I...no. That's enough.
Many companies are at least trying to not suck. They show signs of understanding how to engage with customers. They monitor for signs that customers want to communicate with them, want help, want feedback, want acknowledgement (hey, hear of Netflix? Or Dominoes? Or any of the dozens of other companies that will get in the news with some playful banter on Twitter with customers?)
Most companies at a minimum make it easy to email them with complaints or suggestions. Hell, I've had companies that pester me for feedback via email.
Burger King is like a digital brick wall. They seem to actively not want customer feedback.
...I suppose that kind of explains quite a bit.
I'll look forward to what the next five years holds for Burger King. If McD's is feeling the financial pinch, it may not be long before Burger King topples over. Can't say I'll miss them.
Monday, August 31, 2015
Tuesday, August 25, 2015
Haiku OS...Noticed Something a Little Strange
I was updating my Haiku VM and noticed something a little strange in Top. I checked some Ps output and...yup. It's there too.
Do you see the weirdness?
If not, I'll try making it more obvious.
The "Big Brother" one was the first thing that caught my eye. Was something infecting Haiku?
Who would bother targeting an operating system with a sliver of market share? An OS still in development, for that matter? Perhaps a pissed developer?
These thoughts were going through my head as I weighed the decision to shut down the VM in case it was doing something to probe the network.
I did a quick check online for more information about this...surely someone else had noticed weird processes like that. I found that someone had indeed noticed and asked about this awhile ago; they were pointed to the AppManager.cpp source in Github.
So..."Big Brother" is a humorous way of naming a watchdog process. What about the ancient movie reference? I found a chat transcript from someone else who was rather puzzled as well:
So they're...intentional?
I understand geek humor being integrated with different projects. I'm hardly surprised when I hear there's some little easter egg hidden in the source code, or when there's a somewhat obvious integration of technology with a geeky cultural icon (like OS/2 having a release named Warp.)
I'm not quite so sure about the wisdom of having something with ominous connotations being used as a thread name in a process list, especially in the age of Edward Snowden. And there's something about having a reference to Austin Powers that just...doesn't fit at all into the project. It's not in a theme. It's not...anything. An inside joke? Is it trying to date the project in some way?
At best these are quirks that make you pause and wonder if someone drank too much before making a commit. At worst doing things like this adds to a perception that the project isn't really meant to be taken seriously. At least, in my opinion.
I'm all for fun jokes and geeky humor. I love the little hidden easter eggs in out of the way places. I'm just not sure about humor that pops out in a nonsensical manner like a hernia bulge with no rhyme or reason, hinting that it's up to something nefarious.
Do you see the weirdness?
If not, I'll try making it more obvious.
The "Big Brother" one was the first thing that caught my eye. Was something infecting Haiku?
Who would bother targeting an operating system with a sliver of market share? An OS still in development, for that matter? Perhaps a pissed developer?
These thoughts were going through my head as I weighed the decision to shut down the VM in case it was doing something to probe the network.
I did a quick check online for more information about this...surely someone else had noticed weird processes like that. I found that someone had indeed noticed and asked about this awhile ago; they were pointed to the AppManager.cpp source in Github.
So..."Big Brother" is a humorous way of naming a watchdog process. What about the ancient movie reference? I found a chat transcript from someone else who was rather puzzled as well:
So they're...intentional?
I understand geek humor being integrated with different projects. I'm hardly surprised when I hear there's some little easter egg hidden in the source code, or when there's a somewhat obvious integration of technology with a geeky cultural icon (like OS/2 having a release named Warp.)
I'm not quite so sure about the wisdom of having something with ominous connotations being used as a thread name in a process list, especially in the age of Edward Snowden. And there's something about having a reference to Austin Powers that just...doesn't fit at all into the project. It's not in a theme. It's not...anything. An inside joke? Is it trying to date the project in some way?
At best these are quirks that make you pause and wonder if someone drank too much before making a commit. At worst doing things like this adds to a perception that the project isn't really meant to be taken seriously. At least, in my opinion.
I'm all for fun jokes and geeky humor. I love the little hidden easter eggs in out of the way places. I'm just not sure about humor that pops out in a nonsensical manner like a hernia bulge with no rhyme or reason, hinting that it's up to something nefarious.
Sunday, August 23, 2015
Upgrading Raspberry Pi From Golang 1.4x to 1.5
Some additional notes with my adventures working on an older ARM-based Pi computer and Go. Basically this is notes...not a how-to, but handy reference, so it will probably be a bit more terse than my usually over-narrative writing style.
First I logged in to the Pi and ran screen, because I'm remote on a free wifi connection 150 miles from the Pi and I know it's a hijacked connection from Burger King so trusting its reliability is iffy at best.
Second, I couldn't get the "git checkout go1.5" command to work. So I took my existing ~/go directory and moved it to another directory. Go is now self-bootstrapped...it likes the previous build being available. And it looked for a specific subdirectory. So...
mv ./go ./go1.4
git clone https://go.googlesource.com/go
cd go
git checkout go1.5
cd src
./all.bash
From there it went through the build process, compiling Go with Go (nifty, huh?)
First I logged in to the Pi and ran screen, because I'm remote on a free wifi connection 150 miles from the Pi and I know it's a hijacked connection from Burger King so trusting its reliability is iffy at best.
Second, I couldn't get the "git checkout go1.5" command to work. So I took my existing ~/go directory and moved it to another directory. Go is now self-bootstrapped...it likes the previous build being available. And it looked for a specific subdirectory. So...
mv ./go ./go1.4
git clone https://go.googlesource.com/go
cd go
git checkout go1.5
cd src
./all.bash
From there it went through the build process, compiling Go with Go (nifty, huh?)
Friday, August 21, 2015
GoLang and MSSQL Databases: An Example
(I'll insert the sources for custom_DB_functions.go and sqltest.go at the end of the post.)
(Addendum - I recently ran into an issue with the query of the database in the custom_DB_functions.go package where calls returned an invalid object. It looks like you have to call it with the schema as well [mydatabase.myschema.mytable] to get the call to work. See this StackOverflow question for details.)
I created a small utility recently that pulled data from a text file to display on a web page. I showed it to my manager, and he said we might be able to integrate it with a more useful bit of our infrastructure already in place, but in order to do that, it would have to talk to an MSSQL server for content instead of the text file.
I'd never really worked with testing that setup. In searching the Internet for examples, there are lots of fragments providing hints how to do it...but nothing really spelled out an example in tutorial form. So I kept notes and now I'm sharing what I learned for other beginners that want to experiment with integrating an MSSQL database with their Go application.
I'm only going to hit some highlights in the source; the source code has quite a bit of commenting and is pretty self-documented (but if you have questions...or suggestions/corrections...please leave a comment! In the blog comments, that is.)
Creating a Test Database Server
I wanted to set up an accessible test environment for anyone, not just people who happen to have a full-on SQL Server available. I fired up my Windows VM and downloaded MS SQL Server Express 2014. Free for most purposes!
I ran the installer (the 64 bit version with tools) keeping the defaults.
I had to enable network access to the database engine.
sqltest.go
(Addendum - I recently ran into an issue with the query of the database in the custom_DB_functions.go package where calls returned an invalid object. It looks like you have to call it with the schema as well [mydatabase.myschema.mytable] to get the call to work. See this StackOverflow question for details.)
I created a small utility recently that pulled data from a text file to display on a web page. I showed it to my manager, and he said we might be able to integrate it with a more useful bit of our infrastructure already in place, but in order to do that, it would have to talk to an MSSQL server for content instead of the text file.
I'd never really worked with testing that setup. In searching the Internet for examples, there are lots of fragments providing hints how to do it...but nothing really spelled out an example in tutorial form. So I kept notes and now I'm sharing what I learned for other beginners that want to experiment with integrating an MSSQL database with their Go application.
I'm only going to hit some highlights in the source; the source code has quite a bit of commenting and is pretty self-documented (but if you have questions...or suggestions/corrections...please leave a comment! In the blog comments, that is.)
Creating a Test Database Server
I wanted to set up an accessible test environment for anyone, not just people who happen to have a full-on SQL Server available. I fired up my Windows VM and downloaded MS SQL Server Express 2014. Free for most purposes!
I ran the installer (the 64 bit version with tools) keeping the defaults.
I had to enable network access to the database engine.
- Open the SQL Server 2014 Configuration Manager
- Click SQL Server Network Configuration in the left hand column
- Open Protocols for SQLEXPRESS
- Make sure TCP/IP is "enabled"
- Right click TCP/IP, click Properties
- Click the IP Addresses tab
- Check that IP2 is in the local subnet
- Check that TCP Dynamic Ports is blank in the IPALL section
- Check that the TCP Port is set to 1433
- Restart the SQL Server (SQLEXPRESS) service
At this point a quick NMap scan of the VM showed that port 1433 was open (when I used the -Pn flag.)
There's another setting to change coming up...
There's another setting to change coming up...
Prepare Your Go Project to Talk to The SQL Server
Go projects talking to a SQL server need two components; a driver, and a library that abstracts that driver from the programmer. The driver depends on what type of server you're talking to, but the abstraction layer is pretty standard.
Since we're talking to MSSQL, we'll use the go-mssqldb driver. From your Go workspace run:
go get github.com/denisenkom/go-mssqldb
I decided I was going to create a test application that imported a custom package of functions that specifically accessed the database; that way I could import the resulting package to my existing application and make necessary alterations at that point.
In the package I imported the driver with the line
_ "github.com/denisenkom/go-mssqldb"
WHAT IS THAT UNDERSCORE?
The problem is that I don't use the package identifier for anything in the program; when I tried to compile it, the compiler will error. What I really needed from the driver was the initialization method; the underscore will run the init() function but ignore everything else, and the compiler won't complain.
The abstraction part...the generic SQL Go functions to interact with the database...are imported with
"database/sql"
That import is in my package (custom_DB_functions.go and sqltest.go.) The driver is only imported in the package.
The test program was literally a "add functions as I go along and verify they work" thing. Add a function, code it in the package, recompile...repeat until I had most of the functions I wanted to work.
In order to make the test program flexible, I imported the flag package and created entries for connecting to the database from the command line. Import with
import (
"flag"
)
In main(), I added a series of flags using
// Flags
ptrVersion := flag.Bool("version", false, "Display program version")
ptrDeleteIt := flag.Bool("deletedb", false, "Delete the database")
ptrServer := flag.String("server", "localhost", "Server to connect to")
ptrUser := flag.String("username", "testuser", "Username for authenticating to database; if you use a backslash, it must be escaped or in quotes")
ptrPass := flag.String("password", "", "Password for database connection")
ptrDBName := flag.String("dbname", "test_db", "Database name")
flag.Parse()
You can probably divine the meaning from the syntax, but these are in the form of
<variable> := flag.<type>("flagname", default setting if not provided at command line, "Help explanation")
The variable created is a pointer, and flag.Parse() evaluates the flags at runtime. The default values are set to the second argument if they're not changed at the command line, so you can use them as variables without having to set them to something before referring to them.
The first function I wanted to test was the creation of a database handle. It seems that the behavior of Open relies on the underlying driver; it may, as the docs say, validate arguments without a connection to the database, so it may be necessary to actually do something with the handle before active connections are made. At any rate, here's the function in sqltest.go:
db, err := sql.Open("mssql", "server="+*ptrServer+";user id="+*ptrUser+";password="+*ptrPass)
if err != nil {
fmt.Println("From Open() attempt: " + err.Error())
}
defer db.Close()
Now db is a handle to the database. Because of the way database connections are handled, you don't want to keep opening and closing them. Just defer the Close() until the program exits and that way you won't get goofy pooling/caching issues.
From what I can tell of the documentation the db handle must be kept open for the length of time that you're using it (don't close it until the program exits.) This lets the driver manage a pool of connections; when you operate on the database you use a connection from the pool. Those connection(s) you'll want to close so the connection gets returned to the connection pool. In this program I deferred the close of the db handle because the scope of the test program should keep it open until sqltest ends.
Basically Open() needs the type of database (MSSQL), the server IP or DNS name, the username, and password. I should also note that the username, if you're using Windows auth, the backslash must be entered twice so it's escaped properly or the username must be encased in quotes. for example, the connection at the command line might look like:
From what I can tell of the documentation the db handle must be kept open for the length of time that you're using it (don't close it until the program exits.) This lets the driver manage a pool of connections; when you operate on the database you use a connection from the pool. Those connection(s) you'll want to close so the connection gets returned to the connection pool. In this program I deferred the close of the db handle because the scope of the test program should keep it open until sqltest ends.
Basically Open() needs the type of database (MSSQL), the server IP or DNS name, the username, and password. I should also note that the username, if you're using Windows auth, the backslash must be entered twice so it's escaped properly or the username must be encased in quotes. for example, the connection at the command line might look like:
./sqltest -password=HelloThere -server=192.168.254.222 -username=MySystem\\testuser
Notice there's two backslashes in the username?
Skipping ahead a little, sqltest.go makes a call to PingServer(). In the package, PingServer looks like:
func PingServer(db *sql.DB) string {
err := db.Ping()
if err != nil {
return ("From Ping() Attempt: " + err.Error())
}
return ("Database Ping Worked...")
}
Skipping ahead a little, sqltest.go makes a call to PingServer(). In the package, PingServer looks like:
func PingServer(db *sql.DB) string {
err := db.Ping()
if err != nil {
return ("From Ping() Attempt: " + err.Error())
}
return ("Database Ping Worked...")
}
It's a rather simple function, and all it does is run a call to db.Ping and returns a string with an error or an affirmation that it's working. This created a working connection to the database (as discussed before), and also tested the ability to pass the database handle to a package function.
Something else to note in sqltest.go is that I called the initial Open() using sql.Open(), while the call to PingServer was simply PingServer(). The trick to that is in the import statement.
import (
"database/sql"
"flag"
"fmt"
"os"
. "custom_DB_functions"
"strconv"
)
The import for "custom_DB_functions" is preceded by a period. That allows me to refer to the functions without the preceding library name; if I preceded "fmt" with a period I should be able to use lines like Println("I'm printing this to the console!") instead of fmt.Println("I'm printing this to the console!"). I don't know what happens if I had multiple functions with the same name in different packages imported with the period...I would think the compiler would insist on the proper namespace for referencing those specific cases, but I haven't tested it.
Skipping ahead a little more there's a spot where I create the database if it doesn't already exist:
// If it doesn't exist, let's create the base database
if !boolDBExist {
CreateDBAndTable(db, *ptrDBName)
fmt.Println("********************************")
}
In the library, the call looks like this:
func CreateDBAndTable(db *sql.DB, strDBName string) error {
// Create the database
_, err := db.Exec("CREATE DATABASE [" + strDBName + "]")
if err != nil {
return (err)
}
// Let's turn off AutoClose
_, err = db.Exec("ALTER DATABASE [" + strDBName + "] SET AUTO_CLOSE OFF;")
if err != nil {
return (err)
}
// Create the tables
_, err = db.Exec("USE " + strDBName + "; CREATE TABLE testtable (source nvarchar(100) NOT NULL, timestamp bigint NOT NULL, content nvarchar(4000) NOT NULL)")
if err != nil {
return (err)
}
return nil
}
It was here that I encountered an error from the database.
Next Database Configuration Change: "CREATE DATABASE permission denied in database 'master'"
The actual SQL calls aren't all that difficult if you already understand SQL (the big notes involve not constantly opening and closing the database handle, and to use Query() when getting information back to process and Exec() when the reply is more or less either "this worked" or "error!"
In the above calls, you can see that the function creates a database, alters the AUTO_CLOSE setting on the database so it doesn't throw goofball errors when trying later queries, and then creates a table with particular attributes.
In the above calls, you can see that the function creates a database, alters the AUTO_CLOSE setting on the database so it doesn't throw goofball errors when trying later queries, and then creates a table with particular attributes.
What I got back the first time was the CREATE DATABASE permission denied in database 'master' error. That required some more tinkering with the database engine.
- Open SQL Server 2014 Management Studio and connect to the test database
- Click on your SQL EXPRESS instance
- Expand the Security folder
- Expand the Logins folder
- I created a local user on my system for testing, so I right clicked on BUILTIN\Users and select Properties
- Click on "Server Roles" on the left side
- Select "dbcreator" from the list of roles; this should be enough access
Clicking okay and re-running the database/table creation calls should work. CreateDBAndTable() and DropDB() were created mostly for testing purposes so I could periodically work with a fresh database without having to futz with Management Studio or other interface (and of course I learned how to do these tasks programmatically.)
Any Other Notes?
Between heavy commenting and naming functions and variables in a (hopefully) mostly obvious manner I think that the code is semi-obvious; most of what I would explain in text here would seem redundant or obvious. If you have questions feel free to leave a blog comment!
The only thing that comes to mind from the design point of view is the use of returning an error from the library functions instead of dumping something to the console. This means the caller is responsible for the presentation of messages; the application can decide if the returned message goes to the console or redirected to a file or possibly ignored.
Here's the source code! Hope it is somewhat helpful to someone! (Probably it will be most useful to me as a reference while trying to tune what I've been working on...)
Source Code
custom_DB_functions.go
// Package custom_DB_functions contains functions customized to manipulate MSSQL databases/tables // for our application // // Version 0.15, 8-13-2015 package custom_DB_functions import ( "database/sql" _ "github.com/denisenkom/go-mssqldb" "strconv" ) // PingServer uses a passed database handle to check if the database server works func PingServer(db *sql.DB) string { err := db.Ping() if err != nil { return ("From Ping() Attempt: " + err.Error()) } return ("Database Ping Worked...") } // CheckDB checks if the database "strDBName" exists on the MSSQL database engine. func CheckDB(db *sql.DB, strDBName string) (bool, error) { // Does the database exist? result, err := db.Query("SELECT db_id('" + strDBName + "')") defer result.Close() if err != nil { return false, err } for result.Next() { var s sql.NullString err := result.Scan(&s) if err != nil { return false, err } // Check result if s.Valid { return true, nil } else { return false, nil } } // This return() should never be hit... return false, err } // CreateDBAndTable creates a new content database on the SQL Server along with // the necessary tables. Keep in mind the user credentials that opened the database // connection with sql.Open must have at least dbcreator rights to the database. The // table (testtable) will have columns source (nvarchar), timestamp (bigint), and // content (nvarchar). func CreateDBAndTable(db *sql.DB, strDBName string) error { // Create the database _, err := db.Exec("CREATE DATABASE [" + strDBName + "]") if err != nil { return (err) } // Let's turn off AutoClose _, err = db.Exec("ALTER DATABASE [" + strDBName + "] SET AUTO_CLOSE OFF;") if err != nil { return (err) } // Create the tables _, err = db.Exec("USE " + strDBName + "; CREATE TABLE testtable (source nvarchar(100) NOT NULL, timestamp bigint NOT NULL, content nvarchar(4000) NOT NULL)") if err != nil { return (err) } return nil } // DropDB deletes the database strDBName. func DropDB(db *sql.DB, strDBName string) error { // Drop the database _, err := db.Exec("DROP DATABASE [" + strDBName + "]") if err != nil { return err } return nil } // AddToContent adds new content to the database. func AddToContent(db *sql.DB, strDBName string, strSource string, int64Timestamp int64, strContent string) error { // Add a record entry _, err := db.Exec("USE " + strDBName + "; INSERT INTO testtable (source, timestamp, content) VALUES ('" + strSource + "','" + strconv.FormatInt(int64Timestamp, 10) + "','" + strContent + "');") if err != nil { return err } return nil } // RemoveFromContentBySource removes a record from the database with source strSource. The // int64 returned is a message indicating the number of rows affected. func RemoveFromContentBySource(db *sql.DB, strSource string) (int64, error) { // Remove entries containing the source... result, err := db.Exec("DELETE FROM testtable WHERE source=$1;", strSource) if err != nil { return 0, err } // What was the result? rowsAffected, _ := result.RowsAffected() return rowsAffected, nil } // Query the content in the database and return the source (string), timestamp (int64), and // content (string) as slices func GetContent(db *sql.DB) ([]string, []int64, []string, error) { var slcstrContent []string var slcint64Timestamp []int64 var slcstrSource []string // Run the query rows, err := db.Query("SELECT source, timestamp, content FROM testtable") if err != nil { return slcstrSource, slcint64Timestamp, slcstrContent, err } defer rows.Close() for rows.Next() { // Holding variables for the content in the columns var source, content string var timestamp int64 // Get the results of the query err := rows.Scan(&source, ×tamp, &content) if err != nil { return slcstrSource, slcint64Timestamp, slcstrContent, err } // Append them into the slices that will eventually be returned to the caller slcstrSource = append(slcstrSource, source) slcstrContent = append(slcstrContent, content) slcint64Timestamp = append(slcint64Timestamp, timestamp) } return slcstrSource, slcint64Timestamp, slcstrContent, nil }
package main // Notice in the import list there's one package prefaced by a ".", // which allows referencing functions in that package without naming the library in // the call (if using . "fmt", I can call Println as Println, not fmt.Println) import ( "database/sql" "flag" "fmt" "os" . "custom_DB_functions" "strconv" ) const strVERSION string = "0.18 compiled on 8/11/2015" // sqltest is a small application for demonstrating/testing/learning about SQL database connectivity from Go func main() { // Flags ptrVersion := flag.Bool("version", false, "Display program version") ptrDeleteIt := flag.Bool("deletedb", false, "Delete the database") ptrServer := flag.String("server", "localhost", "Server to connect to") ptrUser := flag.String("username", "testuser", "Username for authenticating to database; if you use a backslash, it must be escaped or in quotes") ptrPass := flag.String("password", "", "Password for database connection") ptrDBName := flag.String("dbname", "test_db", "Database name") flag.Parse() // Does the user just want the version of the application? if *ptrVersion == true { fmt.Println("Version " + strVERSION) os.Exit(0) } // Open connection to the database server; this doesn't verify anything until you // perform an operation (such as a ping). db, err := sql.Open("mssql", "server="+*ptrServer+";user id="+*ptrUser+";password="+*ptrPass) if err != nil { fmt.Println("From Open() attempt: " + err.Error()) } // When main() is done, this should close the connections defer db.Close() // Does the user want to delete the database? if *ptrDeleteIt == true { boolDBExist, err := CheckDB(db, *ptrDBName) if err != nil { fmt.Println("Error running CheckDB: " + err.Error()) os.Exit(1) } if boolDBExist { fmt.Println("(sqltest) Deleting database " + *ptrDBName + "...") DropDB(db, *ptrDBName) os.Exit(0) } else { // Database doesn't seem to exist... fmt.Println("(sqltest) Database " + *ptrDBName + " doesn't appear to exist...") os.Exit(1) } } // Let's start the tests... fmt.Println("********************************") // Is the database running? strResult := PingServer(db) fmt.Println("(sqltest) Ping of Server Result Was: " + strResult) fmt.Println("********************************") // Does the database exist? boolDBExist, err := CheckDB(db, *ptrDBName) if err != nil { fmt.Println("(sqltest) Error running CheckDB: " + err.Error()) os.Exit(1) } fmt.Println("(sqltest) Database Existence Check: " + strconv.FormatBool(boolDBExist)) fmt.Println("********************************") // If it doesn't exist, let's create the base database if !boolDBExist { CreateDBAndTable(db, *ptrDBName) fmt.Println("********************************") } // Enter a test record boolDBExist, err = CheckDB(db, *ptrDBName) if err != nil { fmt.Println("(sqltest) CheckDB() error: " + err.Error()) os.Exit(1) } if boolDBExist == true { err := AddToContent(db, *ptrDBName, "Bob", 1437506592, "Hello!") if err != nil { fmt.Println("(sqltest) Error adding line to content: " + err.Error()) os.Exit(1) } err = AddToContent(db, *ptrDBName, "user", 1437506648, "Now testing memory") if err != nil { fmt.Println("(sqltest) Error adding line to content: " + err.Error()) os.Exit(1) } err = AddToContent(db, *ptrDBName, "user", 1437503394, "test, text!") if err != nil { fmt.Println("(sqltest) Error adding line to content: " + err.Error()) os.Exit(1) } err = AddToContent(db, *ptrDBName, "Bob", 1437506592, "Hope this works!") if err != nil { fmt.Println("(sqltest) Error adding line to content: " + err.Error()) os.Exit(1) } } fmt.Println("(sqltest) Completed entering test records.") fmt.Println("********************************") fmt.Println("(sqltest) Deleting records from a particular source.") // Delete from a source int64Deleted, err := RemoveFromContentBySource(db, "user") if err != nil { fmt.Println("(sqltest) Error deleting records by source: " + err.Error()) os.Exit(1) } else { // How many records were removed? fmt.Println("Removed " + strconv.FormatInt(int64Deleted, 10) + " records") fmt.Println("********************************") } // Get the content slcstrSource, slcint64Timestamp, slcstrContent, err := GetContent(db) if err != nil { fmt.Println("(sqltest) Error getting content: " + err.Error()) } // Now read the contents for i := range slcstrContent { fmt.Println("Entry " + strconv.Itoa(i) + ": " + strconv.FormatInt(slcint64Timestamp[i], 10) + ", from " + slcstrSource[i] + ": " + slcstrContent[i]) } }
Tuesday, August 18, 2015
Adventures in Apartment Mismanagement
I like to look at examples of events and practices that piss me off and question, "How could this be improved? Is there a good reason the system sucks?"
I'm running into that today with my apartment management company.
On a Saturday afternoon not long ago my family and I were sitting in the apartment trying not to melt during yet another hot, sticky NYC day. All of a sudden the doorbell rang.
The ring was followed shortly by another ring. My wife and I looked at each other, perplexed; we didn't order anything. We weren't expecting anyone from the maintenance department. The way the building was managed, I didn't even try to get deliveries. Who the hell could it be?
Before I could get up, the fucking door unlocked.
Yeah. We keep the door locked and chained when in the apartment. And whoever was at the door unlocked it. The door started to open, only to have the chain go taut.
A 20-something year old guy and his companion peered in, mumbled something about, "Oh, someone's home," and closed the door.
I grabbed a shirt...like I said, it was hot...and opened the door. The hallway was empty.
I went down to the front desk where "security" is stationed. I explained what happened, and he made a phone call. He said that maintenance didn't have anyone there. He didn't know why anyone would be on our floor. "They shouldn't do that...that's like an invasion of privacy or something." Yeah, thanks Security. I'm feeling more secure knowing how sure you are of the rights of your tenants not to have intruders barge in. "The super has a master key, and some keys that are good in certain apartments. But access to those are controlled." Well, that's good, I guess. If this was an employee they should know who has access.
He asked if I wanted to file a report.
"Well...yes." I just had someone with a copy of my key open the door to my apartment. If I wasn't there, would he have stolen my laptop? Would he have used the toilet? Maybe kick back and watch my non-cable-connected TV?
Security said he'd sent the cop up to my apartment, since I said I would head back up there and my wife could help with the description of what happened. After a few minutes the doorbell rang and a uniformed cop...think he has a "special patrol" badge sewn on his uniform?...is standing there. He takes down the description of what happened on a piece of what seemed to be scratch paper plucked from the corner of a desk he passed on his way up.
We're short on details. Male. No uniform, so he's not maintenance...t-shirt and shorts, my wife said. Early to mid twenties. Maybe 5'9" or so. I wanted to say Asian, but wasn't entirely sure...it happened pretty fast and I'd rather say nothing than give the incorrect details. Second guy standing behind him in the hall.
The cop says that sometimes lock keys work in multiple apartments. "Someone could have gone into the wrong apartment," he says. "Got on the wrong floor, or something. It happens." I wanted to remind him of the detail regarding the doorbell. I don't know too many people that are like, "Home at last! I'd better ring the doorbell a few times before going in. In case, I don't know, my wife is banging the neighbor. They'd appreciate the heads up that I'm home."
"You should get a top lock," the cop continued. He's referring to getting a deadbolt that the resident installs, instead of just the door lock the super has access to.
We thank him and he leaves. I'm wondering if they're going to follow up using the security footage from the elevators.
At this point I'm uncomfortable leaving the apartment, since that means the chain would be undone and Heckle and Jeckle might come back. I give my wife some cash and ask her to run to a store to find a lock we could install; she's had experience with lock installation and the door has a pre-drilled cylinder hole in it from another resident in a bygone era. Surely we can figure this out.
She gets the lock, and after an hour of trying...it won't work. The holes already in the door from a previous resident are too large for the screws in the lock kit, and the bolt doesn't readily fit into the frame in a way that allows the door to shut.
I go online and file a maintenance ticket marked "emergency" priority explaining what happened and asking for assistance in getting the lock mounted. I mention that at this point, my son is disturbed at the idea of sleeping in the apartment without periodically checking that the door is chained so "bad men" won't come in during the night.
"Dad, is it okay to stab people that come into the apartment if we don't know them?"
He's such a kidder. But I tell him that yes, he can stab people that break into the apartment.
On Sunday I get a call from the super saying he got an email about the incident. I'm not entirely sure, but I think the super for my apartment building is also the head of maintenance for our building, so...did he hear about this from security? The management office? My maintenance ticket? I'm not sure. We had convinced ourselves that this incident was weird enough that we could go out for awhile, since this was our last weekend together in the city and we'd been promising Lil' Dude we'd take him to Coney Island for a walk on the beach and a trip to Nathan's famous hot dog stand. Plus talking on the phone is not my favorite activity in the world, so making out everything he was saying was taking extra effort.
What I did get out of the call was that he didn't know who it was, he didn't know of any reason for it to happen, and once again, we should get a top lock. He also promised to send a recommended locksmith contact information to me via email.
Monday morning came and my wife sent a message that the maintenance guy came, only to be told they won't install locks or have anything to do with locks. Considering that we needed this to get done on Tuesday at the latest since I was leaving town...yay, school starting plus doctor appointment/lecture time...this was not news I wanted to hear. We bought the lock. We just didn't have a way to get the damn screws to fit into a door that was already fucked up from a previous tenant.
Well...fuck it.
My wife found a recommended locksmith, available 24 hours, with high ratings on Yelp...we never got a recommended locksmith from the super. My wife insisted I had to contact them ("Your name is on the lease!") and they made an appointment for early afternoon.
True to their word, they arrived, and $160 later, we had a new top-lock. Their lock, plus they drilled into the frame area to get it all fitted properly. My wife was impressed. I was relieved to get this crap out of the way before having to leave.
I can understand why the building would have a policy against installing locks since that can imply some kind of liability if someone still broke in after the fact. At the same time, I haven't seen too much action taken in regards to actually trying to figure out who the fuck opened my door.
It was most likely not another apartment-dweller who happened to have a key that accidentally fit my apartment lock. As I said...they rang the doorbell and were surprised we were home.
It wasn't building maintenance...they were out of uniform.
They had a key that may likely have been an access key...the super is supposed to have it, or the main office. Someone that has access for building work or in the event of a lockout. Supposedly these keys are locked up, and possession is tracked.
So far all I've heard is a lot of head scratching, like "That's not supposed to happen..." Well, no shit.
I haven't even heard if someone checked the security cameras on the floor. They knew approximately when it happened. I was told no one else reported a disturbance. More to the point, my neighbor has papers sticking out of their door...they're obviously not home since the papers have been there awhile. So...why my place? And why aren't the security cameras being checked for two guys, in that approximate time frame, riding the elevators after I said they had disappeared from the hallways? Are the cameras nonfunctional (I should hope not...little corner dome cams suddenly appeared in the elevators recently...) or are they only consulted if someone is assaulted in the elevator?
The entire feeling I get is one of lackadaisical malaise from people involved. Like, "Well, these things happen. You should get a better lock."
We have "security" at the entrance of the building and the only thing they seem to guard against is me getting a Dominoes delivery (or any other package delivery, for that matter, if it doesn't fit in my mailbox...) Any stranger can walk in if they wait long enough for someone to walk through.
We have cameras, but I still have no indication they're going to check it.
There are supposed to be secured keys with limited access to each of the apartments. But I've heard nothing of them being audited. Does someone have the key that shouldn't? And what about the whole "Tenant keys sometimes fit other doors" thing. That's a bit of a worrying thing, don't you think?
I feel like my car broke down, smoke pouring from the engine, fluids spurting out like a fountain, and the mechanic...the people who should know what they're doing...is standing there, scratching his chin and ruminating, "Well, that ain't supposed to happen..."
No shit. Such is life in the city, apparently. And another reason to think landlords just hate their tenants.
Wednesday, August 5, 2015
Let's Make a Go (Golang) Package!
Go tends to be rigid in certain customs. That isn't to say you can't bend or break these customs, but the culture certain makes it clear that some things are...frowned upon.
Now I need the package. I create a new directory and .go file:
Next I compile the everything. From /go/src/uses_a_package, I run:
go install
I didn't have to separately compile the package. Why?
The workspace now looks like this:
...and add a call in main() to the added function:
Compile, and run:
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
At this point the directory structure looks like this:
And of course a function in the package can return results. A quick edit to package_demo.go:
...followed by a call added to uses_a_package.go:
Then compile and test!
go install
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!
And of course GoDoc is helpful with documentation. From go/src, I ran "godoc ./package_demo":
PACKAGE DOCUMENTATION
package package_demo
import "./package_demo"
Package package_demo is demonstrating how to make a simple package
file/library
Package package_demo2 takes the package demonstration one step farther,
showing one package directory is "merged" into one package despite
multiple sub-files
FUNCTIONS
func CustomHello(strName string)
CustomHello takes a name passed as a parameter and prints a greeting to
that string
func HelloBack(strName string) string
HelloBack will return a string with a greeting to the caller rather than
printing directly to the console
func Howdy()
Howdy() says hello with no arguments and no value returned
One thing to note is functions to be exported in the package...visible to the executable main()...must be capitalized. An edit to our growing package_demo.go file:
You'll notice I now added a CallsFuncInPackageSelf function, which when invoked calls "constructString" (lowercase first letter) to return a secret hello to the name that was passed to CallsFuncInPackageSelf. In main() I make a small addition:
Run "go install" then call the resulting binary and:
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!
A secret hello to Tom!
But if I edit uses_a_package.go to directly call constructString("Tom") and try a recompile, I get this:
go install
# uses_a_package
./uses_a_package.go:26: cannot refer to unexported name package_demo.constructString
./uses_a_package.go:26: undefined: package_demo.constructString
In order for the function to be visible outside the package, it must begin with a capital letter. That's why when you're working with methods like fmt.Println() the function has a capital...in this case, it's exported from the fmt package!
I hope this is helpful in getting started creating packages in Go!
For example, you can write fairly complicated programs using just a single .go file. Good coding practice is to put your reusable functions into a library (or in Go, a package).
I'm assuming a file structure similar to this for your workspace:
Go
|
|-bin
|-pkg
|-src
Now, let's create a small main() Go file.
Go
|
|-bin
|-pkg
|-src
|-uses_a_package
|-uses_a_package.go
I have a personal habit when creating files to test out theories or implementations of using the word "test" in the names. Go language developers frown on this practice because "test" is used as a literal code testing reserved word; one of the things I personally find screwy about the language, but it is what it is. If you put something like "uses_a_package_test.go" as a filename, you're in for some head scratching and Google searches leading you to articles about running application tests.
So in the file /go/src/uses_a_package/uses_a_package.go I put the following:
package main import ( "fmt" "package_demo" ) func main() { // main() says hello fmt.Println("Hello from main()!") // Package, say hello package_demo.Howdy() }
Now I need the package. I create a new directory and .go file:
Go
|
|-bin
|-pkg
|-src
|-uses_a_package
|-uses_a_package.go
|-package_demo
|-package_demo.go
In package_demo.go I enter the following:
// Package package_demo is demonstrating how to make a simple package file/library package package_demo import ( "fmt" ) // Howdy() says hello with no arguments and no value returned func Howdy() { // Prints a hello to the console fmt.Println("Hello from the package_demo package!") return }
Next I compile the everything. From /go/src/uses_a_package, I run:
go install
I didn't have to separately compile the package. Why?
The workspace now looks like this:
Go
|
|-bin
|-uses_a_package
|-uses_a_package
|-pkg
|-darwin_amd64
|package_demo.a
|-darwin_amd64
|package_demo.a
|-src
|-uses_a_package
|-uses_a_package.go
|-package_demo
|-package_demo.go
The answer is when I ran the "install" command, Go saw the extra package import and found that it, too, needed compilation. I can run the install command just on the package and it'll compile and insert it into my /go/pkg directory, but I didn't need to.
Running the application:
../../bin/uses_a_package
...yields the following output:
Hello from main()!
Hello from the package_demo package!
The way I added comments to the package is kind of important. It's another bit of knowledge that isn't necessarily intuitive, but you run across it in trying to get other things working in Go; the self-documenting feature of GoDoc.
From /go/src, I run:
godoc ./package_demo
Output is:
PACKAGE DOCUMENTATION
package package_demo
import "./package_demo"
Package package_demo is demonstrating how to make a simple package
file/library
FUNCTIONS
func Howdy()
Howdy() says hello with no arguments and no value returned
The answer is when I ran the "install" command, Go saw the extra package import and found that it, too, needed compilation. I can run the install command just on the package and it'll compile and insert it into my /go/pkg directory, but I didn't need to.
Running the application:
../../bin/uses_a_package
...yields the following output:
Hello from main()!
Hello from the package_demo package!
The way I added comments to the package is kind of important. It's another bit of knowledge that isn't necessarily intuitive, but you run across it in trying to get other things working in Go; the self-documenting feature of GoDoc.
From /go/src, I run:
godoc ./package_demo
Output is:
PACKAGE DOCUMENTATION
package package_demo
import "./package_demo"
Package package_demo is demonstrating how to make a simple package
file/library
FUNCTIONS
func Howdy()
Howdy() says hello with no arguments and no value returned
There is definitely room for improvement in how I documented things with comments, but you get the point. Properly comment your code in Go!
I should also note that GoDoc is used for the documentation on the main Go site. Visit that to get an idea how to properly document your functions and code...as I'm throwing together quick demo stuff for this, you can see I say something stupidly obvious like "no arguments and no return value."
Now, why is GoDoc run on the directory, and not on the file? For one, it doesn't work.
godoc ./package_demo/package_demo.go
2015/07/31 13:44:27 newDirectory(/target): not a package directory
Two, the directory acts like the package, and the files in it are the goodies. Let's try something...
in /go/src/package_demo, I create a new file called package_demo2.go and populate it with the following text:
// Package package_demo2 takes the package demonstration one step farther, showing one package // directory is "merged" into one package despite multiple sub-files package package_demo import ( "fmt" ) // CustomHello takes a name passed as a parameter and prints a greeting to that string func CustomHello(strName string) { // Print a custom hello! fmt.Println("Hello, " + strName + "!") return }
...and add a call in main() to the added function:
package main import ( "fmt" "package_demo" ) func main() { // main() says hello fmt.Println("Hello from main()!") // Package, say hello package_demo.Howdy() // Second package file, say hello to Bob! package_demo.CustomHello("Bob") }
Compile, and run:
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
At this point the directory structure looks like this:
Go
|
|-bin
|-uses_a_package
|-uses_a_package
|-pkg
|-darwin_amd64
|package_demo.a
|-darwin_amd64
|package_demo.a
|-src
|-uses_a_package
|-uses_a_package.go
|-package_demo
|-package_demo.go
|-package_demo2.go
|-package_demo2.go
// Package package_demo is demonstrating how to make a simple package file/library package package_demo import ( "fmt" ) // Howdy() says hello with no arguments and no value returned func Howdy() { // Prints a hello to the console fmt.Println("Hello from the package_demo package!") return } // HelloBack will return a string with a greeting to the caller rather than printing directly to the console func HelloBack(strName string) string { // Who to say hello to? strName = "Hello there, " + strName + "!" return(strName) }
...followed by a call added to uses_a_package.go:
package main import ( "fmt" "package_demo" ) func main() { // main() says hello fmt.Println("Hello from main()!") // Package, say hello package_demo.Howdy() // Second package file, say hello to Bob! package_demo.CustomHello("Bob") // Back to the first file... fmt.Println(package_demo.HelloBack("Phil")) }
Then compile and test!
go install
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!
And of course GoDoc is helpful with documentation. From go/src, I ran "godoc ./package_demo":
PACKAGE DOCUMENTATION
package package_demo
import "./package_demo"
Package package_demo is demonstrating how to make a simple package
file/library
Package package_demo2 takes the package demonstration one step farther,
showing one package directory is "merged" into one package despite
multiple sub-files
FUNCTIONS
func CustomHello(strName string)
CustomHello takes a name passed as a parameter and prints a greeting to
that string
func HelloBack(strName string) string
HelloBack will return a string with a greeting to the caller rather than
printing directly to the console
func Howdy()
Howdy() says hello with no arguments and no value returned
One thing to note is functions to be exported in the package...visible to the executable main()...must be capitalized. An edit to our growing package_demo.go file:
// Package package_demo is demonstrating how to make a simple package file/library package package_demo import ( "fmt" ) // Howdy() says hello with no arguments and no value returned func Howdy() { // Prints a hello to the console fmt.Println("Hello from the package_demo package!") return } // HelloBack will return a string with a greeting to the caller rather than printing directly to the console func HelloBack(strName string) string { // Who to say hello to? strName = "Hello there, " + strName + "!" return(strName) } // CallsFuncInPackageSelf takes a name passed as an argument and calls another function within the package // source, a non-exported (capitalized) function, to construct a reply func CallsFuncInPackageSelf(strName string) { strResponse := constructString(strName) fmt.Println(strResponse) } // constructString crafts a greeting and returns a reply, but only works within the package func constructString(strGreetMe string) string { return("A secret hello to " + strGreetMe + "!") }
You'll notice I now added a CallsFuncInPackageSelf function, which when invoked calls "constructString" (lowercase first letter) to return a secret hello to the name that was passed to CallsFuncInPackageSelf. In main() I make a small addition:
package main import ( "fmt" "package_demo" ) func main() { // main() says hello fmt.Println("Hello from main()!") // Package, say hello package_demo.Howdy() // Second package file, say hello to Bob! package_demo.CustomHello("Bob") // Back to the first file... fmt.Println(package_demo.HelloBack("Phil")) // Now a secret greeting... package_demo.CallsFuncInPackageSelf("Tom") }
../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!
A secret hello to Tom!
But if I edit uses_a_package.go to directly call constructString("Tom") and try a recompile, I get this:
go install
# uses_a_package
./uses_a_package.go:26: cannot refer to unexported name package_demo.constructString
./uses_a_package.go:26: undefined: package_demo.constructString
In order for the function to be visible outside the package, it must begin with a capital letter. That's why when you're working with methods like fmt.Println() the function has a capital...in this case, it's exported from the fmt package!
I hope this is helpful in getting started creating packages in Go!
Subscribe to:
Posts (Atom)