/// <summary> /// Sanity testing on whether the file being uploaded is worth uploading /// </summary> /// <param name="localFilePath"></param> /// <returns></returns> bool IsValidUploadFile(string localFilePath) { //If the file is a custom settings file for the workbook, then ignore it if (DatasourcePublishSettings.IsSettingsFile(localFilePath)) { return(false); //Nothing to do, it's just a settings file } //Ignore temp files, since we know we don't want to upload them var fileExtension = Path.GetExtension(localFilePath).ToLower(); if ((fileExtension == ".tmp") || (fileExtension == ".temp")) { StatusLog.AddStatus("Ignoring temp file, " + localFilePath, -10); return(false); } //These are the only kinds of data sources we know about... if ((fileExtension != ".tds") && (fileExtension != ".tdsx")) { StatusLog.AddError("File is not a data source: " + localFilePath); return(false); } return(true); }
/// <summary> /// Attempts to upload a single file a Tableau Server, and then make it a published data source /// </summary> /// <param name="localFilePath"></param> /// <param name="projectId"></param> /// <param name="dbCredentials">If not NULL, then these are the DB credentials we want to associate with the content we are uploading</param> /// <returns></returns> private bool AttemptUploadSingleFile( string localFilePath, string projectId, CredentialManager.Credential dbCredentials, DatasourcePublishSettings publishSettings) { string uploadSessionId; try { var fileUploader = new UploadFile(Urls, Login, localFilePath, HttpClientFactory, UploadChunkSizeBytes, UploadChunkDelaySeconds); uploadSessionId = fileUploader.ExecuteRequest(); } catch (Exception exFileUpload) { Login.Logger.Error("Unexpected error attempting to upload file " + localFilePath + ", " + exFileUpload.Message); throw exFileUpload; } SiteDatasource dataSource = null; Login.Logger.Information("File chunks upload successful. Next step, make it a published datasource"); try { string fileName = Path.GetFileNameWithoutExtension(localFilePath); string uploadType = RemoveFileExtensionDot(Path.GetExtension(localFilePath).ToLower()); dataSource = FinalizePublish( uploadSessionId, FileIOHelper.Undo_GenerateWindowsSafeFilename(fileName), //[2016-05-06] If the name has escapted characters, unescape them uploadType, projectId, dbCredentials); UploadeDatasources.Add(dataSource); Login.Logger.Information("Upload content details: " + dataSource.ToString()); Login.Logger.Information("Success! Uploaded datasource " + Path.GetFileName(localFilePath)); } catch (Exception exPublishFinalize) { Login.Logger.Error("Unexpected error finalizing publish of file " + localFilePath + ", " + exPublishFinalize.Message); throw exPublishFinalize;; } //See if we want to reassign ownership of the datasource if (AttemptOwnershipAssignment) { try { AttemptOwnerReassignment(dataSource, publishSettings, SiteUsers); } catch (Exception exOwnershipAssignment) { Login.Logger.Error("Unexpected error reassigning ownership of published datasource " + dataSource.Name + ", " + exOwnershipAssignment.Message); LogManualAction_ReassignOwnership(dataSource.Name); throw exOwnershipAssignment; } } return(true); //Success }
/// <summary> /// Assign ownership /// </summary> /// <param name="datasource"></param> /// <param name="publishSettings"></param> /// <param name="siteUsers"></param> /// <returns>TRUE: The server content has the correct owner now. FALSE: We were unable to give the server content the correct owner</returns> private bool AttemptOwnerReassignment(SiteDatasource datasource, DatasourcePublishSettings publishSettings, IEnumerable <SiteUser> siteUsers) { this.StatusLog.AddStatusHeader("Attempting ownership assignement for Datasource " + datasource.Name + "/" + datasource.Id); //Something went wrong if we don't have a set of site users to do the look up if (siteUsers == null) { throw new ArgumentException("No set of site users provided for lookup"); } //Look the local meta data to see what the desired name is var desiredOwnerName = publishSettings.OwnerName; if (string.IsNullOrEmpty(desiredOwnerName)) { this.StatusLog.AddStatus("Skipping owner assignment. The local file system has no metadata with an owner information for " + datasource.Name); LogManualAction_ReassignOwnership(datasource.Name, "none specified", "No client ownership information was specified"); return(true); //Since there is no ownership preference stated locally, then ownership we assigned during upload was fine. } //Look on the list of users in the target site/server, and try to find a match // //NOTE: We are doing a CASE INSENSITIVE name comparison. We assume that there are not 2 users with the same name on server w/differet cases // Because if this, we want to be flexible and allow that our source/destination servers may have the user name specified with a differnt // case. -- This is less secure than a case-sensitive comparison, but is almost always what we want when porting content between servers var desiredServerUser = SiteUser.FindUserWithName(siteUsers, desiredOwnerName, StringComparison.InvariantCultureIgnoreCase); if (desiredServerUser == null) { this.StatusLog.AddError("The local file has a workbook/user mapping: " + datasource.Name + "/" + desiredOwnerName + ", but this user does not exist on the target site"); LogManualAction_ReassignOwnership(datasource.Name, desiredOwnerName, "The target site does not contain a user name that matches the owner specified by the local metadata"); return(false); //Not a run time error, but we have manual steps to perform } //If the server content is already correct, then there is nothing to do if (desiredServerUser.Id == datasource.OwnerId) { this.StatusLog.AddStatus("Workbook " + datasource.Name + "/" + datasource.Id + ", already has correct ownership. No update requried"); return(true); } //Lets tell server to update the owner var changeOwnership = new SendUpdateDatasourceOwner(_onlineUrls, _onlineSession, datasource.Id, desiredServerUser.Id); SiteDatasource updatedDatasource; try { this.StatusLog.AddStatus("Server request to change Datasource ownership, ds: " + datasource.Name + "/" + datasource.Id + ", user:"******"/" + desiredServerUser.Id); updatedDatasource = changeOwnership.ExecuteRequest(); } catch (Exception exChangeOnwnerhsip) { throw exChangeOnwnerhsip; //Unexpected error, send it upward } //Sanity check the result we got back: Double check to make sure we have the expected owner. if (updatedDatasource.OwnerId != desiredServerUser.Id) { this.StatusLog.AddError("Unexpected server error! Updated workbook Owner Id does not match expected. ds: " + datasource.Name + "/" + datasource.Id + ", " + "expected user: "******", " + "actual user: " + updatedDatasource.OwnerId ); } return(true); }
/// <summary> /// Uploads the contents of a directory to server /// </summary> /// <param name="rootContentPath"></param> /// <param name="currentContentPath"></param> /// <param name="recurseDirectories"></param> private void UploadDirectoryToServer( string rootContentPath, string currentContentPath, ProjectFindCreateHelper projectsList, bool recurseDirectories, out int countSuccess, out int countFailure) { countSuccess = 0; countFailure = 0; //Look up the project name based on directory name, and creating a project on demand string projectName; if (rootContentPath == currentContentPath) //If we are in the root upload directory, then assume any content goes to the Default project { projectName = ""; //Default project } else { projectName = FileIOHelper.Undo_GenerateWindowsSafeFilename(Path.GetFileName(currentContentPath)); } //Start off with no project ID -- we'll look it up as needed string projectIdForUploads = null; //------------------------------------------------------------------------------------- //Upload the files from local directory to server //------------------------------------------------------------------------------------- foreach (var thisFilePath in Directory.GetFiles(currentContentPath)) { bool isValidUploadFile = IsValidUploadFile(thisFilePath); if (isValidUploadFile) { //If we don't yet have a project ID, then get one if (string.IsNullOrWhiteSpace(projectIdForUploads)) { projectIdForUploads = projectsList.GetProjectIdForUploads(projectName); } try { //See if there are any credentials we want to publish with the content var dbCredentialsIfAny = helper_DetermineContentCredential( Path.GetFileName(thisFilePath), projectName); //See what content specific settings there may be for this workbook var publishSettings = DatasourcePublishSettings.GetSettingsForSavedDatasource(thisFilePath); //Do the file upload bool wasFileUploaded = AttemptUploadSingleFile(thisFilePath, projectIdForUploads, dbCredentialsIfAny, publishSettings); if (wasFileUploaded) { countSuccess++; } } catch (Exception ex) { countFailure++; StatusLog.AddError("Error uploading datasource " + thisFilePath + ". " + ex.Message); LogManualAction_UploadDataSource(thisFilePath); } } } //If we are running recursive , then look in the subdirectories too if (recurseDirectories) { int subDirSuccess; int subDirFailure; foreach (var subDirectory in Directory.GetDirectories(currentContentPath)) { UploadDirectoryToServer(rootContentPath, subDirectory, projectsList, true, out subDirSuccess, out subDirFailure); countSuccess += subDirSuccess; countFailure += subDirFailure; } } }
/// <summary> /// /// </summary> /// <param name="serverName"></param> public List <SiteDatasource> ExecuteRequest() { var statusLog = _onlineSession.StatusLog; var downloadedContent = new List <SiteDatasource>(); //Depending on the HTTP download file type we want different file extensions var typeMapper = new DownloadPayloadTypeHelper("tdsx", "tds"); var datasources = _datasources; if (datasources == null) { statusLog.AddError("NULL datasources. Aborting download."); return(null); } //For each datasource, download it and save it to the local file system foreach (var dsInfo in datasources) { //Local path save the workbook string urlDownload = _onlineUrls.Url_DatasourceDownload(_onlineSession, dsInfo); statusLog.AddStatus("Starting Datasource download " + dsInfo.Name); try { //Generate the directory name we want to download into var pathToSaveTo = FileIOHelper.EnsureProjectBasedPath( _localSavePath, _downloadToProjectDirectories, dsInfo, this.StatusLog); var fileDownloaded = this.DownloadFile(urlDownload, pathToSaveTo, dsInfo.Name, typeMapper); var fileDownloadedNoPath = System.IO.Path.GetFileName(fileDownloaded); statusLog.AddStatus("Finished Datasource download " + fileDownloadedNoPath); //Add to the list of our downloaded data sources if (!string.IsNullOrEmpty(fileDownloaded)) { downloadedContent.Add(dsInfo); //Generate the metadata file that has additional server provided information about the workbook if (_generateInfoFile) { DatasourcePublishSettings.CreateSettingsFile(dsInfo, fileDownloaded, _siteUserLookup); } } else { //We should never hit this code; just being defensive statusLog.AddError("Download error, no local file path for downloaded content"); } } catch (Exception ex) { statusLog.AddError("Error during Datasource download " + dsInfo.Name + "\r\n " + urlDownload + "\r\n " + ex.ToString()); } } //foreach //Return the set of successfully downloaded content return(downloadedContent); }
/// <summary> /// Attempts to upload a single file a Tableau Server, and then make it a published data source /// </summary> /// <param name="localFilePath"></param> /// <param name="projectId"></param> /// <param name="dbCredentials">If not NULL, then these are the DB credentials we want to associate with the content we are uploading</param> /// <returns></returns> private bool AttemptUploadSingleFile( string localFilePath, string projectId, CredentialManager.Credential dbCredentials, DatasourcePublishSettings publishSettings) { string uploadSessionId; try { var fileUploader = new UploadFile(_onlineUrls, _onlineSession, localFilePath, _uploadChunkSizeBytes, _uploadChunkDelaySeconds); uploadSessionId = fileUploader.ExecuteRequest(); } catch (Exception exFileUpload) { this.StatusLog.AddError("Unexpected error attempting to upload file " + localFilePath + ", " + exFileUpload.Message); throw exFileUpload; } SiteDatasource dataSource = null; this.StatusLog.AddStatus("File chunks upload successful. Next step, make it a published datasource", -10); try { string fileName = Path.GetFileNameWithoutExtension(localFilePath); string uploadType = RemoveFileExtensionDot(Path.GetExtension(localFilePath).ToLower()); dataSource = FinalizePublish( uploadSessionId, FileIOHelper.Undo_GenerateWindowsSafeFilename(fileName), //[2016-05-06] If the name has escapted characters, unescape them uploadType, projectId, dbCredentials); StatusLog.AddStatus("Upload content details: " + dataSource.ToString(), -10); StatusLog.AddStatus("Success! Uploaded datasource " + Path.GetFileName(localFilePath)); } catch (Exception exPublishFinalize) { this.StatusLog.AddError("Unexpected error finalizing publish of file " + localFilePath + ", " + exPublishFinalize.Message); throw exPublishFinalize; ; } //See if we want to reassign ownership of the datasource if (_attemptOwnershipAssignment) { try { AttemptOwnerReassignment(dataSource, publishSettings, _siteUsers); } catch (Exception exOwnershipAssignment) { this.StatusLog.AddError("Unexpected error reassigning ownership of published datasource " + dataSource.Name + ", " + exOwnershipAssignment.Message); LogManualAction_ReassignOwnership(dataSource.Name); throw exOwnershipAssignment; } } return true; //Success }
/// <summary> /// Assign ownership /// </summary> /// <param name="datasource"></param> /// <param name="publishSettings"></param> /// <param name="siteUsers"></param> /// <returns>TRUE: The server content has the correct owner now. FALSE: We were unable to give the server content the correct owner</returns> private bool AttemptOwnerReassignment(SiteDatasource datasource, DatasourcePublishSettings publishSettings, IEnumerable<SiteUser> siteUsers) { this.StatusLog.AddStatusHeader("Attempting ownership assignement for Datasource " + datasource.Name + "/" + datasource.Id); //Something went wrong if we don't have a set of site users to do the look up if (siteUsers == null) { throw new ArgumentException("No set of site users provided for lookup"); } //Look the local meta data to see what the desired name is var desiredOwnerName = publishSettings.OwnerName; if (string.IsNullOrEmpty(desiredOwnerName)) { this.StatusLog.AddStatus("Skipping owner assignment. The local file system has no metadata with an owner information for " + datasource.Name); LogManualAction_ReassignOwnership(datasource.Name, "none specified", "No client ownership information was specified"); return true; //Since there is no ownership preference stated locally, then ownership we assigned during upload was fine. } //Look on the list of users in the target site/server, and try to find a match // //NOTE: We are doing a CASE INSENSITIVE name comparison. We assume that there are not 2 users with the same name on server w/differet cases // Because if this, we want to be flexible and allow that our source/destination servers may have the user name specified with a differnt // case. -- This is less secure than a case-sensitive comparison, but is almost always what we want when porting content between servers var desiredServerUser = SiteUser.FindUserWithName(siteUsers, desiredOwnerName, StringComparison.InvariantCultureIgnoreCase); if (desiredServerUser == null) { this.StatusLog.AddError("The local file has a workbook/user mapping: " + datasource.Name + "/" + desiredOwnerName + ", but this user does not exist on the target site"); LogManualAction_ReassignOwnership(datasource.Name, desiredOwnerName, "The target site does not contain a user name that matches the owner specified by the local metadata"); return false; //Not a run time error, but we have manual steps to perform } //If the server content is already correct, then there is nothing to do if (desiredServerUser.Id == datasource.OwnerId) { this.StatusLog.AddStatus("Workbook " + datasource.Name + "/" + datasource.Id + ", already has correct ownership. No update requried"); return true; } //Lets tell server to update the owner var changeOwnership = new SendUpdateDatasourceOwner(_onlineUrls, _onlineSession, datasource.Id, desiredServerUser.Id); SiteDatasource updatedDatasource; try { this.StatusLog.AddStatus("Server request to change Datasource ownership, ds: " + datasource.Name + "/" + datasource.Id + ", user:"******"/" + desiredServerUser.Id); updatedDatasource = changeOwnership.ExecuteRequest(); } catch (Exception exChangeOnwnerhsip) { throw exChangeOnwnerhsip; //Unexpected error, send it upward } //Sanity check the result we got back: Double check to make sure we have the expected owner. if (updatedDatasource.OwnerId != desiredServerUser.Id) { this.StatusLog.AddError("Unexpected server error! Updated workbook Owner Id does not match expected. ds: " + datasource.Name + "/" + datasource.Id + ", " + "expected user: "******", " + "actual user: " + updatedDatasource.OwnerId ); } return true; }