I strongly advise everbody
NOT to buy World Of Warcraft!
My brother bought the game today, I installed it, and then I wanted to create an account for him. And that's when the trouble began...
It seems Blizzard does not care about money, since all it's account creation pages are unavailable, throwing errors everywhere.
Or wait, they get the money from you at the shop, and then make it unable for you to use what you bought an hour ago!
Judging from their own forums (which I cannot post on because I don't have an account, and cannot create either) this problem is going on for some days.
First a normal '
Service Temporarily Unavailable' error, indicating their servers can't handle it. Which is really very bad for their image, a company like Blizzard, which can't keep their main billing pages up and running? Normally it's the other way around, the 'we want your money'-pages always work
And then everything is just falling appart, have some errors:
- [ServletException in:/WEB-INF/jsp/authkeyView.jsp] /WEB-INF/jsp/authkeyView.jsp(86,18) Unable to load tag handler class "org.apache.taglibs.standard.tag.el.fmt.MessageTag" for tag "fmt:message"'
- [ServletException in:/WEB-INF/jsp/agreementView.jsp] File "/WEB-INF/jsp/base/language.jsp" not found' [ServletException in:/WEB-INF/jsp/base/footer.jsp] Error running /usr/local/java/bin/javac compiler'
- org.apache.jasper.JasperException: Unable to compile class for JSP
- Error running /usr/local/java/bin/javac compiler
And if you happen to
not get an error, it will simply tell you that your session timed out, after 2 seconds...
Slow poke! You took too long to complete the Account Creation process and your session has timed out. You will have to start again from the beginning. Sorry!If you are persistent and can get through, to step 4, you notice you can select a game-card as a paying option. (After that, it dropped out again) But if you want to use the guest-account that comes with the box, that option is gone... It's a guest account to play 10 days, but they want your credit card info for that...
Don't expect any response from Blizzard either...
I'm really disappointed, 45 EUR for the game and then you can't play it!
Best thing, even their contact form is throwing errors. And nothing is mentioned on the main site, everybody has to figure it out themselves.
I'm going to start calling them starting from Monday untill I can create the account, otherwise they can give my money back and take their game back untill they get their crap together.
Good game, terrible service...
If anyone from Blizzard reads this: Try to turn this negative publicity into something positive...
Instead of making a webservice call each time certain data was needed, the data was stored in the
SqlCe database on the
Pocket PC, to retrieve when needed. This allowed quickly displaying data after having retrieved it once, while still giving the possibility to retrieve the latest data, and update the local cache with it as well.
To implement this, a
Db class was used with the
Singleton pattern to provide database access to the local
SqlCe engine. A database on the
Pocket PC is simply a file on the file system,
MediaService.sdf in this case.
In the
CheckDb method, the database was created in case it did not exist. This was done with normal
SQL queries defining
Create Table commands.
The following code made up the base functionality of the
Db class:
using System;
using System.IO;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlServerCe;
using System.Collections;
namespace MediaService.Pocket {
public class Db {
private const String DB_NAME = "MediaService.sdf";
private static Db instance = null;
public Db() { }
public static Db NewInstance() {
lock(typeof(Db)) {
if (instance == null) {
instance = new Db();
}
return instance;
}
} /* NewInstance */
private void CheckDB() {
if (!File.Exists(DB_NAME)) {
SqlCeConnection conn = null;
SqlCeTransaction trans = null;
SqlCeEngine engine = new SqlCeEngine("Data Source = " + DB_NAME);
engine.CreateDatabase();
try {
conn = new SqlCeConnection("Data Source = " + DB_NAME);
conn.Open();
SqlCeTransaction trans = conn.BeginTransaction();
SqlCeCommand availableTable = conn.CreateCommand();
availableTable.Transaction = trans;
availableTable.CommandText = "CREATE TABLE Available(songId int,
songTitle nvarchar(200), songArtist nvarchar(200))";
availableTable.ExecuteNonQuery();
trans.Commit();
} catch {
trans.Rollback();
} finally {
if (conn != null && conn.State == ConnectionState.Open) {
conn.Close();
}
}
}
} /* CheckDb */
Storing songs in the database was done every time results were returned from the webservice with the following code:
private void OnGetSongs(IAsyncResult songsResult) {
this.availableSongsCache = this.GetService().EndGetSongs(songsResult);
Db.NewInstance().StoreSongs(this.availableSongsCache);
To store the songs, the table was first emptied, after which the new results were inserted all at once by using the following method:
public void StoreSongs(Song[] songs) {
this.CheckDB();
SqlCeConnection conn = null;
SqlCeTransaction trans = null;
try {
conn = new SqlCeConnection("Data Source = " + DB_NAME);
conn.Open();
trans = conn.BeginTransaction();
SqlCeCommand deleteSong = conn.CreateCommand();
deleteSong.Transaction = trans;
String deleteSql = "DELETE FROM Available";
deleteSong.CommandText = deleteSql;
deleteSong.ExecuteNonQuery();
SqlCeCommand insertSong = conn.CreateCommand();
String insertSql = "INSERT INTO Available(songId, songTitle, songArtist)
VALUES (?, ?, ?)";
insertSong.Transaction = trans;
insertSong.CommandText = insertSql;
foreach (Song song in songs) {
insertSong.Parameters.Clear();
insertSong.Parameters.Add("@songId", song.ID);
insertSong.Parameters.Add("@songTitle", song.Title);
insertSong.Parameters.Add("@songArtist", song.Artist);
insertSong.ExecuteNonQuery();
}
trans.Commit();
} catch (SqlCeException ex) {
trans.Rollback();
System.Windows.Forms.MessageBox.Show(FormatErrorMessage(ex));
} finally {
if (conn != null && conn.State == ConnectionState.Open) {
conn.Close();
}
}
} /* StoreSongs */
Retrieving the songs can be done exactly as with the regular
SqlClient classes.
On the client-side, a
Pocket PC application was used. Since this has no guaranteed connectivity, some additional techniques had to be used to improve the end-user experience.
First of all, when calling a webservice from a
Pocket PC, it was possible that the call would take a long time. If this would have been done synchronously, the application would lock up as long as the call was being processed. To prevent this, the call was made asynchronously and a progress bar was displayed.
To achieve this, a
Timer was used from the
System.Threading class, to update the progress bar when it was visible. This caused the timer to run on a different thread from the application, and make call-backs at certain intervals to update the user interface containing the progress bar.
The following code was used to easily start and stop the progress bar:
using System;
using System.Threading;
namespace MediaService.Pocket {
public class MediaForm : System.Windows.Forms.Form {
private System.Threading.Timer progressTimer;
private OpenNETCF.Windows.Forms.ProgressBarEx asyncProgress;
private System.Windows.Forms.Label asyncLabel;
public MediaForm(Int32 userId, String authTicket) {
TimerCallback progressDelegate = new TimerCallback(this.UpdateProgress);
this.progressTimer = new System.Threading.Timer(progressDelegate, null,
Timeout.Infinite, Timeout.Infinite);
} /* MediaForm */
private void StartProgress(ProgressEnum progressType) {
// Reset progressbar and show
this.asyncProgress.Value = this.asyncProgress.Minimum;
this.asyncProgress.Visible = true;
this.asyncLabel.Visible = true;
this.asyncLabel.Text = "Retrieving Content";
this.progressTimer.Change(0, 100);
} /* StartProgress */
protected void UpdateProgress(Object state) {
if (this.asyncProgress.Value + 1 > this.asyncProgress.Maximum) {
this.asyncProgress.Value = this.asyncProgress.Minimum;
} else {
this.asyncProgress.Value++;
}
} /* UpdateProgress */
private void StopProgress() {
this.progressTimer.Change(Timeout.Infinite, Timeout.Infinite);
this.asyncProgress.Visible = false;
this.asyncLabel.Visible = false;
} /* StopProgress */
After the progress bar was started, an asynchronous call was made to the webservice, preventing the application to lock up, using the following syntax:
AsyncCallback callBack = new AsyncCallback(this.OnGetSongs);
IAsyncResult songsResult = this.GetService().BeginGetSongs(callBack, null);
This started the call to the webservice on a different thread, and when the webservice call finished, it called back to the
OnGetSongs method in this case. In this method, the results were retrieved and the user interface was updated.
private void OnGetSongs(IAsyncResult songsResult) {
this.availableSongsCache = this.GetService().EndGetSongs(songsResult);
if (this.InvokeRequired()) {
this.Invoke(new EventHandler(this.UpdateAvailableSongs));
} else {
this.UpdateAvailableSongs(this, System.EventArgs.Empty);
}
} /* OnGetSongs */
It was possible that the callback occurred from a different thread. In that case it was not possible to update the user interface, since the thread did not own the form controls. To detect if the callback occurred on another thread or not, the following code was used:
namespace MediaService.Pocket {
public class MediaForm : System.Windows.Forms.Form {
private readonly Thread formThread = Thread.CurrentThread;
private Boolean InvokeRequired() {
return !this.formThread.Equals(Thread.CurrentThread);
} /* InvokeRequired */
If the callback happened on another thread, the Invoke method had to be used to handle the update of the user interface on the thread that owned the interface. For this reason, the method updating the interface had to have the following signature:
private void UpdateAvailableSongs(object sender, EventArgs e) {
At this point, it was possible to make a webservice call without locking the user interface, and informing the user something is going on thanks to the progress bar.