{***************************************************************************}
{ TMS GDrive Cloud access                                                   }
{ for Delphi & C++Builder                                                   }
{                                                                           }
{ written by TMS Software                                                   }
{            copyright © 2012                                               }
{            Email : info@tmssoftware.com                                   }
{            Web : http://www.tmssoftware.com                               }
{                                                                           }
{ The source code is given as is. The author is not responsible             }
{ for any possible damage done due to the use of this code.                 }
{ The component can be freely used in any application. The complete         }
{ source code remains property of the author and may not be distributed,    }
{ published, given or sold in any form as such. No parts of the source      }
{ code can be included in any other component or application without        }
{ written authorization of the author.                                      }
{***************************************************************************}

unit CloudGDrive;

interface

uses
  Classes, CloudBase, SHDocVw, SysUtils, ComCtrls,
  IdCustomTCPServer, IdCustomHTTPServer, IdContext, IdHTTPServer;

const
  MAJ_VER = 1; // Major version nr.
  MIN_VER = 0; // Minor version nr.
  REL_VER = 1; // Release nr.
  BLD_VER = 0; // Build nr.

  // v1.0.0.0 : First release
  // v1.0.0.1 : Fixed : Issue with download file
  // v1.0.1.0 : New : Uses Locale property for consent page language customization

type
  TGDriveItems = class;

  TGDriveItem = class(TCloudItem)
  private
    FID: string;
    FDescription: string;
    FLink: string;
    FLastUser: string;
    FCommentsEnabled: boolean;
    FAccessValue: string;
    FMimeType: string;
    FOwners: string;
    FParentRoot: boolean;
    FParentID: string;
    FInTree: boolean;
    FDownloadURL: string;
  protected
    property AccessValue: string read FAccessValue write FAccessValue;
    property InTree: boolean read FInTree write FInTree;
  public
    function GetNext: TGDriveItem;
    procedure Assign(Source: TPersistent); override;
    property ParentID: string read FParentID write FParentID;
    property ParentRoot: boolean read FParentRoot write FParentRoot;
    property DownloadURL: string read FDownloadURL write FDownloadURL;
  published
    property ID: string read FID write FID;
    property MimeType: string read FMimeType write FMimeType;
    property Description: string read FDescription write FDescription;
    property Link: string read FLink write FLink;
    property LastUser: string read FLastUser write FLastUser;
    property Owners: string read FOwners write FOwners;
    property CommentsEnabled: boolean read FCommentsEnabled write FCommentsEnabled;
  end;

  TGDriveItems = class(TCloudItems)
  private
    FFolder: TGDriveItem;
    function GetItems(index: integer): TGDriveItem;
    procedure SetItems(index: integer; const Value: TGDriveItem);
  protected
    function GetItemClass: TCollectionItemClass; override;
  public
    constructor Create(AFolder: TGDriveItem);
    function Add: TGDriveItem;
    function Insert(Index: integer): TGDriveItem;
    property Items[index: integer]: TGDriveItem read GetItems write SetItems;
    property Folder: TGDriveItem read FFolder;
  published
  end;

  TAdvGDrive = class(TCloudStorage)
  private
    FCallBackURL: string;
    FHttpServer: TIdHTTPServer;
    function GetGDrive: TGDriveItems;
  protected
    function GetVersionNr: integer; override;

    function CreateCloudItems: TCloudItems; override;
    procedure RestructureInfo(TempDrive: TGDriveItems);
    procedure DoCommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    function GetLocaleParam: string;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function TestTokens: boolean; override;
    function RefreshAccess: boolean; override;
    procedure DoAuth; override;
    procedure GetDriveInfo; override;
    function CreateFolder(ci: TCloudItem; const FolderName: string): TCloudItem; override;
    function Download(ci: TCloudItem; const TargetFile: string): boolean; override;
    function Delete(ci: TCloudItem): boolean; override;
    function Upload(Folder: TGDriveItem; FileName: string; Description: string = ''): TGDriveItem;
    property CallBackURL: string read FCallBackURL write FCallBackURL;
    property GDrive: TGDriveItems read GetGDrive;
  published
    property Scopes;
    property Locale;
  end;

implementation

uses
  DBXJSON, Windows;

const
  ctFolderMimeType = 'application/vnd.google-apps.folder';

{ TAdvGDrive }

constructor TAdvGDrive.Create(AOwner: TComponent);
begin
  inherited;

  APIBase := 'https://www.googleapis.com/drive/v2/files/';

  FHttpServer := nil;

  FCallBackURL := 'http://127.0.0.1:8888/';

  Scopes.Clear;
  Scopes.Add('https://www.googleapis.com/auth/drive');
  Scopes.Add('https://www.googleapis.com/auth/drive.file');

  (*
   '?scope=' + URLEncode('https://www.googleapis.com/auth/userinfo.email')
    + '+' + URLEncode('https://www.googleapis.com/auth/userinfo.profile')
    + '+' + URLEncode('https://www.googleapis.com/auth/plus.me')
//    + '+' + URLEncode('https://www.googleapis.com/auth/calendar')
    + '+' + URLEncode('')
    + '+' + URLEncode('')
  *)
end;

function TAdvGDrive.CreateCloudItems: TCloudItems;
begin
  Result := TGDriveItems.Create(nil);
end;

function TAdvGDrive.CreateFolder(ci: TCloudItem;
  const FolderName: string): TCloudItem;
var
  url,postdata: string;
  resdat: ansistring;
  ownr,parent: string;
  headers: TCoreCloudHeaders;
  jp,fo: TJSONArray;
  jv: TJSONValue;
  jo: TJSONObject;
  par: TJSONObject;
  gdi: TGDriveItem;
  j: integer;
  Folder: TGDriveItem;
begin
  Result := nil;

  if Assigned(ci) then
  begin
    Folder := ci as TGDriveItem;
    if not (Folder.ItemType = ciFolder) then
      Exit;
  end
  else
    Folder := nil;

  url := APIBase + '?access_token=' + Token_Access;

  parent := '';

  if Assigned(Folder) then
  begin
    parent := ',"parents": ['
    +  '{'
    + '"kind": "drive#parentReference",'
    + '"id": "' + (ci as TGDriveItem).ID + '",'
    + '"isRoot": false'
    + '}'
    + ']';
  end;

  AddHeader(headers, 'Content-Type', 'application/json'#13#10);
  postdata := '{'
    + '"title": "' + ExtractFileName(Foldername)+'",'
    + '"mimeType": "' + ctFolderMimeType + '"'
    + parent
    + '}';

  HttpsPost(extractserver(url),removeserver(url),headers,ansistring(postdata),resdat);

  // create the object from here
  jv := TJSONObject.ParseJSONValue(string(CleanUpJSON(resdat)));

  if Assigned(jv) then
  begin
    try
      jo := jv as TJSONObject;

      if GetJSONProp(jo,'title') <> '' then
      begin
        if Assigned(Folder) then
          Result := Folder.Folder.Add as TGDriveItem
        else
          Result := GDrive.Add;

        gdi := Result as TGDriveItem;

        gdi.ItemType := ciFolder;

        gdi.FileName := GetJSONProp(jo,'title');
        gdi.ID := GetJSONProp(jo,'id');
        gdi.MimeType := GetJSONProp(jo,'mimeType');
        gdi.DownloadURL := GetJSONProp(jo,'downloadUrl');
        gdi.CreationDate := IsoToDateTime(GetJSONProp(jo,'createdDate'));
        gdi.ModifiedDate := IsoToDateTime(GetJSONProp(jo,'modifiedDate'));
        gdi.Size := 0;

        jp := GetJSONValue(jo,'parents') as TJSONArray;

        if Assigned(jp) and (jp.Size > 0) then
        begin
          par := jp.Get(0) as TJSONObject;
          gdi.ParentID := GetJSONProp(par,'id');
          gdi.ParentRoot := GetJSONValue(par,'isRoot') is TJSONTrue;
        end;

        fo := GetJSONValue(jo,'ownerNames') as TJSONArray;
        if Assigned(fo) then
        begin
          for j := 0 to fo.Size - 1 do
          begin
            ownr := fo.Get(j).Value;
            if j > 0 then
              gdi.Owners := gdi.Owners + ',' + ownr
            else
              gdi.Owners := ownr;
          end;
        end;
      end;
    finally
      jv.Free;
    end;
  end;
end;

function TAdvGDrive.Delete(ci: TCloudItem): boolean;
var
  url: string;
  resdat: ansistring;
  Item: TGDriveItem;
  jv: TJSONValue;
  jo: TJSONObject;
begin
  Result := false;

  inherited;

  if not (ci is TGDriveItem) then
    Exit;

  Item := (ci as TGDriveItem);

  url := APIBase + Item.Id + '/trash?access_token=' + Token_Access;
  //url := item.DownloadURL+'&access_token=' + FToken_Access;;
  HttpsPost(ExtractServer(url),RemoveServer(url),nil,'',resdat);

  jv := TJSONObject.ParseJSONValue(string(CleanUpJSON(resdat)));

  if Assigned(jv) then
  begin
    try
      jo := jv as TJSONObject;
      jv := GetJSONValue(jo,'error');
      if not Assigned(jv) then
        Result := true;
    finally
      jv.Free;
    end;
  end;
end;

destructor TAdvGDrive.Destroy;
begin
  inherited;
end;

procedure TAdvGDrive.DoAuth;
var
  url: string;
begin
  inherited;
     (*
  url :=
   '?scope=' + URLEncode('https://www.googleapis.com/auth/userinfo.email')
    + '+' + URLEncode('https://www.googleapis.com/auth/userinfo.profile')
    + '+' + URLEncode('https://www.googleapis.com/auth/plus.me')
//    + '+' + URLEncode('https://www.googleapis.com/auth/calendar')
    + '+' + URLEncode('https://www.googleapis.com/auth/drive')
    + '+' + URLEncode('https://www.googleapis.com/auth/drive.file')
    *)

  url :=
      '?scope=' + ScopeParamText('+',true)
    + '&state=profile'
    + '&redirect_uri='+URLEncode(CallBackURL)
    + '&response_type=code'
    + '&client_id=' + App.Key
    + '&approval_prompt=force'
    + GetLocaleParam;

  url := 'https://accounts.google.com/o/oauth2/auth' + url;

  if not Assigned(FHttpServer) then
    FHttpServer := TIdHTTPServer.Create(Owner);

  FHttpServer.Active := false;
  FHttpServer.DefaultPort := 8888;
  FHttpServer.OnCommandGet := DoCommandGet;
  FHttpServer.Active := true;

  if Assigned(AuthBrowser) then
    AuthBrowser.Navigate(url)
  else
    CreateOAuthForm(url,nil,nil);
end;

procedure TAdvGDrive.DoCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  fserver,faction: string;
  headers: TCoreCloudHeaders;
  fdata,lf:string;
  resdat: ansistring;
  o: TJSONObject;
  jv: TJSONValue;
//  inif: tinifile;

begin
  // detect Google callback for profile state
  if (arequestinfo.params.Count = 2) and (arequestinfo.params.IndexOfName('state') <> -1) then
  begin
    Token_Auth := arequestinfo.params.ValueFromIndex[arequestinfo.params.IndexOfName('code')];

    // as soon as the auth. code is received -> request access token
    FServer := 'accounts.google.com';
    FAction := '/o/oauth2/token';
    AddHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');

    lf := '&';

    FData :=
      'code=' + UrlEncode(Token_Auth) + lf
    + 'client_id=' + UrlEncode(App.Key) + lf
    + 'client_secret=' + UrlEncode(App.Secret) + lf
    + 'redirect_uri=' + UrlENcode(CallBackURL)+lf
    + 'grant_type=authorization_code';

    HttpsPost(FServer, FAction, headers, AnsiString(FData), resdat);

    jv := TJSONObject.ParseJSONValue(string(resdat));

    if Assigned(jv) then
    begin
      try
        o := jv as TJSONObject;
        Token_Access := GetJSONProp(o,'access_token');
        Token_Refresh := GetJSONProp(o,'refresh_token');
      finally
        jv.Free;
      end;
      DoReceivedAccessToken;
      CloseOAuthForm;
    end;
  end;
end;

function TAdvGDrive.Download(ci: TCloudItem; const TargetFile: string): boolean;
var
  url: string;
  resdat: ansistring;
  Item: TGDriveItem;
  jv: TJSONValue;
  jo: TJSONObject;
begin
  Result := false;

  inherited;

  if not (ci is TGDriveItem) then
    Exit;

  Item := ci as TGDriveItem;
  url := item.DownloadURL + '&access_token=' + Token_Access;;

  resdat := HttpsGet(url,TargetFile, item.Size);

  jv := TJSONObject.ParseJSONValue(string(CleanUpJSON(resdat)));

  if Assigned(jv) then
  begin
    try
      jo := jv as TJSONObject;
      jv := GetJSONValue(jo,'error');
      Result := not Assigned(jv);
    finally
      jv.Free;
    end;
  end;
end;

procedure TAdvGDrive.GetDriveInfo;
var
  o,fo,par,lbl: TJSONObject;
  ja,jo,jp: TJSONArray;
  i,j: integer;
  url,ownr,fs: string;
  gdi: TGDriveItem;
  TempDrive: TGDriveItems;
begin
  GDrive.Clear;
  TempDrive := TGDriveItems.Create(nil);

  url := APIBase + '?maxResults=10000&access_token=' + Token_Access;

  o := GetJSONObject(url);

  if Assigned(o) then
  begin
    try
      ja := GetJSONValue(o, 'items') as TJSONArray;

      // read the entire array
      for i := 0 to ja.Size - 1 do
      begin
        fo := ja.Get(i) as TJSONObject;

        lbl := GetJSONValue(fo,'labels') as TJSONObject;

        if (GetJSONValue(lbl,'hidden') is TJSONFalse) and (GetJSONValue(lbl,'trashed') is TJSONFalse) then
        begin
          gdi := TempDrive.Add;
          gdi.FileName := GetJSONProp(fo,'title');
          gdi.ID := GetJSONProp(fo,'id');
          gdi.MimeType := GetJSONProp(fo,'mimeType');
          gdi.DownloadURL := GetJSONProp(fo,'downloadUrl');
          gdi.CreationDate := IsoToDateTime(GetJSONProp(fo,'createdDate'));
          gdi.ModifiedDate := IsoToDateTime(GetJSONProp(fo,'modifiedDate'));

          if gdi.MimeType = ctFolderMimeType then
          begin
            gdi.ItemType := ciFolder
          end
          else
            gdi.ItemType := ciFile;

          fs := GetJSONProp(fo,'fileSize');
          gdi.Size := StrToInt64T(fs);

          jp := GetJSONValue(fo,'parents') as TJSONArray;

          if Assigned(jp) and (jp.Size > 0) then
          begin
            par := jp.Get(0) as TJSONObject;
            gdi.ParentID := GetJSONProp(par,'id');
            gdi.ParentRoot := GetJSONValue(par,'isRoot') is TJSONTrue;
          end;

          jo := GetJSONValue(fo,'ownerNames') as TJSONArray;

          if Assigned(jo) then
          begin
            for j := 0 to jo.Size - 1 do
            begin
              ownr := jo.Get(j).Value;
              if j > 0 then
                gdi.Owners := gdi.Owners + ',' + ownr
              else
                gdi.Owners := ownr;
            end;
          end;

          if gdi.ParentID = '' then
            gdi.Free;
        end;
      end;
    finally
      o.Free;
    end;
    // restructure here
    RestructureInfo(TempDrive);
    TempDrive.Free;
  end;
end;

function TAdvGDrive.RefreshAccess: boolean;
var
  resdat: ansistring;
  FServer,FAction: string;
  headers: TCoreCloudHeaders;
  FData: string;
  lf,at: string;
  jv: TJSONValue;
  jo: TJSONObject;
begin
  Result := false;

  if Token_Refresh = '' then
    Exit;

  FServer := 'accounts.google.com';
  FAction := '/o/oauth2/token';
  AddHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');

  lf := '&';

  FData :=
      'client_id=' + UrlEncode(App.Key) + lf
    + 'client_secret=' + UrlEncode(App.Secret) + lf
    + 'refresh_token=' + URLEncode(Token_Refresh) + lf
    + 'grant_type=refresh_token';

  HttpsPost(FServer, FAction, headers, AnsiString(FData), resdat);

  if (resdat <> '') then
  begin
    jv := TJSONObject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;

        at := GetJSONProp(jo,'access_token');

        Result := at <> '';
        if Result then
          Token_Access := at;
      finally
        jv.Free;
      end;
    end;
  end;
end;

procedure TAdvGDrive.RestructureInfo(TempDrive: TGDriveItems);
var
  i: integer;
  fi: TGDriveItem;
  gdi: TGDriveItem;
  cnt: integer;
begin
  // first add all items of the root
  for i := 0 to TempDrive.Count - 1 do
  begin
    if (TempDrive.Items[i] as TGDriveItem).ParentRoot then
    begin
      gdi := GDrive.Add;
      gdi.Assign(TempDrive.Items[i]);
      gdi.InTree := true;
      TempDrive.Items[i].InTree := true;
    end;
  end;

  repeat
    cnt := 0;

    for i := 0 to TempDrive.Count - 1 do
    begin
      if not TempDrive.Items[i].InTree then
        inc(cnt);

      if not TempDrive.Items[i].ParentRoot and not (TempDrive.Items[i].ParentID = '') and not TempDrive.Items[i].InTree then
      begin
        // the first item
        fi := GDrive.Items[0];

        while (fi <> nil) do
        begin
          if fi.ID = TempDrive.Items[i].ParentID then
          begin
            TempDrive.Items[i].InTree := true;

            if not Assigned(fi.Folder) then
              fi.Folder := TGDriveItems.Create(fi);

            gdi := fi.Folder.Add as TGDriveItem;
            gdi.Assign(TempDrive.Items[i]);
            gdi.InTree := true;
            dec(cnt);
            break;
          end;
          fi := fi.GetNext;
        end;
      end;
    end;

  until cnt = 0;
end;

function TAdvGDrive.GetGDrive: TGDriveItems;
begin
  Result := Drive as TGDriveItems;
end;

function TAdvGDrive.GetLocaleParam: string;
begin
  case Locale of
    lcDefault: Result := '';
    lcEnglish: Result := '&hl=en';
    lcDutch: Result := '&hl=nl';
    lcGerman: Result := '&hl=de';
    lcFrench: Result := '&hl=fr';
    lcSpanish: Result := '&hl=es';
    lcItalian: Result := '&hl=it';
    lcPortuguese: Result := '&hl=pt';
    lcGreek: Result := '&hl=el';
    lcDanish: Result := '&hl=da';
    lcRussian: Result := '&hl=ru';
    lcRomanian: Result := '&hl=ro';
    lcSwedish: Result := '&hl=sv';
    lcFinnish: Result := '&hl=fi';
    lcTurkish: Result := '&hl=tr';
    lcJapanese: Result := '&hl=ja';

  end;
end;

function TAdvGDrive.GetVersionNr: integer;
begin
  Result := MakeLong(MakeWord(BLD_VER,REL_VER),MakeWord(MIN_VER,MAJ_VER));
end;

function TAdvGDrive.TestTokens: boolean;
var
  url: string;
  o: TJSONObject;
begin
  Result := false;

  url := 'https://www.googleapis.com/drive/v2/about/?access_token=' + Token_Access;

  o := GetJSONObject(url);
  if Assigned(o) then
  begin
    try
      Result := GetJSONProp(o,'name') <> '';
    finally
      o.Free;
    end;
  end;
end;

function TAdvGDrive.Upload(Folder: TGDriveItem; FileName: string; Description: string = ''): TGDriveItem;
var
  url,fid,postdata: string;
  resdat,customdata: ansistring;
  s,fs,ownr,parent: string;
  headers: TCoreCloudHeaders;
  jp: TJSONPair;
  jv: TJSONValue;
  jo,fo: TJSONObject;
  jap,jao: TJSONArray;
  par: TJSONObject;
  StringBytes: TBytes;
  gdi: TGDriveItem;
  j: integer;
begin
  Result := nil;

  url := APIBase + '?access_token=' + Token_Access;

  parent := '';

  if Assigned(Folder) then
  begin
    parent := ',"parents": ['
    +  '{'
    + '"kind": "drive#parentReference",'
    + '"id": "' + Folder.ID + '",'
    + '"isRoot": false'
    + '}'
    + ']';
  end;


  AddHeader(headers, 'Content-Type', 'application/json'#13#10);
  postdata := '{'
    + '"title": "' + ExtractFileName(FileName)+'",'
    + '"description": "' + Description +'",'
    + '"mimeType": "' + GetMimeType(FileName)+ '"'
    + parent
    + '}';

  HttpsPost(extractserver(url),removeserver(url),headers,ansistring(postdata),resdat);

  // this created the file, now upload it
  s := string(resdat);

  jo := TJSONObject.Create;
  try
    StringBytes := TEncoding.ASCII.GetBytes(s);
    jo.Parse(StringBytes,0);
    jp := jo.Get('id');
    fid := jp.JsonValue.Value;
  finally
    jo.Free;
  end;

  customdata :=
         'Content-Disposition: form-data; name="userfile"; filename="' + ansistring(ExtractFileName(FileName)) + '"'#13#10
         +'Content-Type: application/octet-stream'#13#10
         + #13#10;

  url := 'https://www.googleapis.com/upload/drive/v2/files/'+fid+'/?access_token=' + Token_Access;
  resdat := HttpsPut(url,ExtractFilePath(FileName),ExtractFileName(FileName),nil,customdata,THTTPCommand.hcPutMultiPart);

  if resdat <> '' then
  begin
    jv := TJSONObject.ParseJSONValue(string(resdat));

    if Assigned(jv) then
    begin
      try
        fo := jv as TJSONObject;

        if Assigned(Folder) then
        begin
          if not Assigned(Folder.Folder) then
            Folder.Folder := TGDriveItems.Create(Folder);

          gdi := Folder.Folder.Add as TGDriveItem;
        end
        else
          gdi := GDrive.Add;

        gdi.FileName := GetJSONProp(fo,'title');
        gdi.ID := GetJSONProp(fo,'id');
        gdi.MimeType := GetJSONProp(fo,'mimeType');
        gdi.DownloadURL := GetJSONProp(fo,'downloadUrl');
        gdi.CreationDate := IsoToDateTime(GetJSONProp(fo,'createdDate'));
        gdi.ModifiedDate := IsoToDateTime(GetJSONProp(fo,'modifiedDate'));

        fs := GetJSONProp(fo,'fileSize');
        gdi.Size := StrToInt64T(fs);

        jap := GetJSONValue(fo,'parents') as TJSONArray;

        if Assigned(jap) and (jap.Size > 0) then
        begin
          par := jap.Get(0) as TJSONObject;
          gdi.ParentID := GetJSONProp(par,'id');
          gdi.ParentRoot := GetJSONValue(par,'isRoot') is TJSONTrue;
        end;

        jao := GetJSONValue(fo,'ownerNames') as TJSONArray;

        if Assigned(jao) then
        begin
          for j := 0 to jao.Size - 1 do
          begin
            ownr := jao.Get(j).Value;
            if j > 0 then
              gdi.Owners := gdi.Owners + ',' + ownr
            else
              gdi.Owners := ownr;
          end;
        end;

        Result := gdi;
      finally
        jv.Free;
      end;
    end;
  end;
end;

{ TGDriveItems }

function TGDriveItems.Add: TGDriveItem;
begin
  Result := TGDriveItem(inherited Add);
end;

constructor TGDriveItems.Create(AFolder: TGDriveItem);
begin
  inherited Create;
  FFolder := AFolder;
end;

function TGDriveItems.GetItemClass: TCollectionItemClass;
begin
  Result := TGDriveItem;
end;

function TGDriveItems.GetItems(index: integer): TGDriveItem;
begin
  Result := TGDriveItem(inherited Items[index]);
end;

function TGDriveItems.Insert(Index: integer): TGDriveItem;
begin
  Result := TGDriveItem(inherited Insert(Index));
end;

procedure TGDriveItems.SetItems(index: integer; const Value: TGDriveItem);
begin
  inherited Items[index] := Value;
end;

{ TGDriveItem }

procedure TGDriveItem.Assign(Source: TPersistent);
begin
  inherited;
  if (Source is TGDriveItem) then
  begin
    FID := (Source as TGDriveItem).ID;
    FMimeType := (Source as TGDriveItem).MimeType;
    FDescription := (Source as TGDriveItem).Description;
    FLink := (Source as TGDriveItem).Link;
    FLastUser := (Source as TGDriveItem).LastUser;
    FOwners := (Source as TGDriveItem).Owners;
    FCommentsEnabled := (Source as TGDriveItem).CommentsEnabled;
    FDownloadURL := (Source as TGDriveItem).DownloadURL;
  end;
end;

function TGDriveItem.GetNext: TGDriveItem;
var
  gdi: TGDriveItem;
begin
  Result := nil;

  if Assigned(Folder) and (Folder.Count > 0) then
  begin
    Result := TGDriveItem(Folder.Items[0]);
  end
  else
  if Index + 1 < Collection.Count then
  begin
    Result := TGDriveItem(Collection.Items[Index + 1]);
  end
  else
  begin
    // go back up one level
    if Assigned(TGDriveItems(Collection).Folder) then
    begin
      gdi := TGDriveItems(Collection).Folder;
      if gdi.Index + 1 < gdi.Collection.Count then
      begin
        Result := TGDriveItem(gdi.Collection.Items[gdi.Index + 1]);
      end
    end;
  end;
end;


end.
