Upgrading to GitLab 11.11.0.

Commit 823d879c authored by Sriram Karra's avatar Sriram Karra

Heavy lifting to make GC-GC sync possible

- A builder pattern to build an Asynk instance from command line
  arguments with validation

- Implement the GC login logic in the Collection infrastructure

- A lot of related code rearrangement and cleanup
parent 8e4d4ea1
This diff is collapsed.
......@@ -24,18 +24,96 @@
## (DB ID, Store ID, Folder ID)
##
from abc import ABCMeta, abstractmethod
import netrc
from abc import ABCMeta, abstractmethod
from pimdb_bb import BBPIMDB
from gdata.client import BadAuthentication
from pimdb_gc import GCPIMDB
try:
from pimdb_ol import OLPIMDB
except ImportError, e:
## This could mean one of two things: (a) we are not on Windows, or (b)
## some of th relevant supporting stuff is not installed (like
## pywin32). these error cases are handled elsewhere, so move on.
pass
class AsynkCollectionError(Exception):
pass
class NetrcAuth:
"""A helper Singletone class to parse netrc files and to look for the
right authentication information for a specified collection."""
def __init__ (self):
try:
self.netrc = None
self.netrc = netrc.netrc()
except IOError, e:
logging.info('~/.netrc not found.')
except netrc.NetrcParseError, e:
logging.warning('Ignoring ~/.netrc: could not parse it (%s)', e)
def get_auth (self, pname, dbid, coll_n):
"""Look for the right collection, parsing the netrc for both styles of
machine names - the old one which was of the form gc_pname and the new
one of the form gc1_pname. Note that this means the caller should know
how to use the coll_n parameter and should expect that parameter to be
ignored if that suits the netrc data!"""
tries = ['%s%s_%s' % (dbid, coll_n, pname), '%s_%s' % (dbid, pname)]
for mach in tries:
res = self.netrc.authenticators(mach)
if res is not None:
return res[0], res[2]
return None, None
netrc = NetrcAuth()
## FIXME: The Profile class needs a little more work; It really calls for a
## refactor of state.py and this class perhaps fits into a file of its
## own. Let's take this up at a separate time as a separate refactoring
## altogether.
class Profile:
def __init__ (self, conf, pname=None, **kwargs):
self.set_conf(conf)
self.set_pname(pname)
self.read_netrc()
def read_netrc (self):
self.netrc = NetrcAuth()
##
## getter / setters
##
def get_conf (self):
return self.name
def set_conf (self, conf):
self.conf = conf
def get_pname (self):
return self.name
def set_pname (self, pname):
self.pname = pname
class Collection:
def __init__ (self, config=None, dbid=None, stid=None,
fid=None, pname=None):
self.config = config
fid=None, pname=None, colln=1):
self.set_config(config)
self.set_dbid(dbid)
self.set_db(None)
self.set_stid(stid)
self.set_fid(fid)
self.set_pname(pname)
self.set_colln(colln)
self.dbid = dbid
self.stid = stid
self.fid = fid
self.pname = pname
self.set_username(None)
self.set_pwd(None)
@abstractmethod
def login (self):
......@@ -46,11 +124,16 @@ class Collection:
pass
##
## Getters and Setters
##
def get_db (self):
return self.db
def set_db (self, db):
self.db = db
return db
def get_dbid (self):
return self.dbid
......@@ -74,25 +157,104 @@ class Collection:
def set_fid (self, fid):
self.fid = fid
def get_username (self):
return self.username
def set_username (self, username):
self.username = username
def get_pwd (self):
return self.pwd
def set_pwd (self, pwd):
self.pwd = pwd
def get_pname (self):
return self.pname
def set_pname (self, pname):
self.pname = pname
def get_config (self):
return self.config
def set_config (self, config):
self.config = config
def get_colln (self):
return self.colln
def set_colln (self, colln):
self.colln = colln
def all_set (self):
return (self.get_dbid() is not None and
self.get_stid() is not None and
self.get_fid() is not None)
##
## more serious action
##
def init_username_pwd (self):
"""Take in account the authentication credentials available from
different sources for this Collection, resolve any conflicts and set
the state to the right values."""
u = None
p = None
u, p = netrc.get_auth(self.get_pname(), self.get_dbid(),
self.get_colln())
cmd_u = self.get_username()
cmd_p = self.get_pwd()
if cmd_u is not None:
u = cmd_u
else:
while not u and self.force_username():
msg = 'Please enter username for %s%s: ' % (self.get_dbid(),
self.get_colln())
u = raw_input(msg)
if cmd_p is not None:
p = cmd_p
else:
while not p and self.force_pwd():
msg = 'Please enter password for %s%s: ' % (self.get_dbid(),
self.get_colln())
p = raw_input(msg)
self.set_username(u)
self.set_pwd(p)
def force_username (self):
return False
def force_pwd (self):
return False
##
## Finally some representation and other such object level methods
##
def __eq__ (self, other):
return (self.get_dbid() == other.get_dbid() and
self.get_stid() == other.get_stid() and
self.get_fid() == other.get_fid())
def repr (self):
def __str__ (self):
return str({'dbid' : self.dbid,
'stid' : self.stid,
'foid' : self.fid })
'foid' : self.fid,
'username' : self.get_username(),
'password' : self.get_pwd()})
class BBCollection(Collection):
def __init__ (self, config=None, stid=None, fid=None, pname=None):
def __init__ (self, config=None, stid=None, fid=None, pname=None, colln=1):
Collection.__init__(self, config=config, dbid='bb', stid=stid,
fid=fid, pname=pname)
fid=fid, pname=pname, colln=colln)
def login (self):
if self.get_stid() is not None:
......@@ -101,33 +263,66 @@ class BBCollection(Collection):
bbfn = '~/.bbdb'
if not bbfn:
raise AsynkError('No BBDB Store provided. Unable to initialize.')
raise AsynkCollectionError('Need BBDB Store to be specified')
bb = BBPIMDB(self.config, bbfn)
self.set_db(bb)
return self.set_db(bb)
return bb
class CDCollection(Collection):
def __init__ (self, config=None, stid=None, fid=None, pname=None):
def __init__ (self, config=None, stid=None, fid=None, pname=None, colln=1):
Collection.__init__(self, config=config, dbid='cd', stid=stid, fid=fid,
pname=pname)
pname=pname, colln=colln)
def force_username (self):
return True
def force_pwd (self):
return True
class EXCollection(Collection):
def __init__ (self, config=None, stid=None, fid=None, pname=None):
def __init__ (self, config=None, stid=None, fid=None, pname=None, colln=1):
Collection.__init__(self, config=config, dbid='ex', stid=stid, fid=fid,
pname=pname)
pname=pname, colln=colln)
def force_username (self):
return True
def force_pwd (self):
return True
class GCCollection(Collection):
def __init__ (self, config=None, stid=None, fid=None, pname=None):
def __init__ (self, config=None, stid=None, fid=None, pname=None, colln=1):
Collection.__init__(self, config=config, dbid='gc', stid=stid, fid=fid,
pname=pname)
pname=pname, colln=colln)
def login (self):
try:
pimgc = GCPIMDB(self.get_config(),
self.get_username(), self.get_pwd())
except BadAuthentication, e:
raise AsynkCollectionError('Invalid Google credentials (%s)' % e)
return self.set_db(pimgc)
def force_username (self):
return True
def force_pwd (self):
return True
class OLCollection(Collection):
def __init__ (self, config=None, stid=None, fid=None, pname=None):
Collection.__init__(self, config=config, dbid='ol', stid=stid, fid=fid,
pname=pname)
def login (self):
return OLPIMDB(self.get_config())
collection_id_to_class = {
'bb' : BBCollection,
'cd' : CDCollection,
......
......@@ -18,7 +18,7 @@
## You should have a copy of the license in the doc/ directory of ASynK. If
## not, see <http://www.gnu.org/licenses/>.
import logging, os, sys
import logging, os, string, sys
CUR_DIR = os.path.abspath('')
ASYNK_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
......@@ -30,9 +30,7 @@ import argparse, utils
from asynk_logger import ASynKLogger
from asynk_core import Asynk, AsynkParserError
from state import Config
class AsynkError(Exception):
pass
from state_collection import collection_id_to_class as coll_id_class
class AsynkInternalError(Exception):
pass
......@@ -144,6 +142,129 @@ def setup_parser ():
return p
class AsynkBuilderC:
"""A helper class written in the Builder design pattern to build a Asynk
class from the command line inputs and application configuration."""
def __init__ (self, uinps, config, alogger):
"""uinps is a Namespace object as returned from the parse_args()
routine of argparse module."""
level = string.upper(uinps.log)
if level:
alogger.consoleLogger.setLevel(getattr(logging, level))
self.asynk = Asynk(config, alogger)
self.validate_and_snarf_uinps(uinps)
def _snarf_store_ids (self, uinps):
if uinps.store is None:
return
for i, stid in enumerate(uinps.store):
coll = self.asynk.get_colls()[i]
coll.set_stid(stid)
def _snarf_auth_creds (self, uinps):
if uinps.gcpwd and len(uinps.gcpwd) > 2:
raise AsynkParserError('--gcpwd takes 1 or 2 arguments only')
if uinps.cduser and len(uinps.cduser) > 2:
raise AsynkParserError('--cduser takes 1 or 2 arguments only')
if uinps.cdpwd and len(uinps.cdpwd) > 2:
raise AsynkParserError('--cdpwd takes 1 or 2 arguments only')
if (uinps.cdpwd and len(uinps.cdpwd) > 1 and
uinps.gcpwd and len(uinps.gcpwd) > 1):
raise AsynkParserError('--cdpwd and --gcpwd should together have'
'only 2 values')
# FIXME: I think the following is a bit flawed if a gc and cd profile
# exists and the order is cd, gc.
if uinps.gcpwd:
for i, gcpwd in enumerate(uinps.gcpwd):
coll = self.asynk.get_colls()[i]
if gcpwd != 'None':
coll.set_pwd(gcpwd)
if uinps.cduser:
for i, cduser in enumerate(uinps.cduser):
coll = self.asynk.get_colls()[i]
if cduser != 'None':
coll.set_username(cduser)
if uinps.cdpwd:
for i, cdpwd in enumerate(uinps.cdpwd):
coll = self.asynk.get_colls()[i]
if cdpwd != 'None':
coll.set_pwd(cdpwd)
def _snarf_pname (self, uinps):
if uinps.name:
self.asynk.set_name(uinps.name)
else:
self.asynk.set_name(None)
def _snarf_folder_ids (self, uinps):
if uinps.folder:
for i, fid in enumerate(uinps.folder):
coll = self.asynk.get_colls()[i]
coll.set_fid(fid)
def _snarf_sync_dir (self, uinps):
if uinps.direction:
d = 'SYNC1WAY' if uinps.direction == '1way' else 'SYNC2WAY'
else:
d = None
self.asynk.set_sync_dir(d)
def validate_and_snarf_uinps (self, uinps):
# Most of the validation is already done by argparse. This is where we
# will do some additional sanity checking and consistency enforcement,
# mutual exclusion and so forth. In addition to this, every command
# will do some parsing and validation itself.
op = 'op_' + string.replace(uinps.op, '-', '_')
self.asynk.set_op(op)
# Let's start with the db flags
if uinps.db:
if len(uinps.db) > 2:
raise AsynkParserError('--db takes 1 or 2 arguments only')
for dbid in uinps.db:
coll = coll_id_class[dbid](config=self.asynk.get_config())
self.asynk.add_coll(coll)
else:
# Only a few operations do not need a db. Check for this and move
# on.
if not ((self.asynk.get_op() in ['op_startweb', 'op_sync']) or
(re.search('_profile', self.asynk.get_op()))):
raise AsynkParserError('--db needed for this operation.')
# The validation that follows is only relevant for command line
# usage.
if self.asynk.get_op() == 'op_startweb':
return
self.asynk.set_dry_run(uinps.dry_run)
self.asynk.set_sync_all(uinps.sync_all)
self._snarf_store_ids(uinps)
self._snarf_auth_creds(uinps)
self._snarf_pname(uinps)
self._snarf_folder_ids(uinps)
self._snarf_sync_dir(uinps)
self.asynk.set_label_re(uinps.label_regex)
self.asynk.set_conflict_resolve(uinps.conflict_resolve)
self.asynk.set_item_id(uinps.item)
# self.asynk.set_port(uinps.port)
def main (argv=sys.argv):
parser = setup_parser()
uinps = parser.parse_args()
......@@ -161,7 +282,7 @@ def main (argv=sys.argv):
logging.debug('Command line: "%s"', ' '.join(sys.argv))
try:
asynk = Asynk(uinps, config, alogger)
asynk = AsynkBuilderC(uinps, config, alogger).asynk
except AsynkParserError, e:
logging.critical('Error in User input: %s', e)
quit()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment