Skip to content

Commit

Permalink
Added option to search a file in all Drive (global search)
Browse files Browse the repository at this point in the history
  • Loading branch information
yasirkula committed Aug 26, 2021
1 parent 313a765 commit 8926407
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 4 deletions.
52 changes: 52 additions & 0 deletions Plugins/DriveBrowser/Core/DriveAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -261,6 +262,57 @@ public static async Task<string> GetActivityAsync( this DriveFile file, Activity
return pageToken;
}

// The built-in "name contains 'World'" query unfortunately fails for "HelloWorld.txt" (i.e. it doesn't perform substring search)
// Thus, to get the results that a human being would expect to see, we need to go through ALL files on Drive and search their names manually
public static async Task<string> PerformGlobalSearchAsync( string searchTerm, SearchResultEntryDelegate onEntryReceived, CancellationToken cancellationToken, int minimumEntryCount = 50, string pageToken = null )
{
try
{
CompareInfo textComparer = new CultureInfo( "en-US" ).CompareInfo;
CompareOptions textCompareOptions = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace;

int receivedEntryCount = 0;
do
{
FilesResource.ListRequest request = ( await GetDriveAPIAsync() ).Files.List();
request.PageSize = 1000;
request.Fields = "nextPageToken, files(id, name)";
request.Q = "trashed = false and mimeType != 'application/vnd.google-apps.shortcut'";
request.PageToken = pageToken;

FileList result = await request.ExecuteAsync( cancellationToken );
if( result.Files != null )
{
foreach( DFile file in result.Files )
{
if( cancellationToken.IsCancellationRequested )
return null;

// Manually search the file's name
if( textComparer.IndexOf( file.Name, searchTerm, textCompareOptions ) < 0 )
continue;

// Calling GetFileByIDAsync to fetch all of the required fields of the file from the server and not just its name
onEntryReceived?.Invoke( await GetFileByIDAsync( file.Id ) );
receivedEntryCount++;
}
}

pageToken = result.NextPageToken;
} while( pageToken != null && receivedEntryCount < minimumEntryCount );
}
catch( System.OperationCanceledException )
{
return null;
}
catch( System.Exception e )
{
Debug.LogException( e );
}

return pageToken;
}

public static async Task<string> GetUsernameAsync( this Actor actor )
{
if( actor == null || actor.User == null || actor.User.KnownUser == null )
Expand Down
28 changes: 25 additions & 3 deletions Plugins/DriveBrowser/FileBrowser/FileBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private void OnDisable()

private void OnDestroy()
{
// Close all ActivityViewer windows with this window since they are tied to this window
// Close all ActivityViewer and GlobalSearchWindow windows with this window since they are tied to this window
ActivityViewer[] activityViewerWindows = Resources.FindObjectsOfTypeAll<ActivityViewer>();
if( activityViewerWindows != null )
{
Expand All @@ -102,6 +102,18 @@ private void OnDestroy()
activityViewerWindow.Close();
}
}

GlobalSearchWindow[] globalSearchWindows = Resources.FindObjectsOfTypeAll<GlobalSearchWindow>();
if( globalSearchWindows != null )
{
foreach( GlobalSearchWindow globalSearchWindow in globalSearchWindows )
{
if( globalSearchWindow != null && !globalSearchWindow.Equals( null ) )
globalSearchWindow.Close();
}
}

IsBusy = false;
}

void IHasCustomMenu.AddItemsToMenu( GenericMenu menu )
Expand Down Expand Up @@ -222,6 +234,18 @@ private void OnGUI()

filesTreeView.OnGUI( GUILayoutUtility.GetRect( 0, 100000, 0, 100000 ) );

EditorGUILayout.EndScrollView();

if( !string.IsNullOrWhiteSpace( filesTreeView.searchString ) )
{
EditorGUILayout.Space();

if( GUILayout.Button( $"Search '{filesTreeView.searchString}' In All Drive..." ) )
GlobalSearchWindow.Initialize( this, filesTreeView.searchString );

EditorGUILayout.Space();
}

// This happens only when the mouse click is not captured by the TreeView
// In this case, clear the TreeView's selection
if( Event.current.type == EventType.MouseDown && Event.current.button == 0 )
Expand All @@ -230,8 +254,6 @@ private void OnGUI()
EditorApplication.delayCall += Repaint;
}

EditorGUILayout.EndScrollView();

GUI.enabled = true;
}

Expand Down
8 changes: 8 additions & 0 deletions Plugins/DriveBrowser/GlobalSearch.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

185 changes: 185 additions & 0 deletions Plugins/DriveBrowser/GlobalSearch/GlobalSearchTreeView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Globalization;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;

