Créer un helper pour gérer Skydrive - PARTIE II.

Publié le par Jérémy JANISZEWSKI

Cet article fait suite à celui-là.

Alors maintenant, nous allons créer notre classe de gestion de SkyDrive.

En premier lieu, cette classe disposera de deux évènements :

  • Un évènement qui va permettre d'informer l'utilisateur de l'avancement de l'upload d'un fichier
  • Un évènement qui va permettre d'informer l'utilisateur de l'avancement de téléchargement d'un fichier.

L'évènement qui permet le téléchargement d'un fichier permet enregistre un event args de type DownloadWebFileProgressChangedEventArgs.

Cette classe permet de connaitre :

  • Le nombre d'octets reçus
  • Le nombre d'octets à recevoir
  • La progression du téléchargement (en pourcentage)

Elle est donc définie ainsi :

 /// <summary> /// Represents the event args for the progress of a download of a file. /// </summary> sealed public class DownloadWebFileProgressChangedEventArgs : EventArgs {  #region Properties /// <summary> /// Gets the number of bytes recieved. /// </summary> public long BytesRecieved { get; private set; } // <summary> /// Gets the percentage of the download. /// </summary> public int Percentage { get; private set; } /// <summary> /// Gets the number of bytes to recieve. /// </summary> public long BytesToRecieve { get; private set; }  #endregion  #region Constructor /// <summary> /// Instantiates the event. /// </summary> /// <param name="bytesReceived">Number of recieved bytes.</param> /// <param name="bytesToRecieved">Number of bytes to recieve.</param> /// <param name="percentage">Percentage of progression.</param> public DownloadWebFileProgressChangedEventArgs( long bytesReceived, long bytesToRecieved, int percentage) { this.BytesRecieved = bytesReceived; this.BytesToRecieve = bytesToRecieved; this.Percentage = percentage; }  #endregion } 

Donc le début de notre classe SkyDrive commence ainsi :

 /// <summary> /// Class to manage the SKYDRIVE system. /// </summary> sealed public class SkyDrive : IDisposable {  #region Events /// <summary> /// Event raised during the upload of the file. /// </summary> public event EventHandler<UploadWebFileProgressChangedEventArgs> UploadWebFileProgressChanged; /// <summary> /// Event raised during the download of the file. /// </summary> public event EventHandler<DownloadWebFileProgressChangedEventArgs> DownloadWebFileProgressChanged;  #endregion 

