Skip to content

Commit

Permalink
Merge pull request #711 from insanum/vobject
Browse files Browse the repository at this point in the history
Add some friendlier help for the import command when missing vobject
  • Loading branch information
dbarnett authored Aug 25, 2024
2 parents 4bb0859 + 58ffd74 commit 5db218b
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 219 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
v4.4.0
* Fix lots of bugs by switching from deprecated oauth2client to
google_auth_oauthlib
* Friendlier help output when `import` command is missing vobject extra
* Handle encoding/decoding errors more gracefully by replacing with
placeholder chars instead of blowing up
* Fix `--lineart` option failing with unicode errors
Expand Down
231 changes: 53 additions & 178 deletions gcalcli/gcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
from dateutil.tz import tzlocal
from google_auth_oauthlib.flow import InstalledAppFlow # type: ignore
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError # type: ignore
from googleapiclient.errors import HttpError
from google.auth.transport.requests import Request # type: ignore

from . import actions, utils
from . import actions, ics, utils
from ._types import Cache, CalendarListEntry
from .actions import ACTIONS
from .conflicts import ShowConflicts
Expand Down Expand Up @@ -104,15 +104,6 @@ def _select_cals(self, selected_names):
# operate against
self.cals += matches

@staticmethod
def _localize_datetime(dt):
if not hasattr(dt, 'tzinfo'): # Why are we skipping these?
return dt
if dt.tzinfo is None:
return dt.replace(tzinfo=tzlocal())
else:
return dt.astimezone(tzlocal())

def _retry_with_backoff(self, method):
for n in range(self.max_retries):
try:
Expand Down Expand Up @@ -1055,15 +1046,15 @@ def _GetAllEvents(self, cal, events, end):
# all date events
event['s'] = parse(event['start']['date'])

event['s'] = self._localize_datetime(event['s'])
event['s'] = utils.localize_datetime(event['s'])

if 'dateTime' in event['end']:
event['e'] = parse(event['end']['dateTime'])
else:
# all date events
event['e'] = parse(event['end']['date'])

event['e'] = self._localize_datetime(event['e'])
event['e'] = utils.localize_datetime(event['e'])

# For all-day events, Google seems to assume that the event
# time is based in the UTC instead of the local timezone. Here
Expand Down Expand Up @@ -1428,129 +1419,21 @@ def Remind(self, minutes, command, use_reminders=False):

def ImportICS(self, verbose=False, dump=False, reminders=None,
icsFile=None):

def CreateEventFromVOBJ(ve):

event = {}

if verbose:
print('+----------------+')
print('| Calendar Event |')
print('+----------------+')

if hasattr(ve, 'summary'):
if verbose:
print('Event........%s' % ve.summary.value)
event['summary'] = ve.summary.value

if hasattr(ve, 'location'):
if verbose:
print('Location.....%s' % ve.location.value)
event['location'] = ve.location.value

if not hasattr(ve, 'dtstart') or not hasattr(ve, 'dtend'):
self.printer.err_msg(
'Error: event does not have a dtstart and dtend!\n'
)
return None

if verbose:
if ve.dtstart.value:
print('Start........%s' % ve.dtstart.value.isoformat())
if ve.dtend.value:
print('End..........%s' % ve.dtend.value.isoformat())
if ve.dtstart.value:
print('Local Start..%s' %
self._localize_datetime(ve.dtstart.value)
)
if ve.dtend.value:
print('Local End....%s' %
self._localize_datetime(ve.dtend.value)
)

if hasattr(ve, 'rrule'):
if verbose:
print('Recurrence...%s' % ve.rrule.value)

event['recurrence'] = ['RRULE:' + ve.rrule.value]

if hasattr(ve, 'dtstart') and ve.dtstart.value:
# XXX
# Timezone madness! Note that we're using the timezone for the
# calendar being added to. This is OK if the event is in the
# same timezone. This needs to be changed to use the timezone
# from the DTSTART and DTEND values. Problem is, for example,
# the TZID might be "Pacific Standard Time" and Google expects
# a timezone string like "America/Los_Angeles". Need to find a
# way in python to convert to the more specific timezone
# string.
# XXX
# print ve.dtstart.params['X-VOBJ-ORIGINAL-TZID'][0]
# print self.cals[0]['timeZone']
# print dir(ve.dtstart.value.tzinfo)
# print vars(ve.dtstart.value.tzinfo)

