Ejemplo n.º 1
0
 def on_cancel_or_quit(self, event):
     if __debug__: log('got Cancel')
     self._cancel = True
     self.return_values()
     # self.Destroy()
     self.return_values()
     self.EndModal(event.EventObject.Id)
Ejemplo n.º 2
0
 def on_escape(self, event):
     keycode = event.GetKeyCode()
     if keycode == wx.WXK_ESCAPE:
         if __debug__: log('got Escape')
         self.on_cancel_or_quit(event)
     else:
         event.Skip()
Ejemplo n.º 3
0
 def on_help(self, event):
     if __debug__: log('opening Help window')
     wx.BeginBusyCursor()
     help_file = path.join(datadir_path(), "help.html")
     if readable(help_file):
         webbrowser.open_new("file://" + help_file)
     wx.EndBusyCursor()
     return True
Ejemplo n.º 4
0
 def __init__(self, cfg_file):
     self._cfg = ConfigParser()
     try:
         with open(cfg_file) as f:
             if __debug__: log('reading "{}"', cfg_file)
             self._cfg.readfp(f)
     except IOError:
         if __debug__: log('"{}" not found', cfg_file)
         warnings.warn('file "{}" not found'.format(cfg_file))
Ejemplo n.º 5
0
def open_file(file):
    '''Open document with default application in Python.'''
    # Code originally from https://stackoverflow.com/a/435669/743730
    if __debug__: log('opening file {}', file)
    if sys.platform.startswith('darwin'):
        subprocess.call(('open', file))
    elif os.name == 'nt':
        os.startfile(file)
    elif os.name == 'posix':
        subprocess.call(('xdg-open', file))
Ejemplo n.º 6
0
 def on_about(self, event):
     if __debug__: log('opening About window')
     dlg = wx.adv.AboutDialogInfo()
     dlg.SetName(holdit.__name__)
     dlg.SetVersion(holdit.__version__)
     dlg.SetLicense(holdit.__license__)
     dlg.SetDescription('\n'.join(textwrap.wrap(holdit.__description__, 81)))
     dlg.SetWebSite(holdit.__url__)
     dlg.AddDeveloper(holdit.__author__)
     wx.adv.AboutBox(dlg)
     return True
Ejemplo n.º 7
0
def records_diff(known_records, new_records):
    '''Returns the records from 'new_records' missing from 'known_records'.
    The comparison is done on the basis of bar codes and request dates.'''
    if __debug__: log('Diffing known records with new records')
    diffs = []
    for candidate in new_records:
        found = [
            record for record in known_records
            if same_request(record, candidate)
        ]
        if not found:
            diffs.append(candidate)
    if __debug__: log('Found {} different records', len(diffs))
    return diffs
Ejemplo n.º 8
0
 def name_and_password(self):
     '''Returns a tuple of user, password, and a Boolean indicating
     whether the user cancelled the dialog.  (The last will always be False
     in this CLI version of the method.)
     '''
     tmp_user = self._user
     tmp_pswd = self._pswd
     if not all([tmp_user, tmp_pswd]) or self._reset or not self._use_keyring:
         if self._use_keyring and not self._reset:
             if __debug__: log('getting credentials from keyring')
             tmp_user, tmp_pswd, _, _ = credentials(_KEYRING, "Caltech access login",
                                                    tmp_user, tmp_pswd)
         else:
             if not self._use_keyring:
                 if __debug__: log('keyring disabled')
             if self._reset:
                 if __debug__: log('reset invoked')
             tmp_user = input('Caltech access login: '******'Password for "{}": '.format(tmp_user))
         if self._use_keyring:
             # Save the credentials if they're different.
             s_user, s_pswd, _, _ = keyring_credentials(_KEYRING)
             if s_user != tmp_user or s_pswd != tmp_pswd:
                 if __debug__: log('saving credentials to keyring')
                 save_keyring_credentials(_KEYRING, tmp_user, tmp_pswd)
     self._user = tmp_user
     self._pswd = tmp_pswd
     return self._user, self._pswd, False
