Make oauth2client support Windows-friendly locking.

Reviewed in http://codereview.appspot.com/6265043/.

Fixes issue #138.

Index: oauth2client/locked_file.py
===================================================================
new file mode 100644
diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py
index 1f756c7..60ac684 100644
--- a/oauth2client/multistore_file.py
+++ b/oauth2client/multistore_file.py
@@ -33,7 +33,6 @@
 
 import base64
 import errno
-import fcntl
 import logging
 import os
 import threading
@@ -41,6 +40,7 @@
 from anyjson import simplejson
 from client import Storage as BaseStorage
 from client import Credentials
+from locked_file import LockedFile
 
 logger = logging.getLogger(__name__)
 
@@ -94,9 +94,8 @@
 
     This will create the file if necessary.
     """
-    self._filename = filename
+    self._file = LockedFile(filename, 'r+b', 'rb')
     self._thread_lock = threading.Lock()
-    self._file_handle = None
     self._read_only = False
     self._warn_on_readonly = warn_on_readonly
 
@@ -176,30 +175,24 @@
     This method will not initialize the file. Instead it implements a
     simple version of "touch" to ensure the file has been created.
     """
-    if not os.path.exists(self._filename):
+    if not os.path.exists(self._file.filename()):
       old_umask = os.umask(0177)
       try:
-        open(self._filename, 'a+b').close()
+        open(self._file.filename(), 'a+b').close()
       finally:
         os.umask(old_umask)
 
   def _lock(self):
     """Lock the entire multistore."""
     self._thread_lock.acquire()
-    # Check to see if the file is writeable.
-    try:
-      self._file_handle = open(self._filename, 'r+b')
-      fcntl.lockf(self._file_handle.fileno(), fcntl.LOCK_EX)
-    except IOError, e:
-      if e.errno != errno.EACCES:
-        raise e
-      self._file_handle = open(self._filename, 'rb')
+    self._file.open_and_lock()
+    if not self._file.is_locked():
       self._read_only = True
       if self._warn_on_readonly:
         logger.warn('The credentials file (%s) is not writable. Opening in '
                     'read-only mode. Any refreshed credentials will only be '
-                    'valid for this run.' % self._filename)
-    if os.path.getsize(self._filename) == 0:
+                    'valid for this run.' % self._file.filename())
+    if os.path.getsize(self._file.filename()) == 0:
       logger.debug('Initializing empty multistore file')
       # The multistore is empty so write out an empty file.
       self._data = {}
@@ -214,9 +207,7 @@
 
   def _unlock(self):
     """Release the lock on the multistore."""
-    if not self._read_only:
-      fcntl.lockf(self._file_handle.fileno(), fcntl.LOCK_UN)
-    self._file_handle.close()
+    self._file.unlock_and_close()
     self._thread_lock.release()
 
   def _locked_json_read(self):
@@ -228,8 +219,8 @@
       The contents of the multistore decoded as JSON.
     """
     assert self._thread_lock.locked()
-    self._file_handle.seek(0)
-    return simplejson.load(self._file_handle)
+    self._file.file_handle().seek(0)
+    return simplejson.load(self._file.file_handle())
 
   def _locked_json_write(self, data):
     """Write a JSON serializable data structure to the multistore.
@@ -242,9 +233,9 @@
     assert self._thread_lock.locked()
     if self._read_only:
       return
-    self._file_handle.seek(0)
-    simplejson.dump(data, self._file_handle, sort_keys=True, indent=2)
-    self._file_handle.truncate()
+    self._file.file_handle().seek(0)
+    simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
+    self._file.file_handle().truncate()
 
   def _refresh_data_cache(self):
     """Refresh the contents of the multistore.