Accessing Protrack API (an Object-Oriented Approach)

Overview

Protrack is one of the well-known web-based GPS tracking software and today we will learn how to access its API using C# and .NET Framework. We will create a little wrapper around the API and use it to track GPS devices in a target account. This lesson will show you some of object-oriented concepts in action.

Requirements

A Protrack account with one or more GPS devices available for testing. If you are facing permission problems contact your vendor or Protrack technical support.

Introduction

Accessing Protrack API (https://www.protrack365.com/api.html) is fairly simple, it’s all about creating simple HTTP requests and handling the results which are represented in JSON. The trick here is using OOP concepts to make our code more reusable and more maintainable.

Dependencies

Start by adding a reference to Newtonsoft JSON.NET (https://www.newtonsoft.com/json) library to your project. This can be directly downloaded from vendor’s website or installed through NuGet Manager.ِ

Abstraction

Base Request

Referring to Protrack API we can see that all functions (except the authorization function) accept an access token as a parameter. This token is valid for certain time (2 hours) and it can be received through authorization only.

We will start by creating a base class that will represent the request data. The source code is fairly self-explanatory:

  internal abstract class ProtrackRequest {
    /// <summary>
    /// Path to function.
    /// </summary>
    public abstract string BaseUri { get; }
    /// <summary>
    /// This is used by all functions except 'authorization'.
    /// </summary>
    public string AccessToken { get; set; }

    /// <summary>
    /// Returns the list of request parameters packaged in a dictionary. Where Key is parameter name and Value is parameter value.
    /// </summary>
    public virtual IDictionary<string, object> GetParams() {
      var list = new Dictionary<string, object>();
      if (AccessToken != null) // adding access token only if necessary
        list.Add("access_token", AccessToken);
      return list;
    }

    /// <summary>
    /// Returns the list of request parameters as a query string.
    /// </summary>
    public virtual string GetParamsQueryString() {
      string queryString = string.Empty;

      foreach (var itm in GetParams()) {
        // This will keep empty parameters. You can skip them if you like.
        string valueStr = string.Empty;

        if (itm.Value != null)
          valueStr = System.Uri.EscapeDataString(itm.Value.ToString()); 

        queryString += string.Format("{0}={1}&", itm.Key, valueStr);
      }

      return queryString;
    }

    /// <summary>
    /// Returns full request signature (request URI along with parameter query string.)
    /// </summary>
    public virtual string GetRequestUri() {
      return BaseUri + "?" + GetParamsQueryString();
    }

    public override string ToString() {
      return GetRequestUri();
    }
  }

From the above code we can see that every request class derived from the base will have to fill its path (BaseUri property; mandatory) and parameter list (GetParams() method; optional).

Base Response

Referring to the Protrack API again we can see that every call response returned from the server besides being in JSON format, have two common attributes: code and message. The code may refer to one of the error codes available as a list in the API reference, while the message is the description. Keeping those two attributes in mind, we can create our response class:

  internal class ProtrackResponse {
    [JsonProperty("code")]
    public int Code { get; set; }

    [JsonIgnore]
    public ProtrackResponseCode ResponseCode { get { return (ProtrackResponseCode)Code; } }
    [JsonProperty("message")]
    public string Message { get; set; }
  }


  internal enum ProtrackResponseCode {
    Success = 0,
    SystemError = 10000,
    UnknownRequest = 10001,
    LoginTimeout = 10002,
    Unauthorized = 10003,
    ParameterError = 10004,
    MissingParameter = 10005,
    ParamOutOfRange = 10006,
    PermissionDenied = 10007,
    RequestLimit = 10009,
    AccessTokenNotExist = 10010,
    AccessTokenInvalid = 10011,
    AccessTokenExpired = 10012,
    ImeiUnauthorized = 10013,
    RequestTimeError = 10014,
    LoginFailed = 20001,
    TargetNotExist = 20005,
    DeviceOffline = 20017,
    SendCommandFailed = 20018,
    NoData = 20023,
    TargetExpired = 20046,
    Unsupported = 20048
  }

As response class need to be instantiated, we cannot just mark it as abstract. Abstract classes cannot be instantiated.

API Wrapper

Now the actual code that connects things together. This code represents the wrapper itself. The code is very generic. We will add function wrappers later.

  class ProtrackWrapper {
    protected string Account { get; set; }
    protected string Password { get; set; }
    /// <summary>
    /// API base URI
    /// </summary>
    protected string BaseUri { get { return "http://api.protrack365.com"; } }
    /// <summary>
    /// This will be used for all requests
    /// </summary>
    public string AccessToken { get; protected set; }
    /// <summary>
    /// Access token expiry date
    /// </summary>
    public DateTime? AccessTokenExpiresOnUtc { get; protected set; }


    public ProtrackWrapper(string account, string password) {
      this.Account = account;
      this.Password = password;
    }

    /// <summary>
    /// Returns a response from a web resource.
    /// </summary>
    /// <returns>Response represented as string.</returns>
    protected static string GetResponse(string requestUri) {
      HttpWebRequest req = WebRequest.CreateHttp(requestUri);
      using (var rsp = req.GetResponse())
      using (var stm = rsp.GetResponseStream())
      using (var rdr = new StreamReader(stm)) {
        return rdr.ReadToEnd();
      }
    }

    public virtual T GetResponse<T>(ProtrackRequest req) where T : ProtrackResponse {
      var requestUrl = new Uri(new Uri(BaseUri), req.GetRequestUri()).ToString();

      var rspStr = GetResponse(requestUrl);

      T rsp = JsonConvert.DeserializeObject<T>(rspStr);
      // should not throw a generic exception, 
      // and should not throw an exception for just everything
      // we will just keep this for now!
      if (rsp.Code != 0)
        throw new Exception(rsp.Message);

      return rsp;
    }
  }

As you can see in the above code, we used static polymorphism to create two versions of GetResponse(), one that returns bare response for bare request URI, and another one that accepts a typed request object and returned a typed response object. In fact, the other version is a generic one that returns only objects that derive from ProtrackResponse.

Authorization

To call any API function you need an access token, and this can be retrieved through the authorization function. The authorization function accepts three arguments: request time (in Unix format), username (i.e. account), and signature.

Unix Time

As described by Wikipedia, Unix time (also known as POSIX time or UNIX Epoch time) is a system for describing a point in time. It is the number of seconds that have elapsed since 00:00:00 Thursday, 1 January 1970, Coordinated Universal Time (UTC), minus leap seconds.

Unix time format will be used throughout the API, so we have created a helper class for it:

  static class UnixTimeHelper {
    /// <summary>
    /// Converts DateTime to Unix time.
    /// </summary>
    public static long ToUnixTime(this DateTime time) {
      var totalSeconds = (long)(time.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;

      if (totalSeconds < 0)
        throw new ArgumentOutOfRangeException("Unix time starts Jan 1 1970 00:00:00");

      return totalSeconds;
    }
    /// <summary>
    /// Converts Unix time to DateTime.
    /// </summary>
    public static DateTime ToDateTime(long unixTime) {
      return new DateTime(1970, 1, 1).Add(TimeSpan.FromSeconds(unixTime));
    }
}

You do not have to worry about leap seconds as System.DateTime does not take leap seconds into account.

Signature

Signature is an MD5 hash of a combination of MD5 password hash and request time (in Unix format). In other words:

signature = MD5 ( MD5(password) + unix_time ) 

Signature is represented as 32 bytes lower-case string.

Authorization Request

The authorization request class is as follows:

  internal class ProtrackAuthorizationRequest : ProtrackRequest {
    /// <summary>
    /// Path to function.
    /// </summary>
    public override string BaseUri { get { return "api/authorization"; } }
    public string Account { get; protected set; }
    protected string Password { get; set; }
    public DateTime RequestTimeUtc { get; private set; }

    public ProtrackAuthorizationRequest() { }
    public ProtrackAuthorizationRequest(string account, string password) {
      this.Account = account;
      this.Password = password;
    }

    public override IDictionary<string, object> GetParams() {
      RequestTimeUtc = DateTime.UtcNow;
      var unixTime = UnixTimeHelper.ToUnixTime(RequestTimeUtc);

      string signature = GetSignature(unixTime);

      var list = base.GetParams(); // retrieving base parameters (if any)
      list.Add("time", unixTime);
      list.Add("account", this.Account);
      list.Add("signature", signature);

      return list;
    }

    private string GetSignature(long unixTime) {
      // signature is md5(md5(password) + time) encoded as a 32 bytes lower-case characters.
      var signature = ProtrackHelper.HashMD5(this.Password);
      signature = ProtrackHelper.HashMD5(signature + unixTime.ToString());
      return signature;
    }
  }

As you can see, you need to provide the function path through BaseUri. And by overriding GetParams() you can provide your parameter list.

To make things work, here’s the declaration of the MD5 hashing function:

  static class ProtrackHelper {
    public static string HashMD5(string input) {
      byte[] data = System.Text.Encoding.UTF8.GetBytes(input);
      data = System.Security.Cryptography.MD5.Create().ComputeHash(data);
      return BitConverter.ToString(data).Replace("-", "").ToLower();
    }
  }

Authorization Response

The authorization response class is fairly simple. It reflects the JSON response data returned from the server. While ProtrackAuthorizationResponse focuses on authorization attributes, the base ProtrackResponse has the two common attributes, code and message.

  internal class ProtrackAuthorizationResponse : ProtrackResponse {
    [JsonProperty("record")]
    public ProtrackAuthorizationRecord Record { get; set; }
  }

  internal class ProtrackAuthorizationRecord {
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    [JsonProperty("expires_in")]
    public int ExpiresInSeconds { get; set; }
  }

We tagged properties with JsonPropertyAttribute attribute to allow our code to use different names for properties.

Connecting Things Together

Now we can add the following authorization code to the wrapper class:

    public void Authorize() {
      this.AccessToken = null;
      this.AccessTokenExpiresOnUtc = null;

      var req = new ProtrackAuthorizationRequest(this.Account, this.Password);
      var rsp = GetResponse<ProtrackAuthorizationResponse>(req);
      
      // updating access token and expiration time
      this.AccessToken = rsp.Record.AccessToken;
      this.AccessTokenExpiresOnUtc = req.RequestTimeUtc.AddSeconds(rsp.Record.ExpiresInSeconds);
    }

Now test your code and check if everything is going well:

      var wrapper = new ProtrackWrapper("test", "123456");
      wrapper.Authorize( );
      Console.WriteLine("Authorization code is: {0}", wrapper.AccessToken);.
      // Prints:
      // Authorization code is: A156321......69a0ef614ef3f582

Tracking

Now that everything is going well, we can move next to the tracking function. The tracking function accepts one or more GPS device server IMEI codes and returns latest coordinates for each code. Device server IMEI can be found through the Protrack web/mobile interface or through Param# command (specific GPS device models only.)

Track Request

Now that we have out requirements list create the request class:

  internal class ProtrackTrackRequest : ProtrackRequest {
    public override string BaseUri { get { return "api/track"; } }
    public string[] ImeiList { get; set; }

    public ProtrackTrackRequest() {

    }

    public ProtrackTrackRequest(string accessToken, string[] imeiList) {
      this.AccessToken = accessToken;
      this.ImeiList = imeiList;
    }

    public override IDictionary<string, object> GetParams() {
      var list = base.GetParams();
      list.Add("imeis", string.Join(",", ImeiList));

      return list;
    }
  }

Track Response

The response class lists the attributes that are returned from the server. I did not list all attributes, just for clarity.

  internal class ProtrackTrackResponse : ProtrackResponse {
    [JsonProperty("record")]
    public ProtrackTrackRecord[] Records { get; set; }
  }

  internal class ProtrackTrackRecord {
    [JsonProperty("imei")]
    public string IMEI { get; set; }
    [JsonProperty("longitude")]
    public decimal Longitude { get; set; }
    [JsonProperty("latitude")]
    public decimal Latitude { get; set; }
    [JsonProperty("systemtime")]
    public long SystemUnixTime { get; set; }
    [JsonProperty("gpstime")]
    public long GpsUnixTime { get; set; }

    // To make things easier, we have made extra DateTime properties
    // An alternative is to create a custom JSON converter for unix time
    public DateTime SystemTimeUtc { get { return UnixTimeHelper.ToDateTime(SystemUnixTime); } }
    public DateTime GpsTimeUtc { get { return UnixTimeHelper.ToDateTime(GpsUnixTime); } }

    // add any field you like
  } 

Connecting Things Together

Now add the following code to the wrapper class. Notice how we test access token expiration before making our request:

    public ProtrackTrackRecord Track(string imei) {
      return Track(new string[] { imei })[0];
    }
    public ProtrackTrackRecord[] Track(string[] imeiList) {
      if (this.AccessToken == null || DateTime.UtcNow >= this.AccessTokenExpiresOnUtc) {
        Authorize();
      }

      var req = new ProtrackTrackRequest(this.AccessToken, imeiList);
      var rsp = GetResponse<ProtrackTrackResponse>(req);

      return rsp.Records;
    }

And test:

      var track = wrapper.Track("123456789012345");
      Console.WriteLine("{0},{1},{2}", track.Latitude, track.Longitude, track.GpsTimeUtc);
      // Prints
      // 30.193456, 31.463092, 15 / 07 / 2019 19:41:38

One Step Further

In the previous response code as you can notice in those lines we have added two extra properties to convert Unix time to DateTime:

    public DateTime SystemTimeUtc { get { return UnixTimeHelper.ToDateTime(SystemUnixTime); } }
    public DateTime GpsTimeUtc { get { return UnixTimeHelper.ToDateTime(GpsUnixTime); } }

An alternative is to use a JSON converter:

  internal class JsonUnixTimeConverter : Newtonsoft.Json.Converters.DateTimeConverterBase {
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
      if (reader.TokenType != JsonToken.Integer)
        throw new Exception("Unexpected token type.");

      var unixTime = (long)reader.Value;

      return UnixTimeHelper.ToDateTime(unixTime);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
      if (false == value is DateTime)
        throw new Exception("Unexpected object type.");

      var dateTime = (DateTime)value;

      var unixTime = UnixTimeHelper.ToUnixTime(dateTime);

      writer.WriteValue(unixTime);
    }
  }

You will need to modify the ProtrackTrackRecord class:

    [JsonProperty("systemtime")]
    public DateTime SystemTimeUtc { get; set; }
    [JsonProperty("gpstime")]
    public DateTime GpsTimeUtc { get; set; }

And ProtrackWrapper. GetResponse<T> method:

      T rsp = JsonConvert.DeserializeObject<T>(rspStr, new JsonUnixTimeConverter());

Playback

Besides the access token, the playback method accepts single IMEI code, range start time and end time (both in Unix format.)

Playback Request

You can easily guess the request class code:

  internal class ProtrackPlaybackRequest : ProtrackRequest {
    public override string BaseUri { get { return "api/playback"; } }
    public string Imei { get; set; }
    public DateTime BeginTimeUtc{ get; set; }
    public DateTime EndTimeUtc { get; set; }

    public ProtrackPlaybackRequest() {

    }

    public ProtrackPlaybackRequest(string accessToken, string imei, DateTime beginTimeUtc, DateTime endTimeUtc) {
      this.AccessToken = accessToken;
      this.Imei = imei;
      this.BeginTimeUtc = beginTimeUtc;
      this.EndTimeUtc = endTimeUtc;
    }

    public override IDictionary<string, object> GetParams() {
      var list = base.GetParams();
      list.Add("imei", this.Imei);
      list.Add("begintime", UnixTimeHelper.ToUnixTime(BeginTimeUtc));
      list.Add("endtime", UnixTimeHelper.ToUnixTime(EndTimeUtc));

      return list;
    }
  }

Playback Response

The response class is fairly simple too:

  internal class ProtrackPlaybackResponse : ProtrackResponse {
    [JsonProperty("record")]
    public string RecordString { get; set; }

    // a custom JSON converter can be used here too
    public ProtrackPlaybackRecord[] GetRecords() {
      var recordsStrList = RecordString.Split(';');
      List<ProtrackPlaybackRecord> records = new List<ConsoleApp.ProtrackPlaybackRecord>(recordsStrList.Length);

      foreach (var recordStr in recordsStrList) {
        if (recordStr.Length == 0)
          continue;

        var record = new ProtrackPlaybackRecord(recordStr);
        records.Add(record);
      }

      return records.ToArray();
    }
  }

  internal class ProtrackPlaybackRecord {
    public decimal Longitude { get; set; }
    public decimal Latitude { get; set; }
    public DateTime GpsTimeUtc { get; set; }
    public int Speed { get; set; }
    public int Course { get; set; }

    public ProtrackPlaybackRecord() {

    }
    public ProtrackPlaybackRecord(string str) {
      string[] args = str.Split(',');

      Longitude = decimal.Parse(args[0]);
      Latitude = decimal.Parse(args[1]);
      GpsTimeUtc = UnixTimeHelper.ToDateTime(int.Parse(args[2]));
      Speed = int.Parse(args[3]);
      Course = int.Parse(args[4]);
    }
  }

Connecting Things Together

ProtrackWrapper code:

    public ProtrackPlaybackRecord[] Playback(string imei, DateTime beginTimeUtc, DateTime endTimeUtc) {
      if (this.AccessToken == null || DateTime.UtcNow >= this.AccessTokenExpiresOnUtc) {
        Authorize();
      }

      var req = new ProtrackPlaybackRequest(this.AccessToken, imei, beginTimeUtc, endTimeUtc);
      var rsp = GetResponse<ProtrackPlaybackResponse>(req);

      return rsp.GetRecords();
    }

And test:

      var records = wrapper.Playback("123456789012345", DateTime.UtcNow, DateTime.Today);
      foreach (var rec in records)
        Console.WriteLine("{0},{1},{2}", rec.GpsTimeUtc, rec.Latitude, rec.Longitude);

What’s Next

Using the above mentioned mechanism, you can easily create wrappers for the rest of API functions. I will be happy to receive your feedback and comments over this code.

Full Code Listing

Full code listing is available for download here: https://app.box.com/s/0nqvt7atf6hjueixk18qt23a9b3b5g7u

PM Notebook – Chapter 1: Introduction

This chapter is part of my personal notebook for PMP preparation. Check out the full notebook here.

This notebook summarizes project management concepts and terms in PMP context. Chapter 1 is an introduction to project management. Contents of this chapter are as follow:

  • Project Management
  • Portfolio, Program, Project and Operations
  • Project Initiation Context
  • Benefits Realization Management
    • Identify Benefits
    • Execute Benefits
    • Sustain Benefits
  • Business Documents
    • Business Case
    • Benefits Management Plan
  • Project Selection Methods
    • Benefits Measurement Methods (Comparative Approach)
      • Depreciation
      • Return on Investment (ROI)
      • Future Value (FV)
      • Present Value (PV)
      • Net Present Value (NPV)
      • Internal Rate of Return (IRR)
      • Other Methods
    • Constrained Optimization Methods / Mathematical Model
    • Non-Financial Considerations
  • Constraints
    • Theory of Constraints
  • Project Lifecycle
  • Project Management Methodologies (PMM)
    • Predictive Lifecycle / Plan-Driven / Waterfall
    • Iterative and Incremental Lifecycles / Iterations
    • Adaptive Lifecycle / Change-Driven / Agile
      • Characteristics
      • Agile Release Planning
      • Iteration Burndown Chart
      • Terms
      • Scrum
      • Extreme Programming (XP)
    • Hybrid Lifecycle / Structured Agile
    • Gates Methodology
    • Integrated Project Management (IPM)
    • Projects integration Sustainable Methods (PRiSM)
    • Projects IN Controlled Environments (PRINCE2)
  • Project Documents
  • Additional Terms

Sharing with you My Personal PM Notebook

Hello everybody and welcome to my blog. Starting from today I will be sharing part of my personal PMP-preparation notebook every week. This notebook summarizes project management concepts and terms in PMP context.

During my journey to PMP, I have gathered data and information in this document from various reliable sources, so NONE of this data/information is a property of mine. And NONE is intended for profit in any way. Please use this as your personal reference for ITTOs, terms, and formulas and feel free to manipulate it the way you like as long as it’s for your personal usage.

The contents of this notebook are as follow:

  • Chapter 1: Introduction
  • Chapter 2: Organizations
  • Chapter 3: The Process Framework
  • Chapter 4: Integration Management
  • Chapter 5: Scope Management
  • Chapter 6: Schedule Management
  • Chapter 7: Cost Management
  • Chapter 8: Quality Management
  • Chapter 9: Resources Management
  • Chapter 10: Communication Management
  • Chapter 11: Risk Management
  • Chapter 12: Procurement Management
  • Chapter 13: Stakeholder Management
  • Chapter 14: Professional and Social Responsibility
  • Appendix A: Data Analysis Techniques
  • Appendix B: Data Gathering Techniques
  • Appendix C: Data Representation Tools
  • Appendix D: Decision-Making Techniques
  • Appendix E: Estimating Techniques
  • Appendix F: Forecasting Methods
  • Appendix G: Interpersonal/Team/Soft Skills
  • Appendix H: Formulas

Keep an eye on this category for post updates or use the above content list to navigate to the desired part. Links will updated whenever a part is published.

Please let me know your feedback and comments! Have a great weekend!

I’m Back!

Hello everyone, I’m back!

It’s been a few, some good, some terrible, years since my last blog post. I have been very busy since I started working at Continental School of Cairo. I have done quite a lot of projects there, I got promoted, I run a team, I failed thousand times, and I won.

Despite that, the last two years were not easy at all, actually they were terrible. I feel more tightened and totally overwhelmed by an increasing number of non-related time-consuming tasks, my burn-down chart is “burning-up”, really stressed, and don’t have any time for a vacation. I realize that I need a change, a big one! I’m really disappointed and need to get back to track.

I started by delegating some routine tasks to my team, but that did not help much. The number of tasks that can be delegated is fairly small, or at least I’m so insecure when it comes to delegation. I always feel that I should do everything myself or at least watch someone does closely. So this has been sorely failed!

I have no other solutions and I cannot hold it anymore. I cannot decide whether it’s really time to quit. Quitting is one of the riskiest decision I may take and it does not sound like it’s a good one. I have a family and I have many plans that could be extremely affected by the decision.

On the bright side, the past few years has witnessed my marriage, my first baby, Layla, earning my MCSA (SQL Server) certificate, my MCSE (Data Management and Analytics), and finally my PMP credentials.  

Now the real question is: why I’m back? The answer is simply I want to be in other circles. I feel that my circle is so tight so that I cannot keep breathing. I want to be there somewhere else. I need to get out of the routine and get back to track. I’m back, full of enthusiasm and eagerness to help and get help!

So, little Amelie, your bones aren’t made of glass. You can take life’s knocks. If you let this chance go by, eventually your heart will become as dry and brittle as my skeleton. So… Go and get him, for Pete’s sake!

Raymond Dufayel, Amelie [2001]

Today Marks 1 Year of my PMP Credentials, Here are Some Exam Prep Links!

Hi, today, the 2nd of June 2019, marks 1 year of earning my PMP credentials. It took many months to understand and get a solid grasp of the various project management concepts. I read many books and plenty of online papers through my journey. And, believe me, it is one of the best, yet difficult, experiments I had. The more you read in project management, the more you learn, the stronger and more powerful your mind becomes, and the more you can handle in your work and your personal life.

During the journey, I used Joseph Phillips’s Udemy course to earn the PDUs. Joseph is one of the great educators in this area, yet his Udemy courses are much cheaper than other competitors. And his Facebook group is very much helpful than any other group.

Finally, for whom preparing for the exam, here are some exam simulators and training questions for your final preparations. I have divided the links into 3 groups and ordered them the way I see the best. Most of them are FREE while some require very little cost. It’s worth mentioning that you will see NONE of those questions in the real exam. And the real exam is… really… hard. The only advice I can give you is to focus on EVERY word.

Group A:
http://www.oliverlehmann.com/pmp-self-test/100-free-questions.htm
https://www.simplilearn.com/pmp-exam-prep-free-practice-test
https://pmsimulatorapp.com/simulator
https://www.tutorialspoint.com/pmp-exams/pmp_mock_exams.htm
http://www.alvarisys.com/pmpsampletest.html
https://www.projectmanagement.com/PMchallenge/

Group B:
http://www.passtheprojectexam.com/
https://www.preparepm.com/mock1.html
https://www.preparepm.com/mock2.html
http://www.tutorialspoint.com/pmp-exams/pmp_mock_exams.htm
http://www.examcentral.net/pmp/pmp-exam-questions
http://free.pm-exam-simulator.com
http://www.pmppracticeexam.org
https://www.tutorialspoint.com/pmp-exams/pmp_sample_questions.htm
http://www.betterpm.com/member-home-page
http://pmpexamforfree.com
http://exampremium.com

Group C:
http://pmzilla.com/forums/pmpexampreparation/pmpcapmexamquestionbank
https://www.iexamonline.com/exam/pmp-certification-practice-online
https://edward-designer.com/web/list-of-free-pmp-exam-questions
http://www.certgear.com/products/preview/pmp_certification/index.html
https://206-free-pmp-exam-questions.blogspot.com/p/questions-1-20.html
https://pmstudycircle.com/pmp-question-bank
https://pmsimulatorapp.com/home
https://www.project-management-prepcast.com/pmp-practice-exam-questions-sample-test
https://www.project-management-prepcast.com/2018-pmp-exam-update-project
https://www.testprepreview.com/modules/pmp.htm
http://www.free-pm-exam-questions.com
http://www.pmstudy.com/PMP-Exam-Resources/freeSimulatedTest.asp

P.S.: Here’s a copy of my PMP certificate. Say Mashallah! 🙂

A copy of my PMP certificate

Intellisense for Google Maps API in Visual Studio

If you wish to incorporate Intellisense for the Google Maps JavaScript API in Visual Studio you can use the following open-source project for version 3: http://gmapvsdoc.codeplex.com/. For version 2 of the API, you can use the following project: http://gmapjs.codeplex.com/. [Quick start guide and documentation is available in each link.]

It’s not just doing it again and again!

I’d like to quote some lines from Peter Norvig’s masterpiece: Learn Programming in 10 Years:

The key is deliberative practice: not just doing it again and again, but challenging yourself with a task that is just beyond your current ability, trying it, analyzing your performance while and after doing it, and correcting any mistakes. Then repeat. And repeat again.

And…

In any case, book learning alone won’t be enough. “Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter” says Eric Raymond, author of The New Hacker’s Dictionary.

Also another amazing article that you’ll like to check is Herbert Klaeren’s Epigrams on Programming.

Using Video Markers in Silverlight

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.


Definition

Markers are text annotations embedded at certain points in a media file. They help you in several cases. For example, you might use markers to set captions or subtitles for a video file, or maybe you use it to identify particular points in a media file so you can play the media starting at particular points.

 

Marker Support

Silverlight has native support for video markers, you can subscribe to the MarkerReached event of MediaElement control to get notified whenever you reach a marker. However, in order to use video markers you need to encode them in the video file. The best application that you can use to encode markers in a video file for Silverlight is Microsoft Expression Encoder.

 

Marker Encoding

After you have your video file added to Expression Encoder, you can use the timeline to set the markers at the desired locations. You can add a marker by moving to the position where you want to set the marker at, then right-clicking the timeline and selecting Add Marker to add a new marker.

You then go to the Markers window where you can set marker text (value) and manage existing markers.

Notice that every marker is identified on the timeline by a symbol like diamond (♦). You can change marker position by moving this symbol, and you can remove it by right-clicking it and choosing Remove (you can also use the Markers window.)

Now encode the video and prepare it to be used in Silverlight.

 

Code

After you have created your MediaElement and prepared it with the video, you can then subscribe to the MarkerReached event and handle the marker when the video reaches it. An example of handling markers is to display the marker text on the screen if, for example, it is a caption/subtitle for the video:

void theMedia_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
{
    theTextBlock.Text = e.Marker.Text;
}

And you can also load the markers to a list box (for example) and let the user moves to the position of the markers he chooses (worth mentioning that markers are only available after MediaOpened event is raised):

void theList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (this.theList.SelectedIndex > -1)
        theMedia.Position = theMedia.Markers[theList.SelectedIndex].Time;
}

Makes sense?