Ejemplo n.º 9
0
    def on_ok(self, event):
        '''Stores the current values and destroys the dialog.'''

        if __debug__: log('got OK')
        if self.inputs_nonempty():
            self._cancel = False
            self._user = self.login.GetValue()
            self._password = self.password.GetValue()
            self.return_values()
            # self.Destroy()
            self.return_values()
            self.EndModal(event.EventObject.Id)
        else:
            if __debug__: log('has incomplete inputs')
            self.complain_incomplete_values(event)
Ejemplo n.º 10
0
def spreadsheet_credentials(user, message_handler):
    if __debug__: log('Getting token for Google API')
    store = TokenStorage('Holdit!', user)
    creds = store.get()
    if not creds or creds.invalid:
        if __debug__: log('Using secrets file for Google API')
        secrets_file = path.join(datadir_path(), _SECRETS_FILE)
        flow = google_flow(secrets_file, _OAUTH_SCOPE)
        # On Windows, run_flow calls argparse and ends up getting the args
        # we pass to our Hold It __main__.py.  I have no idea how that's
        # happening, but hacked around it this way:
        sys.argv = sys.argv[:1]
        creds = tools.run_flow(flow, store)

    if not creds:
        message_handler.error('Failed to get Google API token')
        raise InternalError('Failed to get Google API token')
    return creds
Ejemplo n.º 11
0
def holdit_path():
    '''Returns the path to where Hold It is installed.'''
    # The path returned by module.__path__ is to the directory containing
    # the __init__.py file.  What we want here is the path to the installation
    # of the Hold It binary.
    if sys.platform.startswith('win'):
        from winreg import OpenKey, CloseKey, QueryValueEx, HKEY_LOCAL_MACHINE, KEY_READ
        try:
            if __debug__: log('Reading Windows registry entry')
            key = OpenKey(HKEY_LOCAL_MACHINE, _HOLDIT_REG_PATH)
            value, regtype = QueryValueEx(key, 'Path')
            CloseKey(key)
            if __debug__: log('Path to windows installation: {}'.format(value))
            return value
        except WindowsError:
            # Kind of a problem. Punt and return a default value.
            return path.abspath('C:\Program Files\Hold It')
    else:
        return path.abspath(path.join(module_path(), '..'))
Ejemplo n.º 12
0
def delete_existing(file):
    '''Delete the given file.'''
    # Check if it's actually a directory.
    if path.isdir(file):
        if __debug__: log('doing rmtree on directory {}', file)
        try:
            shutil.rmtree(file)
        except:
            if __debug__: log('unable to rmtree {}; will try renaming', file)
            try:
                rename_existing(file)
            except:
                if __debug__: log('unable to rmtree or rename {}', file)
    else:
        if __debug__: log('doing os.remove on file {}', file)
        os.remove(file)
Ejemplo n.º 13
0
 def rename(f):
     backup = f + '.bak'
     # If we fail, we just give up instead of throwing an exception.
     try:
         os.rename(f, backup)
         if __debug__: log('renamed {} to {}', file, backup)
     except:
         try:
             delete_existing(backup)
             os.rename(f, backup)
         except:
             if __debug__: log('failed to delete {}', backup)
             if __debug__: log('failed to rename {} to {}', file, backup)
Ejemplo n.º 14
0
def spreadsheet_content(gs_id, user, message_handler):
    creds = spreadsheet_credentials(user, message_handler)
    if __debug__: log('Building Google sheets service object')
    service = build('sheets', 'v4', http = creds.authorize(Http()), cache_discovery = False)
    sheets_service = service.spreadsheets().values()
    try:
        # If you don't supply a sheet name in the range arg, you get 1st sheet.
        data = sheets_service.get(spreadsheetId = gs_id, range = 'A:Z').execute()
    except Exception as err:
        text = 'attempted connection to Google resulted in {}'.format(err)
        if __debug__: log(text)
        message_handler.error('Unable to read Google spreadsheet', text)
        raise InternalError('Failed to get Google API token')
    if __debug__: log('Google call successful')
    return data.get('values', [])