namespace DriveBrowser
{
public class GlobalSearchTreeView : TreeView
{
private readonly List<DriveFile> searchResults;

private readonly CompareInfo textComparer;
private readonly CompareOptions textCompareOptions;

private readonly System.Action<DriveFile[]> onFilesRightClicked;

private readonly GUIContent sharedGUIContent = new GUIContent();

public GlobalSearchTreeView( TreeViewState treeViewState, MultiColumnHeader header, List<DriveFile> searchResults, System.Action<DriveFile[]> onFilesRightClicked ) : base( treeViewState, header )
{
this.searchResults = searchResults;
this.onFilesRightClicked = onFilesRightClicked;

textComparer = new CultureInfo( "en-US" ).CompareInfo;
textCompareOptions = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace;

showBorder = true;

header.visibleColumnsChanged += ( _header ) => _header.ResizeToFit();
header.sortingChanged += ( _header ) =>
{
Reload();

if( HasSelection() )
SetSelection( GetSelection(), TreeViewSelectionOptions.RevealAndFrame );
};

Reload();
}

protected override TreeViewItem BuildRoot()
{
return new TreeViewItem { id = 0, depth = -1 };
}

protected override IList<TreeViewItem> BuildRows( TreeViewItem root )
{
List<TreeViewItem> rows = ( GetRows() as List<TreeViewItem> ) ?? new List<TreeViewItem>( 64 );
rows.Clear();

bool isSearching = !string.IsNullOrEmpty( searchString );

for( int i = 0; i < searchResults.Count; i++ )
{
DriveFile file = searchResults[i];
if( !isSearching || textComparer.IndexOf( file.name, searchString, textCompareOptions ) >= 0 )
rows.Add( new TreeViewItem( i, 0, file.name ) );
}

if( rows.Count > 0 )
{
rows.Sort( ( r1, r2 ) =>
{
DriveFile f1 = searchResults[r1.id];
DriveFile f2 = searchResults[r2.id];

switch( multiColumnHeader.sortedColumnIndex )
{
case 0: // Sort by name
{
if( f1.isFolder && !f2.isFolder )
return -1;
else if( !f1.isFolder && f2.isFolder )
return 1;

return f1.name.CompareTo( f2.name );
}
case 1: // Sort by file size
{
if( f1.isFolder && !f2.isFolder )
return -1;
else if( !f1.isFolder && f2.isFolder )
return 1;

return f1.size.CompareTo( f2.size );
}
case 2: // Sort by last modified date
{
return f1.modifiedTimeTicks.CompareTo( f2.modifiedTimeTicks );
}
default: return 0;
}
} );

if( !multiColumnHeader.IsSortedAscending( multiColumnHeader.sortedColumnIndex ) )
rows.Reverse();

foreach( TreeViewItem row in rows )
root.AddChild( row );
}
else if( root.children == null ) // Otherwise: "InvalidOperationException: TreeView: 'rootItem.children == null'"
root.children = new List<TreeViewItem>( 0 );

return rows;
}

protected override void RowGUI( RowGUIArgs args )
{
DriveFile file = searchResults[args.item.id];

if( Event.current.type == EventType.MouseDown && args.rowRect.Contains( Event.current.mousePosition ) )
{
Rect previewSourceRect = args.rowRect;
previewSourceRect.width = EditorGUIUtility.currentViewWidth;

FilePreviewPopup.Show( previewSourceRect, file );
}

for( int i = 0; i < args.GetNumVisibleColumns(); ++i )
{
Rect cellRect = args.GetCellRect( i );
switch( args.GetColumn( i ) )
{
case 0: // Filename
{
cellRect.xMin += GetContentIndent( args.item );
cellRect.y -= 2f;
cellRect.height += 4f; // Incrementing height fixes cropped icon issue on Unity 2019.2 or earlier

sharedGUIContent.text = file.name;
sharedGUIContent.image = file.isFolder ? AssetDatabase.GetCachedIcon( "Assets" ) as Texture2D : UnityEditorInternal.InternalEditorUtility.GetIconForFile( file.name );
sharedGUIContent.tooltip = file.name;

GUI.Label( cellRect, sharedGUIContent );
break;
}
case 1: // File size
{
if( !file.isFolder )
GUI.Label( cellRect, EditorUtility.FormatBytes( file.size ) );

break;
}
case 2: // Last modified date
{
GUI.Label( cellRect, file.modifiedTime.ToString( "dd.MM.yy HH:mm" ) );
break;
}
}
}
}

protected override bool CanStartDrag( CanStartDragArgs args )
{
return true;
}

protected override void SetupDragAndDrop( SetupDragAndDropArgs args )
{
IList<int> sortedItemIDs = SortItemIDsInRowOrder( args.draggedItemIDs );
if( sortedItemIDs.Count == 0 )
return;

string[] fileIDs = new string[sortedItemIDs.Count];
for( int i = 0; i < sortedItemIDs.Count; i++ )
fileIDs[i] = searchResults[sortedItemIDs[i]].id;

HelperFunctions.InitiateDragDropDownload( fileIDs );
}

protected override void ContextClickedItem( int id )
{
IList<int> selection = GetSelection();
if( selection == null || selection.Count == 0 )
return;

DriveFile[] selectedFiles = new DriveFile[selection.Count];
for( int i = 0; i < selection.Count; i++ )
selectedFiles[i] = searchResults[selection[i]];

onFilesRightClicked?.Invoke( selectedFiles );
}
}
}
11 changes: 11 additions & 0 deletions Plugins/DriveBrowser/GlobalSearch/GlobalSearchTreeView.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8926407

Please sign in to comment.