Refactor calcurse-caldav to use httplib2
This will allow much more flexibility and less code duplication when adding OAuth2 support. OAuth2 support will require this library as it is a dependency of oauth2client. The documentation was updated to reflect the new dependency. Signed-off-by: Randy Ramos <rramos1295@gmail.com> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
parent
2d1e6e394d
commit
1e1d61585d
@ -18,7 +18,9 @@ Usage
|
|||||||
|
|
||||||
calcurse-caldav requires an up-to-date version of calcurse and a configuration
|
calcurse-caldav requires an up-to-date version of calcurse and a configuration
|
||||||
file located at ~/.calcurse/caldav/config. An example configuration file can be
|
file located at ~/.calcurse/caldav/config. An example configuration file can be
|
||||||
found under contrib/caldav/config.sample in the calcurse source tree.
|
found under contrib/caldav/config.sample in the calcurse source tree. You will
|
||||||
|
also need to install *httplib2* for Python 3 using *pip* (e.g. `pip3 install
|
||||||
|
--user httplib2`) or your distribution's package manager.
|
||||||
|
|
||||||
If you run calcurse-caldav for the first time, you need to provide the --init
|
If you run calcurse-caldav for the first time, you need to provide the --init
|
||||||
argument. You can choose between the following initialization modes:
|
argument. You can choose between the following initialization modes:
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import configparser
|
import configparser
|
||||||
import http.client
|
import httplib2
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import ssl
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
@ -113,18 +112,14 @@ def remote_query(conn, cmd, path, additional_headers, body):
|
|||||||
if isinstance(body, str):
|
if isinstance(body, str):
|
||||||
body = body.encode('utf-8')
|
body = body.encode('utf-8')
|
||||||
|
|
||||||
conn.request(cmd, path, headers=headers, body=body)
|
resp, body = conn.request(path, cmd, body=body, headers=headers)
|
||||||
|
body = body.decode('utf-8')
|
||||||
resp = conn.getresponse()
|
|
||||||
|
|
||||||
if not resp:
|
if not resp:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
headers = resp.getheaders()
|
|
||||||
body = resp.read().decode('utf-8')
|
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
print("< Headers: " + repr(headers))
|
print("< Headers: " + repr(resp))
|
||||||
for line in body.splitlines():
|
for line in body.splitlines():
|
||||||
print("< " + line)
|
print("< " + line)
|
||||||
print()
|
print()
|
||||||
@ -134,7 +129,7 @@ def remote_query(conn, cmd, path, additional_headers, body):
|
|||||||
"while trying to access {}.").format(hostname, resp.status,
|
"while trying to access {}.").format(hostname, resp.status,
|
||||||
resp.reason, path))
|
resp.reason, path))
|
||||||
|
|
||||||
return (headers, body)
|
return (resp, body)
|
||||||
|
|
||||||
|
|
||||||
def get_etags(conn, hrefs=[]):
|
def get_etags(conn, hrefs=[]):
|
||||||
@ -155,7 +150,7 @@ def get_etags(conn, hrefs=[]):
|
|||||||
'<D:prop><D:getetag /></D:prop>'
|
'<D:prop><D:getetag /></D:prop>'
|
||||||
'<C:filter><C:comp-filter name="VCALENDAR" /></C:filter>'
|
'<C:filter><C:comp-filter name="VCALENDAR" /></C:filter>'
|
||||||
'</C:calendar-query>')
|
'</C:calendar-query>')
|
||||||
headers, body = remote_query(conn, "REPORT", path, headers, body)
|
headers, body = remote_query(conn, "REPORT", absolute_uri, headers, body)
|
||||||
if not headers:
|
if not headers:
|
||||||
return {}
|
return {}
|
||||||
root = etree.fromstring(body)
|
root = etree.fromstring(body)
|
||||||
@ -230,13 +225,13 @@ def save_syncdb(fn, syncdb):
|
|||||||
def push_object(conn, objhash):
|
def push_object(conn, objhash):
|
||||||
href = path + objhash + ".ics"
|
href = path + objhash + ".ics"
|
||||||
body = calcurse_export(objhash)
|
body = calcurse_export(objhash)
|
||||||
headers, body = remote_query(conn, "PUT", href, {}, body)
|
headers, body = remote_query(conn, "PUT", hostname_uri + href, {}, body)
|
||||||
|
|
||||||
if not headers:
|
if not headers:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
etag = None
|
etag = None
|
||||||
headerdict = dict((key.lower(), value) for key, value in headers)
|
headerdict = dict(headers)
|
||||||
if 'etag' in headerdict:
|
if 'etag' in headerdict:
|
||||||
etag = headerdict['etag']
|
etag = headerdict['etag']
|
||||||
while not etag:
|
while not etag:
|
||||||
@ -266,7 +261,7 @@ def push_objects(objhashes, conn, syncdb, etagdict):
|
|||||||
|
|
||||||
def remove_remote_object(conn, etag, href):
|
def remove_remote_object(conn, etag, href):
|
||||||
headers = {'If-Match': '"' + etag + '"'}
|
headers = {'If-Match': '"' + etag + '"'}
|
||||||
remote_query(conn, "DELETE", href, headers, None)
|
remote_query(conn, "DELETE", hostname_uri + href, headers, None)
|
||||||
|
|
||||||
|
|
||||||
def remove_remote_objects(objhashes, conn, syncdb, etagdict):
|
def remove_remote_objects(objhashes, conn, syncdb, etagdict):
|
||||||
@ -313,7 +308,7 @@ def pull_objects(hrefs_missing, hrefs_modified, conn, syncdb, etagdict):
|
|||||||
for href in (hrefs_missing | hrefs_modified):
|
for href in (hrefs_missing | hrefs_modified):
|
||||||
body += '<D:href>{}</D:href>'.format(href)
|
body += '<D:href>{}</D:href>'.format(href)
|
||||||
body += '</C:calendar-multiget>'
|
body += '</C:calendar-multiget>'
|
||||||
headers, body = remote_query(conn, "REPORT", path, {}, body)
|
headers, body = remote_query(conn, "REPORT", absolute_uri, {}, body)
|
||||||
|
|
||||||
root = etree.fromstring(body)
|
root = etree.fromstring(body)
|
||||||
|
|
||||||
@ -433,6 +428,8 @@ except FileNotFoundError as e:
|
|||||||
|
|
||||||
hostname = config.get('General', 'HostName')
|
hostname = config.get('General', 'HostName')
|
||||||
path = '/' + config.get('General', 'Path').strip('/') + '/'
|
path = '/' + config.get('General', 'Path').strip('/') + '/'
|
||||||
|
hostname_uri = 'https://' + hostname
|
||||||
|
absolute_uri = hostname_uri + path
|
||||||
|
|
||||||
if config.has_option('General', 'InsecureSSL'):
|
if config.has_option('General', 'InsecureSSL'):
|
||||||
insecure_ssl = config.getboolean('General', 'InsecureSSL')
|
insecure_ssl = config.getboolean('General', 'InsecureSSL')
|
||||||
@ -499,18 +496,9 @@ try:
|
|||||||
# Connect to the server via HTTPs.
|
# Connect to the server via HTTPs.
|
||||||
if verbose:
|
if verbose:
|
||||||
print('Connecting to ' + hostname + '...')
|
print('Connecting to ' + hostname + '...')
|
||||||
|
conn = httplib2.Http()
|
||||||
if insecure_ssl:
|
if insecure_ssl:
|
||||||
try:
|
conn.disable_ssl_certificate_validation = True
|
||||||
context = ssl._create_unverified_context()
|
|
||||||
conn = http.client.HTTPSConnection(hostname, context=context)
|
|
||||||
except AttributeError:
|
|
||||||
# Python versions prior to 3.4.3 do not support
|
|
||||||
# ssl._create_unverified_context(). However, these versions do not
|
|
||||||
# seem to verify certificates by default so we can simply fall back
|
|
||||||
# to http.client.HTTPSConnection().
|
|
||||||
conn = http.client.HTTPSConnection(hostname)
|
|
||||||
else:
|
|
||||||
conn = http.client.HTTPSConnection(hostname)
|
|
||||||
|
|
||||||
if init:
|
if init:
|
||||||
# In initialization mode, start with an empty synchronization database.
|
# In initialization mode, start with an empty synchronization database.
|
||||||
@ -563,8 +551,6 @@ try:
|
|||||||
# Write the synchronization database.
|
# Write the synchronization database.
|
||||||
save_syncdb(syncdbfn, syncdb)
|
save_syncdb(syncdbfn, syncdb)
|
||||||
|
|
||||||
# Close the HTTPs connection.
|
|
||||||
conn.close()
|
|
||||||
finally:
|
finally:
|
||||||
# Remove lock file.
|
# Remove lock file.
|
||||||
os.remove(lockfn)
|
os.remove(lockfn)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user