Ejemplo n.º 15
0
def update_google(gs_id, records, user, message_handler):
    data = []
    for record in records:
        record = GoogleHoldRecord(record)
        setattr(record, 'caltech_holdit_user', user)
        data.append(google_row_for_record(record))
    if not data:
        return
    creds = spreadsheet_credentials(user, message_handler)
    if __debug__: log('Building Google sheets service object')
    service = build('sheets', 'v4', http = creds.authorize(Http()), cache_discovery = False)
    sheets_service = service.spreadsheets().values()
    body = {'values': data}
    try:
        if __debug__: log('Calling Google API for updating data')
        result = sheets_service.append(spreadsheetId = gs_id,
                                       range = 'A:Z', body = body,
                                       valueInputOption = 'USER_ENTERED').execute()
    except Exception as err:
        text = 'attempted connection to Google resulted in {}'.format(err)
        if __debug__: log(text)
        message_handler.error('Unable to update Google spreadsheet', text)
        raise InternalError(text)
    if __debug__: log('Google call successful')
Ejemplo n.º 16
0
def records_from_tind(access_handler, notifier, tracer):
    if __debug__: log('Starting procedure for connecting to tind.io')
    json_data = tind_json(access_handler, notifier, tracer)
    if not json_data:
        return []
    records_data = json_data['recordsTotal']
    if records_data:
        num_records = records_data[0][0]
        if num_records < 1:
            return []
    if __debug__: log('Got {} records from tind.io', num_records)
    records = []
    for json_record in json_data['data']:
        tr = TindRecord(json_record)
        # Special hack: the way the holds are being done with Tind, we only
        # need to retrieve the new holds that are marked "on shelf" or "lost".
        if 'on shelf' in tr.item_loan_status or 'lost' in tr.item_loan_status:
            records.append(tr)
    if __debug__: log('Returning {} "on shelf" records', len(records))
    return records
Ejemplo n.º 17
0
 def name_and_password(self):
     '''Shows a login-and-password dialog, and returns a tuple of user,
     password, and a Boolean indicating whether the user cancelled the
     dialog.
     '''
     # This uses a threadsafe queue to implement a semaphore.  The
     # login_dialog will put a results tuple on the queue, but until then,
     # a get() on the queue will block.  Thus, this function will block
     # until the login dialog is closed by the user.
     results = Queue()
     if __debug__: log('sending message to login_dialog')
     wx.CallAfter(pub.sendMessage, "login_dialog", results = results,
                  user = self._user, password = self._pswd)
     if __debug__: log('blocking to get results')
     results_tuple = results.get()
     if __debug__: log('name_and_password results obtained')
     # Results will be a tuple of user, password, cancelled
     self._user = results_tuple[0]
     self._pswd = results_tuple[1]
     if self._use_keyring:
         save_keyring_credentials(_KEYRING, self._user, '')
     return results_tuple[0], results_tuple[1], results_tuple[2]
Ejemplo n.º 18
0
 def login_dialog(self, results, user, password):
     if __debug__: log('creating and showing login dialog')
     dialog = LoginDialog(self)
     dialog.initialize_values(results, user, password)
     dialog.ShowWindowModal()
Ejemplo n.º 19
0
 def start(self, message=None):
     if __debug__: log('sending progress_message for start')
     wx.CallAfter(pub.sendMessage, "progress_message", message=message)
Ejemplo n.º 20
0
 def update(self, message=None, count=None):
     if __debug__: log('sending progress_message for update')
     wx.CallAfter(pub.sendMessage, "progress_message", message=message)