Au niveau des champs, il nous faudra :

  • Un champ permettant de gérer Skydrive
  • Un champ contenant la taille du buffer pour le téléchargement de fichiers
  • Un champ pour générer l'arbre
  • Un champ contenant le nom de la racine mère (c'est ça, nous allons avoir une racine mère qui permettra de lier les premiers répertoires récupérés par Skydrive)
  #region Fields /// <summary> /// Provides a client service for skydrive. /// </summary> private SkyDriveServiceClient sdsc; /// <summary> /// Buffer of 4 KB. /// </summary> public const int BufferSize = 4096; /// <summary> /// Represents the root of the tree. /// </summary> private Leaf rootTree; /// <summary> /// Name of the root. /// </summary> public const string RootName = "ROOT";  #endregion 

Au niveau des propriétés, nous allons en définir deux :

  • Une propriété permettant de récupérer les informations du compte
  • Une propriété permettant de récupérer les informations du disque Skydrive.
  #region Properties /// <summary> /// Gets the information about the account. /// </summary> /// <returns>The information of the account.</returns> public WebAccountInfo GetWebAccountInfo { get { return sdsc.GetWebAccountInfo(); } } /// <summary> /// Gets the information about the drive. /// </summary> /// <returns>The information of the drive.</returns> public WebDriveInfo GetWebDriveInfo { get { return sdsc.GetWebDriveInfo(); } }  #endregion 

Au niveau des constructeurs, nous allons en créer trois :

  • Un constructeur par defaut
  • Un constructeur prenant en paramètre le CID du compte SkyDrive
  • Un constructeur prenant en paramètre un SkyDriveSession.

Chacun des constructeurs instantiera l'arbre avec la feuille racine par défaut.

  #region Constructors /// <summary> /// Instantiates the Skydrive service client. /// </summary> public SkyDrive() { rootTree = new Leaf(RootName); sdsc = new SkyDriveServiceClient(); sdsc.UploadWebFileProgressChanged += OnUploadWebFileProgressChanged; } /// <summary> /// Instantiates the Skydrive service client. /// </summary> /// <param name="cid">CID of the user.</param> public SkyDrive(string cid) { rootTree = new Leaf(RootName); SkyDriveSession session = new SkyDriveSession(); session.Cid = cid; sdsc = new SkyDriveServiceClient(session); sdsc.UploadWebFileProgressChanged += OnUploadWebFileProgressChanged; } // <summary> /// Instantiates the skydrive service client. /// </summary> // <param name="session">Session to use.</param> public SkyDrive(SkyDriveSession session) { rootTree = new Leaf(RootName); sdsc = new SkyDriveServiceClient(session); sdsc.UploadWebFileProgressChanged += OnUploadWebFileProgressChanged; }  #endregion 

Ici, comme pour l'article précédent, il convient de noter que le constructeur sans paramètre doit impérativement s'employer avec la méthode LogOn sous peine de n'avoir rien à manipuler, alors que les deux autres constructeurs n'ont pas besoin d'être utilisés avec de méthode LogOn.

Cependant, avec les constructeurs qui prennent des paramètres, vous ne pouvrez récupérer que les dossiers et fichiers marqués comme PUBLIC. Donc le constructeur sans paramètre avec utilisation de la méthode LogOn vous permettra de récupérer aussi bien les répertoires / fichiers PUBLIC et PRIVE.

A vous de voir l'utilisation que vous souhaitez faire de cette classe.

La méthode LogOn permet de vous connecter sous votre compte SkyDrive. Elle prend en paramètre :

  • Le nom utilisateur du compte
  • Son mot de passe
 /// <summary> /// Log on into Skydrive. /// Don't use this if you use the constructor with the cid parameter  /// or the SkyDriveSession parameter. /// </summary> /// <param name="username">Name of the user.</param> /// <param name="password">Pasword of the user.</param> public void LogOn(string username, string password) { sdsc.LogOn(username, password); } 

Maintenant, nous devons pouvoir permettre la création de répertoires à la racine du système SkyDrive. Notre méthode renverra un booléen pour définir si la création a réussie ou échouée. De même lorsque la création a réussi, nous injectons une nouvelle feuille à notre arbre contenant les informations du répertoire.

  /// <summary> /// Create a root folder into Skydrive. /// </summary> /// <param name="name">Name of the folder.</param> /// <param name="categoryType">Type of the folder.</param> /// <param name="shareType">Share type of the folder.</param> /// <returns>True if the folder has been successfully created,  /// false otherwise.</returns> public bool CreateRootWebFolder(string name, WebFolderCategoryType categoryType, WebFolderItemShareType shareType) { WebFolderInfo rootFolder = null; try { rootFolder = sdsc.CreateRootWebFolder(name, categoryType, shareType); } catch { return false; } if (rootFolder == null) return false; rootTree.Children.Add(rootFolder); return true; } 

Maintenant, nous allons faire de même pour pouvoir créer un sous-répertoire. Cependant, ici, nous allons d'abord chercher la feuille qui servira de parent à notre feuille de sous-répertoire.

 /// <summary> /// Create a web folder inside the specified parent folder. /// </summary> /// <param name="name">Name of the folder.</param> /// <param name="webFolderParent">Name of the parent.</param> /// <returns>True if the folder has been successfully, false  /// otherwise.</returns> public bool CreateSubWebFolder(string name, string webFolderParent) { Leaf leaf = Search(rootTree, webFolderParent, typeof(WebFolderInfo)); if (leaf == null) return false; if (leaf.Value == null) return false; WebFolderInfo wfi = null; try { wfi = sdsc.CreateSubWebFolder(name, (WebFolderInfo)leaf.Value); } catch { return false; } leaf.Children.Add(wfi); return true; } 

Remarquez qu'ici, nous recherchons une feuille répertoire. L'API SkyDrive définit deux classes très importantes :

  • Une classe permettant de définir un répertoire (WebFolderInfo)
  • Une classe permettant de définir un fichier (WebFileInfo)

Donc ici, comme nous souhaitons récupérer le répertoire parent, il convient d'indiquer dans la méthode de recherche, que nous recherchons un type répertoire.

Maintenant, il nous faut une méthode permettant de récupérer les répertoires racines. Cette méthode renverra un booléen mais elle prendra en paramètre un tableau de string qui contiendra les noms de chaque répertoire.

 /// <summary> /// Gets the root folders. /// </summary> /// <param name="rootWebFoldersName">Array which contains the name of  /// each root folder.</param> /// <returns>True if the web folders has been successfully got,  /// false otherwise.</returns> public bool GetRootWebFolders(out string[] rootWebFoldersName) { rootWebFoldersName = null; WebFolderInfo[] wfiArray = null; try { wfiArray = sdsc.ListRootWebFolders(); } catch { return false; } rootWebFoldersName = new string[wfiArray.Length]; int index = 0; foreach (WebFolderInfo wfi in wfiArray) { rootTree.Children.Add(wfi); rootWebFoldersName[index++] = wfi.Name; } return true; } 

Comme vous pouvez le constater, ici l'arbre n'est pas vidé. Il faudra donc que ce soit l'utilisateur qui le vide. ne vous en faites pas, une telle méthode existe, il suffira juste de l'appeler.

Ensuite il nous faudra une méthode permettant de récupérer les sous-répertoires d'un répertoire. Notre méthode prendra donc en paramètres :

  • Le nom du répertoire que nous souhaitons scanné.
  • Un tableau contenant les noms de chaque sous-répertoire trouvé.
  /// <summary> /// Gets the folders inside a specified folder. /// </summary> /// <param name="parentFolder">Specified folder.</param> /// <param name="subfoldersName">Contains the name of the subfolders.</param> /// <returns>True if the subfolders has been successfully got,  /// false otherwise.</returns> public bool GetSubFolders(string parentFolder, out string[] subfoldersName) { subfoldersName = null; Leaf l = Search(rootTree, parentFolder, typeof(WebFolderInfo)); if (l == null) return false; if (l.Value == null) return false; WebFolderInfo[] wfiAr = null; try { wfiAr = sdsc.ListSubWebFolders((WebFolderInfo)l.Value); } catch { return false; } subfoldersName = new string[wfiAr.Length]; int index = 0; foreach (WebFolderInfo wfi in wfiAr) { l.Children.Add(new Leaf(wfi, l)); subfoldersName[index++]= wfi.Name; } return true; } 

Il nous faut la même chose pour les fichiers, le code est sensiblement le même :

 /// <summary> /// Gets the files inside a folder. /// </summary> /// <param name="parentFolder">Folder where the files are.</param> /// <param name="subWebFilesName">Contains the name of the files which  /// are in the folder.</param> /// <returns>True if the files have been successfully got, false  /// otherwise.</returns> public bool GetSubWebFiles(string parentFolder, out string[] subWebFilesName) { subWebFilesName = null; Leaf l = Search(rootTree, parentFolder, typeof(WebFolderInfo)); if (l == null) return false; if (l.Value == null) return false; WebFileInfo[] wfiArr = null; try { wfiArr = sdsc.ListSubWebFiles((WebFolderInfo)l.Value); } catch { return false; } subWebFilesName = new string[wfiArr.Length]; int index = 0; foreach (WebFileInfo wfi in wfiArr) { subWebFilesName[index++] = wfi.Name; l.Children.Add(new Leaf(wfi, l)); } return true; } 

L'API permet de renommer les répertoires, mais pas tel quels; il faut passer par un objet WebFolderInfoItem, donc il nous faut aussi pouvoir récupérer les items de chacun de nos répertoires (ROOT et non ROOT). Ces deux méthodes étant semblable, nous allons les écrires en même temps :

 /// <summary> /// Gets the sub folder items. /// </summary> /// <param name="parentFolder">Folder where the sub folders are.</param> /// <param name="subWebFolderItemsName">Contains the name of the  /// items of the sub folders.</param> /// <returns>True if the sub folder items has been successfully got,  /// false otherwise.</returns> public bool GetSubWebFolderItems(string parentFolder, out string[] subWebFolderItemsName) { subWebFolderItemsName = null; Leaf l = Search(rootTree, parentFolder, typeof(WebFolderInfo)); if (l == null) return false; if (l.Value == null) return false; WebFolderItemInfo[] wfiiArr = null; try { wfiiArr = sdsc.ListSubWebFolderItems((WebFolderInfo)l.Value); } catch { return false; } int index = 0; subWebFolderItemsName = new string[wfiiArr.Length]; foreach (WebFolderItemInfo wfii in wfiiArr) { subWebFolderItemsName[index++] = wfii.Name; l.Children.Add(wfii); } return true; } /// <summary> /// Gets the root folder items. /// </summary> /// <param name="rootWebFolderItemsName">Contains the name of the root  /// folder items.</param> /// <returns>True if the root folder items has been successfully got,  /// false otherwise.</returns> public bool GetRootWebFolderItems(out string[] rootWebFolderItemsName) { rootWebFolderItemsName = null; if (rootTree == null) return false; WebFolderItemInfo[] wfiiArr = null; try { wfiiArr = sdsc.ListRootWebFolderItems(); } catch { return false; } int index = 0; rootWebFolderItemsName = new string[wfiiArr.Length]; foreach (WebFolderItemInfo wfii in wfiiArr) { rootTree.Children.Add(wfii); rootWebFolderItemsName[index++] = wfii.Name; } return true; } 

Ensuite, il nous faut pouvoir effacer notre répertoire ou notre fichier. Pour cela il faudra aussi effacer ce répertoire ou ce fichier de notre arbre. Il suffit donc de rechercher le fichier voulu dans l'arbre et de l'effacer. Les deux méthodes étant similaire, nous écrivons :

 /// <summary> /// Delete a web file. /// </summary> /// <param name="webFile">File to delete.</param> /// <returns>True if the file has been successfully deleted, false  /// otherwise.</returns> public bool DeleteWebFile(string webFile) { Leaf l = Search(rootTree, webFile, typeof(WebFileInfo)); if (l == null) return false; if (l.Value == null) return false; rootTree.Delete(l); sdsc.DeleteWebFile((WebFileInfo)l.Value); return true; } /// <summary> /// Delete a web folder. /// </summary> /// <param name="webFolder">Folder to delete.</param> /// <returns>True if the folder has been successfully deleted,  /// false otherwise.</returns> public bool DeleteWebFolder(string webFolder) { Leaf l = Search(rootTree, webFolder, typeof(WebFolderInfo)); if (l == null) return false; if (l.Value == null) return false; rootTree.Delete(l); sdsc.DeleteWebFolder((WebFolderInfo)l.Value); return true; } 

Maintenant pour notre méthode de renommage de fichier, il suffira de le rechercher dans l'arbre et de changer son nom et d'appeler la méthode de renommage présente dans l'API SkyDrive.

 /// <summary> /// Rename a web folder. /// </summary> /// <param name="oldWebFolderItemName">Old name of the folder item.</param> /// <param name="newName">New name of the folder item.</param> /// <returns>True if the folder has been successfully renamed,  /// false otherwise.</returns> public bool RenameWebFolderItem(string oldWebFolderItemName, string newName) { Leaf l = Search(rootTree, oldWebFolderItemName, typeof(WebFolderItemInfo)); if (l == null) return false; if (l.Value == null) return false; sdsc.RenameWebFolderItem((WebFolderItemInfo)l.Value, newName); ((WebFolderItemInfo)l.Value).Name = newName; return true; } 

En ce qui concerne le téléchargement de fichier, il suffit de rechercher dans l'arbre le nom du fichier passé en paramètre et de l'envoyer à la méthode de téléchargement :

 /// <summary> /// Download a file. /// </summary> /// <param name="webFile">Name of the file to download.</param> /// <param name="path">Path where the downloaded file is stored.</param> /// <returns>True if the file has been successfully downloaded,  /// false otherwise.</returns> public bool DownloadWebFile(string webFile, string path) { Leaf l = Search(rootTree, webFile, typeof(WebFileInfo)); if (l == null) return false; if (l.Value == null) return false; WebFileInfo wfi = ((WebFileInfo)l.Value); byte[] buffer = new byte[BufferSize]; long size = wfi.Size; long writtenCharacters = 0; using (Stream stream = sdsc.DownloadWebFile(wfi)) { using (FileStream fs = new FileStream( path, FileMode.Create, FileAccess.Write)) { int count = stream.Read(buffer, 0, buffer.Length); while (count > 0) { fs.Write(buffer, 0, count); writtenCharacters += count; count = stream.Read(buffer, 0, buffer.Length); OnDownloadWebFileProgressChanged(writtenCharacters, size); } fs.Flush(); fs.Close(); } stream.Close(); } return true; } /// <summary> /// Occurs when the download has changed. /// </summary> /// <param name="writtenCharacters">Number of characters which have  /// been already written.</param> /// <param name="size">Size of the file.</param> private void OnDownloadWebFileProgressChanged( long writtenCharacters, long size) { if (DownloadWebFileProgressChanged != null) { int currentPercentage = (int)((double)writtenCharacters * 100f / size); DownloadWebFileProgressChanged(this, new DownloadWebFileProgressChangedEventArgs( writtenCharacters, size, currentPercentage)); } } 

La méthode d'uploading de fichier est beaucoup plus simple car l'API possède déjà ce qui est nécessaire à la manoeuvre :

 /// <summary> /// Upload a file. /// </summary> /// <param name="fileName">Name of the file to upload.</param> /// <param name="webFolderParent">Folder where the file is uploaded.</param> public void UploadWebFile(string fileName, string webFolderParent) { Leaf l = Search(rootTree, webFolderParent, typeof(WebFolderInfo)); if (l == null) return; if (l.Value == null) return; sdsc.UploadWebFile(fileName, (WebFolderInfo)l.Value); } /// <summary> /// Occurs when the upload has changed. /// </summary> /// <param name="sender">Object which invoke this method.</param> /// <param name="e">Event associated to this method.</param> private void OnUploadWebFileProgressChanged( object sender, UploadWebFileProgressChangedEventArgs e) { if (UploadWebFileProgressChanged != null) UploadWebFileProgressChanged(this, e); } 

Remarquer qu'ici je n'ajoute pas d'enfants à l'arbre. C'est que là nous aurons à faire à un objet de type string et non WebFileInfo. Or la classe WebFileInfo ne pouvant être instanciée, je ne peux pour le moment ajouté notre fichier dans l'arbre.

La méthode Clear retirer l'arbre en mémoire et en créer un nouveau. 

 /// <summary> /// Clear the tree, dispose it and create a new one. /// </summary> public void Clear() { rootTree.Dispose(); rootTree = null; rootTree = new Leaf(RootName); } 

La méthode Dispose détruit l'arbre et l'instance du client Skydrive.

 /// <summary> /// Dispose the instance. /// </summary> public void Dispose() { if (rootTree != null) rootTree.Dispose(); rootTree = null; sdsc = null; }  #endregion 

Vous avez certainement remarqué que je n'explique plus trop les méthodes écrites. Si vous avez l'oeil vif, vous avez dû vous apercevoir que la plupart des méthodes utilisent la méthode Search pour accomplir leurs fonctions. C'est grâce à cette fonction que l'utilisation de SkyDrive en utilisant que des objets String est possible.

Cette fonction va appeler la fonction Search de la classe Leaf; il nous suffira seulement de définir le prédicat de recherche.

Le prototype de cette méthode Search est ainsie :

 /// <summary> /// Search an item in the tree. /// </summary> /// <param name="tree">Tree to use for searching.</param> /// <param name="name">Name of the folder / file to search.</param> /// <param name="type">Type of the folder / file to search.</param> /// <returns>Null if the object wasn't found in the tree, the leaf  /// where the object is stored otherwise.</returns> private Leaf Search(Leaf tree, string name, Type type) { 

Donc notre prédicat devra respecter ces 2 principes :

  • La valeur contenue dans la feuille doit être du même type que le type passé en paramètre ou l'un de ces dérivés. 
  • Si la valeur est du type attendu, il faut récupérer par introspection la valeur de name et la comparer à la valeur name passé en paramètre

 Si les deux principes sont vérifiés, nous renvoyons true, sinon nous renvoyons false.

Le code de recherche est donc écrit ainsi :

  return tree.Search(delegate(Leaf l) { Type valueType = l.Value.GetType(); if (valueType == type || valueType.IsSubclassOf(type)) { if (valueType.GetProperty("Name").GetValue(l.Value, null).ToString() == name) return true; } return false; }); } 

Cependant, nous aurions pû passer par une expression lambda, mais comme il y'a pas de code rébarbatif, autant passé par un délégué.

CONCLUSION

Nous avons au travers de cet article comment nous pouvons utiliser l'API Skydrive en n'utilisant que des objets String, simplifiant grandement son utilisation. Si vous avez des suggestions ou des remarques, n'hésitez surtout pas.

 

Stay tuned,

@bienôt sur ce blog

Pour être informé des derniers articles, inscrivez vous :
Commenter cet article