start = ve.dtstart.value.isoformat()
if isinstance(ve.dtstart.value, datetime):
event['start'] = {'dateTime': start,
'timeZone': self.cals[0]['timeZone']}
else:
event['start'] = {'date': start}

event = self._add_reminders(event, reminders)

# Can only have an end if we have a start, but not the other
# way around apparently... If there is no end, use the start
if hasattr(ve, 'dtend') and ve.dtend.value:
end = ve.dtend.value.isoformat()
if isinstance(ve.dtend.value, datetime):
event['end'] = {'dateTime': end,
'timeZone': self.cals[0]['timeZone']}
else:
event['end'] = {'date': end}

else:
event['end'] = event['start']

if hasattr(ve, 'description') and ve.description.value.strip():
descr = ve.description.value.strip()
if verbose:
print('Description:\n%s' % descr)
event['description'] = descr

if hasattr(ve, 'organizer'):
if ve.organizer.value.startswith('MAILTO:'):
email = ve.organizer.value[7:]
else:
email = ve.organizer.value
if verbose:
print('organizer:\n %s' % email)
event['organizer'] = {'displayName': ve.organizer.name,
'email': email}

if hasattr(ve, 'attendee_list'):
if verbose:
print('attendees:')
event['attendees'] = []
for attendee in ve.attendee_list:
if attendee.value.upper().startswith('MAILTO:'):
email = attendee.value[7:]
else:
email = attendee.value
if verbose:
print(' %s' % email)

event['attendees'].append({'displayName': attendee.name,
'email': email})

return event

try:
import vobject
except ImportError:
if not ics.has_vobject_support():
self.printer.err_msg(
'Python vobject module not installed!\n'
'Python vobject module not installed!\n'
)
self.printer.msg(
'To use the import command, you need to first install the '
'"vobject" extra.\n'
'For setup instructions, see '
"https://github.com/insanum/gcalcli and documentation for the "
'gcalcli package on your platform.\n')
sys_path_str = '\n '.join(sys.path)
self.printer.debug_msg(
'Searched for vobject using python interpreter at '
f'"{sys.executable}" with module search path:\n'
f" {sys_path_str}\n")
sys.exit(1)

if dump:
Expand All @@ -1568,53 +1451,45 @@ def CreateEventFromVOBJ(ve):
self.printer.err_msg('Error: ' + str(e) + '!\n')
sys.exit(1)

while True:
try:
v = next(vobject.readComponents(f))
except StopIteration:
break

for ve in v.vevent_list:
event = CreateEventFromVOBJ(ve)

if not event:
continue
events_to_import = ics.get_events(
f,
verbose=verbose,
default_tz=self.cals[0]['timeZone'],
printer=self.printer)
for event in events_to_import:
if not event:
continue

if dump:
continue
if dump:
continue

if not verbose:
new_event = self._retry_with_backoff(
self.get_events()
.insert(
calendarId=self.cals[0]['id'],
body=event
)
)
hlink = new_event.get('htmlLink')
self.printer.msg(
'New event added: %s\n' % hlink, 'green'
self._add_reminders(event, reminders)
if not verbose:
new_event = self._retry_with_backoff(
self.get_events().insert(
calendarId=self.cals[0]['id'], body=event
)
continue
)
hlink = new_event.get('htmlLink')
self.printer.msg('New event added: %s\n' % hlink, 'green')
continue

self.printer.msg('\n[S]kip [i]mport [q]uit: ', 'magenta')
val = input()
if not val or val.lower() == 's':
continue
if val.lower() == 'i':
new_event = self._retry_with_backoff(
self.get_events()
.insert(
calendarId=self.cals[0]['id'],
body=event
)
)
hlink = new_event.get('htmlLink')
self.printer.msg('New event added: %s\n' % hlink, 'green')
elif val.lower() == 'q':
sys.exit(0)
else:
self.printer.err_msg('Error: invalid input\n')
sys.exit(1)
self.printer.msg('\n[S]kip [i]mport [q]uit: ', 'magenta')
val = input()
if not val or val.lower() == 's':
continue
if val.lower() == 'i':
new_event = self._retry_with_backoff(
self.get_events().insert(
calendarId=self.cals[0]['id'], body=event
)
)
hlink = new_event.get('htmlLink')
self.printer.msg('New event added: %s\n' % hlink, 'green')
elif val.lower() == 'q':
sys.exit(0)
else:
self.printer.err_msg('Error: invalid input\n')
sys.exit(1)
# TODO: return the number of events added
return True
Loading

0 comments on commit 5db218b

Please sign in to comment.