Ejemplo n.º 21
0
    def run(self):
        # Set shortcut variables for better code readability below.
        template = self._template
        output = self._output
        view_sheet = self._view_sheet
        debug = self._debug
        controller = self._controller
        accesser = self._accesser
        notifier = self._notifier
        tracer = self._tracer

        # Preliminary sanity checks.  Do this here because we need the notifier
        # object to be initialized based on whether we're using GUI or CLI.
        tracer.start('Performing initial checks')
        if not network_available():
            notifier.fatal('No network connection.')

        # Let's do this thing.
        try:
            config = Config(path.join(module_path(), "holdit.ini"))

            # The default template is expected to be inside the Hold It module.
            # If the user supplies a template, we use it instead.
            tracer.update('Getting output template')
            template_file = config.get('holdit', 'template')
            template_file = path.abspath(
                path.join(module_path(), template_file))
            if template:
                temp = path.abspath(template)
                if readable(temp):
                    if __debug__:
                        log('Using user-supplied template "{}"'.format(temp))
                    template_file = temp
                else:
                    notifier.warn(
                        'File "{}" not readable -- using default.'.format(
                            template))
            else:
                # Check for "template.docx" in the Hold It installation dir.
                temp = path.abspath(path.join(holdit_path(), "template.docx"))
                if readable(temp):
                    if __debug__:
                        log('Using template found at "{}"'.format(temp))
                    template_file = temp

            # Sanity check against possible screwups in creating the Hold It! app.
            # Do them here so that we can fail early if we know we can't finish.
            if not readable(template_file):
                notifier.fatal('Template doc file "{}" not readable.'.format(
                    template_file))
                sys.exit()
            if not writable(desktop_path()):
                notifier.fatal('Output folder "{}" not writable.'.format(
                    desktop_path()))
                sys.exit()

            # Get the data.
            spreadsheet_id = config.get('holdit', 'spreadsheet_id')
            tracer.update('Connecting to TIND')
            tind_records = records_from_tind(accesser, notifier, tracer)
            tracer.update('Connecting to Google')
            google_records = records_from_google(spreadsheet_id, accesser.user,
                                                 notifier)
            missing_records = records_diff(google_records, tind_records)
            new_records = list(filter(records_filter('all'), missing_records))
            if __debug__:
                log('diff + filter => {} records'.format(len(new_records)))

            if len(new_records) > 0:
                # Update the spreadsheet with new records.
                tracer.update('Updating Google spreadsheet')
                update_google(spreadsheet_id, new_records, accesser.user,
                              notifier)
                # Write a printable report.
                tracer.update('Generating printable document')
                if not output:
                    output = path.join(desktop_path(), "holds_print_list.docx")
                if path.exists(output):
                    rename_existing(output)
                if file_in_use(output):
                    details = '{} appears to be open in another program'.format(
                        output)
                    notifier.warn('Cannot write Word doc -- is it still open?',
                                  details)
                else:
                    result = printable_doc(new_records, template_file)
                    result.save(output)
                    tracer.update('Opening Word document for printing')
                    open_file(output)
            else:
                tracer.update('No new hold requests were found in TIND.')
            # Open the spreadsheet too, if requested.
            if isinstance(notifier, MessageHandlerGUI):
                if notifier.yes_no('Open the tracking spreadsheet?'):
                    open_google(spreadsheet_id)
            elif view_sheet:
                open_google(spreadsheet_id)
        except (KeyboardInterrupt, UserCancelled) as err:
            tracer.stop('Quitting.')
            controller.stop()
        except ServiceFailure:
            tracer.stop('Stopping due to a problem connecting to services')
            controller.stop()
        except Exception as err:
            if debug:
                import pdb
                pdb.set_trace()
            tracer.stop('Stopping due to error')
            notifier.fatal(holdit.__title__ + ' encountered an error',
                           str(err) + '\n' + traceback.format_exc())
            controller.stop()
        else:
            tracer.stop('Done')
            controller.stop()
