python/ffId.py

from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.http import MediaFileUpload

def err(*m):
    print('error in', __file__ + ':', *m)
    x = 1 / 0

def credentials(crt):
    if type(crt) != str or len(crt) < 1:
        err(f"bad certification name: {crt} {type(crt)}")
    """ load the credentials form google OAuth2, 
            see Google APPI Console https://console.developers.google.com/?authuser=0&project=quickstart-1611556606696 
    """
    # If modifying these scopes, delete the file token.pickle.
    SCOPES =[ 'https://www.googleapis.com/auth/drive.metadata.readonly' # drive readonly
            , 'https://www.googleapis.com/auth/drive'                   # drive file update
            , 'https://www.googleapis.com/auth/documents.readonly'      # docs readonly
            #, 'https://www.googleapis.com/auth/documents'               # docs readWrite
            ]
    tokPiFi = f'token-{crt}.pickle'                                # the file to cache authorization
    credJson = '/wkData/pc/googleAPIcredentials.json'       # the credentials file generated by google API

    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists(tokPiFi):
        with open(tokPiFi, 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(credJson, SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(tokPiFi, 'wb') as token:
            pickle.dump(creds, token)
    return creds

def driveInfo():
    global srvDrive, driveUs
    # Call the Drive v3 API
    ab = srvDrive.about().get(fields='user(displayName, emailAddress, kind), storageQuota').execute()
    # print(ab)   
    driveUs = ab['user']['emailAddress']
    print(ab['user']['kind'], ab['user']['displayName'], driveUs
            , '; storage (MB) usage', round(int(ab['storageQuota']['usage'])/1048576), ', trash', round(int(ab['storageQuota']['usageInDriveTrash'])/1048576)
                       , ', limit', round(int(ab['storageQuota']['limit'])/1048576))
def fldWalkPrint(lv, fo, cnt):
    print('{:39}'.format('{:30}'.format('  ' * lv + fo['name']) + ' ' + cnt), fo['id'])

fsE = frozenset()
def fldWalk(fo, cb, lv, done = fsE): 
    fFlds = "name, id, kind, mimeType, createdTime, modifiedTime, trashed, parents, owners(emailAddress)"
    if type(fo) == str:
        fo = srvDrive.files().get(fileId=fo,fields=fFlds).execute()
        fo['path'] = ''
        # print('got fo:', fo)
    if fo['id'] in done:
        fldWalkPrint(0, fo, 'already1')
        return
    elif lv == -1:
        ff = [fo]
    else: 
        ff = []
        next = None
        while True: 
            res = srvDrive.files().list(
                pageSize=500, pageToken=next, fields=f"nextPageToken, files({fFlds})"
                    , q="'" + fo['id'] + "' in parents", orderBy="name"
                    ).execute()
            ff += res.get('files', [])
            next = res.get('nextPageToken')
            if next == None:
                break
        fc = 0
        for f in ff:
            fc += int(f['mimeType'] == 'application/vnd.google-apps.folder')
        fldWalkPrint(lv, fo, '#' + str(fc) + '/' + str(len(ff)) + '#')
        if type(done) == set:
            done.add(fo['id'])

    rec = []
    for f in ff:
        if f['id'] in done:
            fldWalkPrint(lv+1, f, 'already2')
            continue
        if f['mimeType'] == 'application/vnd.google-apps.folder':
            rec.append(f)
        if 'parents' not in f:
            if lv == -1:
                f['parent'] = ''
                f['path'] = ''
            else:
                err('no parent', f)
        elif len(f['parents']) != 1:
            err('parents in', f);
        else:
            f['parent'] = f['parents'][0]
            del f['parents']
            f['path'] = ('' if fo['path'] == '' else fo['path'] + '/') + f['name']
        if len(f['owners']) != 1 or 'emailAddress' not in f['owners'][0]:
            err('owners in', f);
        f['owner'] =  f['owners'][0]['emailAddress']
        del f['owners']
        # print(f)
        cb(f)
    for f in rec:
        fldWalk(f, cb, lv+1, done)


import csv
def fo2csv(fo, fn):
    with open(fn, 'w', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=['name', 'id',  'kind', 'mimeType', 'createdTime', 'modifiedTime', 'trashed', 'owner', 'path', 'parent'])
        writer.writeheader()
        fldWalk(fo, lambda d: writer.writerow(d), -1)

def upload(pa, fn, mime):   
    global srvDrive
    file_metadata = {'name': fn, 'parents': [pa]}
    media = MediaFileUpload(fn, mimetype=mime)
    file = srvDrive.files().create(body=file_metadata,
                                        media_body=media,
                                        fields='id').execute()
    print('created File ID:', file.get('id'))

def uploadVersion(fid, fn, mime):   
    global srvDrive
    #file_metadata = {'name': fn, 'parents': [pa]}
    media = MediaFileUpload(fn, mimetype=mime)
    file = srvDrive.files().update(fileId=fid,  # body=file_metadata,
                                        media_body=media,
                                        fields='id, name').execute()
    print(f"updated File{file.get('name')}, id: {file.get('id')}")

def chownFi(fi):
    global chownCnt, chownUs, driveUs
    if fi['owner'] in chownCnt:
        chownCnt[fi['owner']] += 1
    else:
        chownCnt[fi['owner']] = 1
    if fi['owner'] == chownUs or fi['owner'] != driveUs:
        return
    if not fi['mimeType'].startswith('application/vnd.google-apps.'):
        chownCnt['mimeNotGoogle'] += 1
        return
    chownCnt['chown'] += 1
    chown2(fi, chownUs)
    #print('chownFi', fi)

def chown2(fi, us):
    # print('chown2', fi, us)
    prms = srvDrive.permissions().list(fileId=fi['id'], fields='*').execute()['permissions']
    # print('prms', prms)
    pN = [p for p in prms if 'emailAddress' in p and p['emailAddress'] == us]
    # print('pN ***', len(pN), pN)
    try:
        if len(pN) >= 1:
            res = srvDrive.permissions().update(fileId=fi['id'], permissionId=pN[0]['id'], transferOwnership=True, body={'role': 'owner'}).execute()
        else:
            res = srvDrive.permissions().create(fileId=fi['id'], transferOwnership=True, body={'role': 'owner', 'type': 'user', 'emailAddress': us}).execute()
    except BaseException as e:
        print(fi, 'update/create except', e)
    # print(res)
    nn = srvDrive.files().get(fileId=fi['id'],fields='id, name, owners(emailAddress)').execute()
    # print('after', nn)
    if fi['name'] != nn['name'] or fi['id'] != nn['id'] :
        err('chown2 mismatch fi', fi, '<==>', nn)
    elif us == nn['owners'][0]['emailAddress']:
        print(fi['name'], fi['id'], 'owner from', fi['owner'], 'changedTo', nn['owners'][0]['emailAddress'])
    else:
        err('could not chown fi', fi, 'to nn', nn, 'permissions', prms)
    err('testend')

def chown(us):
    global srvDrive, chownUs, chownCnt
    chownUs = us
    chownCnt = {'chown': 0, 'mimeNotGoogle': 0}
    ff = []
    next = None
    while True: 
        res = srvDrive.files().list(pageSize=500, pageToken=next, fields="nextPageToken, files(id, name, parents)"
                , q="'" + us + "' in owners and mimeType = 'application/vnd.google-apps.folder'").execute()
        ff += res.get('files', [])
        next = res.get('nextPageToken')
        if next == None:
            break
    print('chown found', len(ff), 'folders owned by', us);
    done = set()
    for f in ff:
        # print('walking', f)
        fldWalk(f['id'], chownFi, -1, done=done)
    print('chown', chownUs, 'cnt', chownCnt)

def chown7(fold):
    global srvDrive, chownFlds
    print('get', dUser := srvDrive.about().get(fields='user/emailAddress').execute())
    print('fold', fo := srvDrive.files().get(fileId=fold,fields=chownFlds).execute())
    print('my user', dUser['user']['emailAddress'], 'folder', fo['name'], 'owners', fo['owners'][0]['emailAddress'])  
    chown3(dUser['user']['emailAddress'], fo['owners'][0]['emailAddress'], fo)

def chown33(ownO, ownN, fold):
    global srvDrive, chownFlds
    print('chown3', ownO, '==>', ownN, 'in', fold)
    fls = []
    next = None
    while True:
        res = srvDrive.files().list(
            pageSize=2, pageToken=next, fields="nextPageToken, files(" + chownFlds + ")"
                , q="'" + fold['id'] + "' in parents and ('" + ownO + "' in owners or mimeType = 'application/vnd.google-apps.folder')").execute()
        fls += res.get('files')
        next = res.get('nextPageToken')
        if next == None:
            break
    print('list got', [ f['name'] for f in fls])
    for f in fls:
        if f['owners'][0]['emailAddress'] == ownO :
            print('f', f)
            prms = srvDrive.permissions().list(fileId=f['id'], fields='*').execute()['permissions']
            print('prms', prms)
            pN = [p for p in prms if p['emailAddress'] == ownN]
            print('pN ***', len(pN), pN)
            res = srvDrive.permissions().update(fileId=f['id'], permissionId=pN[0]['id'], transferOwnership=True, body={'role': 'owner'}).execute()
            print(res)
            print('file now', srvDrive.files().get(fileId=f['id'],fields=chownFlds).execute())
    for f in fls:
        if f['mimeType'] == 'application/vnd.google-apps.folder':
            chown3(ownO, ownN, f) 
        
import argparse
def main():
    global srvDrive
    parser = argparse.ArgumentParser(description="ffId.py: google drive files folders Id's: list print to ffId-{cert}.csv")
    parser.add_argument('cert', help='prefix for certificate. hint a=admin@spWallisellen.ch f=fiwiko@wlkl.ch w=wa@wlkl.ch')
    parser.add_argument('fun', nargs='?', default='l', help='function to perform: l list to csv add new version, c=us chown us to cert user')
#    parser.print_help()
#    print('after print_help')
    print(f"{(args := parser.parse_args())=}")
    print(f"{args.cert=} {args.fun=}")
    srvDrive = build('drive', 'v3', credentials=credentials(args.cert))
    driveInfo()
    if args.fun == 'l':
        fn = f'ffId-{args.cert}.csv'
        pa = 'root'
        fo2csv(pa, fn)
        res = srvDrive.files().list(pageSize=5, fields=f"files(id, name, parents)"
                    , q="'" + pa + "' in parents and name = '" + fn + "' and not trashed").execute().get('files', [])
        print(f'list found {len(res)}: {res}');
        if len(res) < 1:
            print('no files found creating', fn)
            upload('root', fn, 'text/csv')
        elif len(res) > 1:
            err(f'multiple files {fn} exists, please remove all but one')
        else:
            print('adding new version to', fn, res[0]['id'])
            uploadVersion(res[0]['id'], fn, 'text/csv')
              
    elif args.fun[0:2] == 'c=':
        us = args.fun[2:]
        print('chown', us) 
        chown(us) 
    elif args.fun[0:2] == 't':
        id = '1jMnsw94SPsgKRg-5oDuapXqxaaLl3YG9'
        fFlds = "name, id, kind, mimeType, createdTime, modifiedTime, trashed, parents, owners(emailAddress)"
        fi = srvDrive.files().get(fileId=id,fields=fFlds).execute()
        print('fi', fi)
        co = srvDrive.files().copy(fileId=id,body={'name':'myCopy', 'parents':fi['parents']}, fields=fFlds).execute()
        print('co', co)
        res = srvDrive.files().delete(fileId=id).execute()
        print('delete', res)
        c2 = srvDrive.files().copy(fileId=co['id'],body={'name':fi['name'], 'id': fi['id'], 'parents':fi['parents']}, fields=fFlds).execute()
        print('c2', c2)
    else:
        err('bad fun:', args.fun)

if __name__ == '__main__':
    main()