Ejemplo n.º 22
0
def main(user='******',
         pswd='P',
         output='O',
         template='F',
         no_color=False,
         no_gui=False,
         no_keyring=False,
         no_sheet=False,
         reset=False,
         debug=False,
         version=False):
    '''Generates a printable Word document containing recent hold requests and
also update the relevant Google spreadsheet used for tracking requests.

By default, Hold It! uses a GUI dialog to get the user's Caltech access login
name and password.  If the -G option is given (/G on Windows), it will not
use a GUI dialog, and will instead use the operating system's
keyring/keychain functionality to get a user name and password.  If the
information does not exist from a previous run of Hold It!, it will query the
user interactively for the user name and password, and (unless the -K or /K
argument is given) store them in the user's keyring/keychain so that it does
not have to ask again in the future.  It is also possible to supply the
information directly on the command line using the -u and -p options (or /u
and /p on Windows), but this is discouraged because it is insecure on
multiuser computer systems.

To reset the user name and password (e.g., if a mistake was made the last
time and the wrong credentials were stored in the keyring/keychain system),
use the -R (or /R on Windows) command-line argument to a command.  This
argument will make Hold It! query for the user name and password again even if
an entry already exists in the keyring or keychain.

By default, Hold It! looks for a .docx file named "template.docx" in the
directory where Hold It! is located, and uses that as the template for record
printing.  If given the -t option followed by a file name (/t on Windows), it
will look for the named file instead.  If it is not given an explicit
template file and it cannot find a file "template.docx", Hold It! will use a
built-in default template file.

By default, Hold It! will also open the Google spreadsheet used by the
Circulation staff to track hold requests.  This action is inhibited if given
the -S option (/S on Windows).  The Google spreadsheet is always updated in
any case.

Hold It! will write the output to a file named "holds_print_list.docx" in the
user's Desktop directory, unless the -o option (/o on Windows) is given with
an explicit file path to use instead.

If given the -V option (/V on Windows), this program will print version
information and exit without doing anything else.
'''

    # Our defaults are to do things like color the output, which means the
    # command line flags make more sense as negated values (e.g., "no-color").
    # However, dealing with negated variables in our code is confusing, so:
    use_color = not no_color
    use_keyring = not no_keyring
    use_gui = not no_gui
    view_sheet = not no_sheet

    # We use default values that provide more intuitive help text printed by
    # plac.  Rewrite the values to things we actually use.
    if user == 'U':
        user = None
    if pswd == 'P':
        pswd = None
    if template == 'F':
        template = None
    if output == 'O':
        output = None

    # Process the version argument first, because it causes an early exit.
    if version:
        print('{} version {}'.format(holdit.__title__, holdit.__version__))
        print('Author: {}'.format(holdit.__author__))
        print('URL: {}'.format(holdit.__url__))
        print('License: {}'.format(holdit.__license__))
        sys.exit()

    # Configure debug logging if it's turned on.
    if debug:
        set_debug(True)

    # Switch between different ways of getting information from/to the user.
    if use_gui:
        controller = HoldItControlGUI()
        accesser = AccessHandlerGUI(user, pswd)
        notifier = MessageHandlerGUI()
        tracer = ProgressIndicatorGUI()
    else:
        controller = HoldItControlCLI()
        accesser = AccessHandlerCLI(user, pswd, use_keyring, reset)
        notifier = MessageHandlerCLI(use_color)
        tracer = ProgressIndicatorCLI(use_color)

    # Start the worker thread.
    if __debug__: log('Starting main body thread')
    controller.start(
        MainBody(template, output, view_sheet, debug, controller, tracer,
                 accesser, notifier))
Ejemplo n.º 23
0
def open_url(url):
    '''Open the given 'url' in a web browser using the current platform's
    default approach.'''
    if __debug__: log('opening url {}', url)
    webbrowser.open(url)
Ejemplo n.º 24
0
def tind_json(access_handler, notifier, tracer):
    # Loop the login part in case the user enters the wrong password.
    logged_in = False
    while not logged_in:
        # Create a blank session and hack the user agent string.
        session = requests.Session()
        session.headers.update({'user-agent': _USER_AGENT_STRING})

        # Start with the full destination path + Shibboleth login component.
        try:
            if __debug__: log('Issuing network get to tind.io shibboleth URL')
            res = session.get(_SHIBBED_HOLD_URL, allow_redirects=True)
            if __debug__:
                log('Succeeded in network get to tind.io shibboleth URL')
        except Exception as err:
            details = 'exception connecting to tind.io: {}'.format(err)
            notifier.fatal('Failed to connect to tind.io -- try again later',
                           details)
            raise ServiceFailure(details)
        if res.status_code >= 300:
            details = 'tind.io shib request returned status {}'.format(
                res.status_code)
            notifier.fatal(
                'Unexpected network result -- please inform developers',
                details)
            raise ServiceFailure(details)

        # Now do the login step.
        user, pswd, cancelled = access_handler.name_and_password()
        if cancelled:
            if __debug__: log('user cancelled out of login dialog')
            raise UserCancelled
        if not user or not pswd:
            if __debug__: log('empty values returned from login dialog')
            return None
        sessionid = session.cookies.get('JSESSIONID')
        login_data = sso_login_data(user, pswd)
        # SAML step 1.
        next_url = '{};jsessionid={}?execution=e1s1'.format(
            _SSO_URL, sessionid)
        try:
            if __debug__: log('Issuing network post to idp.caltech.edu')
            res = session.post(next_url, data=login_data, allow_redirects=True)
            if __debug__: log('Succeeded in network post to idp.caltech.edu')
        except Exception as err:
            details = 'exception connecting to idp.caltech.edu: {}'.format(err)
            notifier.fatal('Failed to connect to tind.io', details)
            raise ServiceFailure(details)
        # SAML step 2.
        next_url = '{};jsessionid={}?execution=e1s2'.format(
            _SSO_URL, sessionid)
        try:
            if __debug__: log('Issuing network post to idp.caltech.edu')
            res = session.post(next_url, data=login_data, allow_redirects=True)
            if __debug__: log('Succeeded in network post to idp.caltech.edu')
        except Exception as err:
            details = 'exception connecting to idp.caltech.edu: {}'.format(err)
            notifier.fatal('Failed to connect to tind.io', details)
            raise ServiceFailure(details)

        logged_in = bool(str(res.content).find('Forgot your password') <= 0)
        if not logged_in and not notifier.yes_no(
                'Incorrect login. Try again?'):
            if __debug__: log('user cancelled access login')
            raise UserCancelled

    # Extract the SAML data and follow through with the action url.
    # This is needed to get the necessary cookies into the session object.
    tracer.update('Extracting data from TIND')
    tree = html.fromstring(res.content)
    if tree is None or tree.xpath('//form[@action]') is None:
        details = 'Caltech Shib access result does not have expected form'
        notifier.fatal('Unexpected network result -- please inform developers',
                       details)
        raise ServiceFailure(details)
    next_url = tree.xpath('//form[@action]')[0].action
    SAMLResponse = tree.xpath('//input[@name="SAMLResponse"]')[0].value
    RelayState = tree.xpath('//input[@name="RelayState"]')[0].value
    saml_payload = {'SAMLResponse': SAMLResponse, 'RelayState': RelayState}
    try:
        if __debug__: log('Issuing network post to {}', next_url)
        res = session.post(next_url, data=saml_payload, allow_redirects=True)
        if __debug__: log('Succeeded in issuing network post')
    except Exception as err:
        details = 'exception connecting to tind.io: {}'.format(err)
        notifier.fatal('Unexpected network problem -- try again later',
                       details)
        raise ServiceFailure(details)
    if res.status_code != 200:
        details = 'tind.io action post returned status {}'.format(
            res.status_code)
        notifier.fatal('Caltech.tind.io circulation page failed to respond',
                       details)
        raise ServiceFailure(details)

    # At this point, the session object has Invenio session cookies and
    # Shibboleth IDP session data.  We also have the TIND page we want,
    # but there's a catch: the TIND page contains a table that is filled
    # in using AJAX.  The table in the HTML we have at this point is
    # empty!  We need to fake the AJAX call to retrieve the data that is
    # used by TIND's javascript (in their bibcirculation.js) to fill in
    # the table.  I found this gnarly URL by studying the network
    # requests made by the page when it's loaded.

    ajax_url = 'https://caltech.tind.io/admin2/bibcirculation/requests?draw=1&order%5B0%5D%5Bdir%5D=asc&start=0&length=1000&search%5Bvalue%5D=&search%5Bregex%5D=false&sort=request_date&sort_dir=asc'
    ajax_headers = {
        "X-Requested-With": "XMLHttpRequest",
        "User-Agent": _USER_AGENT_STRING
    }
    try:
        if __debug__: log('Issuing ajax call to tind.io')
        res = session.get(ajax_url, headers=ajax_headers)
        if __debug__: log('Succeeded in issuing ajax call to tind.io')
    except Exception as err:
        details = 'exception connecting to tind.io bibcirculation page {}'.format(
            err)
        notifier.fatal(
            'Unable to get data from Caltech.tind.io circulation page',
            details)
        raise ServiceFailure(details)
    if res.status_code != 200:
        details = 'tind.io ajax get returned status {}'.format(res.status_code)
        notifier.fatal('Caltech.tind.io failed to return hold data', details)
        raise ServiceFailure(details)
    decoded = res.content.decode('utf-8')
    json_data = json.loads(decoded)
    if 'recordsTotal' not in json_data:
        details = 'Could not find a "recordsTotal" field in returned data'
        notifier.fatal(
            'Caltech.tind.io return results that we could not intepret',
            details)
        raise ServiceFailure(details)
    records_total = json_data['recordsTotal'][0][0]
    if records_total != len(json_data['data']):
        details = 'TIND "recordsTotal" value = {} but we only got {} records'.format(
            records_total, len(json_data['data']))
        notifier.fatal('Failed to get complete list of records from TIND',
                       details)
        raise InternalError(details)
    return json_data
Ejemplo n.º 25
0
 def on_cancel_or_quit(self, event):
     if __debug__: log('got Exit/Cancel')
     self._cancel = True
     self.Destroy()
     return True
Ejemplo n.º 26
0
def open_google(gs_id):
    if __debug__: log('Opening Google spreadsheet')
    open_url(_GS_BASE_URL + gs_id)
Ejemplo n.º 27
0
def user_data_path():
    data_dir = user_data_dir('Hold It', 'CaltechLibrary')
    if not path.exists(data_dir):
        if __debug__: log('creating user data directory {}', data_dir)
        os.makedirs(data_dir, exist_ok=True)
    return data_dir
Ejemplo n.º 28
0
def records_from_google(gs_id, user, message_handler):
    if __debug__: log('Getting entries from Google spreadsheet')
    spreadsheet_rows = spreadsheet_content(gs_id, user, message_handler)
    if spreadsheet_rows == []:
        return []
    # First row is the title row.
    results = []
    if __debug__: log('Building records from {} rows', len(spreadsheet_rows) - 1)
    for index, row in enumerate(spreadsheet_rows[1:], start = 1):
        if not row or len(row) < 8:     # Empty or junk row.
            continue

        record = GoogleHoldRecord()

        cell = row[0]
        end = cell.find('\n')
        if end:
            record.requester_name = cell[:end].strip()
            record.requester_type = cell[end + 1:].strip()
        else:
            record.requester_name = cell.strip()

        cell = row[1]
        end = cell.find('\n')
        if end:
            record.item_title = cell[:end].strip()
            record.item_loan_status = cell[end + 1:].strip()
        else:
            record.item_title = cell.strip()

        cell = row[2]
        end = cell.find('\n')
        if end:
            record.item_barcode = cell[:end]
            record.item_call_number = cell[end + 1:].strip()
        else:
            record.item_title = cell.strip()

        cell = row[3]
        record.date_requested = cell.strip()

        cell = row[4]
        record.overdue_notices_count = cell.strip()

        cell = row[5]
        record.holds_count = cell.strip()

        cell = row[6]
        record.item_location_code = cell.strip()

        if len(row) > 7:
            cell = row[7]
            record.caltech_holdit_user = cell.strip()

        if len(row) > 8:
            cell = row[8]
            record.caltech_status = cell.strip()

        if len(row) > 9:
            cell = row[9]
            record.caltech_staff_initials = cell.strip()

        results.append(record)
    return results
Ejemplo n.º 29
0
 def return_values(self):
     if __debug__: log('return_values called')
     self._wait_queue.put((self._user, self._password, self._cancel))