/// <summary> /// Attempts to upload a single file a Tableau Server, and then make it a published workbook /// </summary> /// <param name="localFilePath"></param> /// <returns></returns> private bool AttemptUploadSingleFile_Inner( string localFilePath, string projectId, CredentialManager.Credential dbCredentials, WorkbookPublishSettings 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; } SiteWorkbook workbook; this.StatusLog.AddStatus("File chunks upload successful. Next step, make it a published workbook", -10); try { string fileName = Path.GetFileNameWithoutExtension(localFilePath); string uploadType = RemoveFileExtensionDot(Path.GetExtension(localFilePath).ToLower()); workbook = FinalizePublish( uploadSessionId, FileIOHelper.Undo_GenerateWindowsSafeFilename(fileName), //[2016-05-06] If the name has escapted characters, unescape them uploadType, projectId, dbCredentials, publishSettings); StatusLog.AddStatus("Upload content details: " + workbook.ToString(), -10); StatusLog.AddStatus("Success! Uploaded workbook " + Path.GetFileName(localFilePath)); } catch (Exception exPublishFinalize) { this.StatusLog.AddError("Unexpected error finalizing publish of file " + localFilePath + ", " + exPublishFinalize.Message); LogManualAction_UploadWorkbook(localFilePath); throw exPublishFinalize; } //See if we want to reassign ownership of the workbook if (_attemptOwnershipAssignment) { try { AttemptOwnerReassignment(workbook, publishSettings, _siteUsers); } catch (Exception exOwnershipAssignment) { this.StatusLog.AddError("Unexpected error reassigning ownership of published workbook " + workbook.Name + ", " + exOwnershipAssignment.Message); LogManualAction_ReassignOwnership(workbook.Name); throw exOwnershipAssignment; } } return(true); //Success }
/// <summary> /// Look up any saved settings we have associated with a datasource on our local file systemm /// </summary> /// <param name="datasourceWithPath"></param> /// <returns></returns> internal static DatasourcePublishSettings GetSettingsForSavedDatasource(string datasourceWithPath) { //Sanity test: If the datasource is not there, then we probably have an incorrect path AppDiagnostics.Assert(File.Exists(datasourceWithPath), "Underlying datasource does not exist"); //Find the path to the settings file var pathToSettingsFile = PathForSettingsFile(datasourceWithPath); if (!File.Exists(pathToSettingsFile)) { return(new DatasourcePublishSettings(null)); } //=================================================================== //We've got a setings file, let's parse it! //=================================================================== var xmlDoc = new XmlDocument(); xmlDoc.Load(pathToSettingsFile); //Show sheets string ownerName = WorkbookPublishSettings.ParseXml_GetOwnerName(xmlDoc); //Return the Settings data return(new DatasourcePublishSettings(ownerName)); }
/// <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 (WorkbookPublishSettings.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 files we know about... if ((fileExtension != ".twb") && (fileExtension != ".twbx")) { StatusLog.AddError("File is not a workbook: " + localFilePath); return(false); } return(true); }
/// <summary> /// /// </summary> /// <param name="serverName"></param> public ICollection <SiteWorkbook> ExecuteRequest() { var statusLog = _onlineSession.StatusLog; var downloadedContent = new List <SiteWorkbook>(); var workbooks = _workbooks; if (workbooks == null) { statusLog.AddError("NULL workbooks. Aborting download."); return(null); } //Depending on the HTTP download file type we want different file extensions var typeMapper = new DownloadPayloadTypeHelper("twbx", "twb"); foreach (var contentInfo in workbooks) { //Local path save the workbook string urlDownload = _onlineUrls.Url_WorkbookDownload(_onlineSession, contentInfo); statusLog.AddStatus("Starting Workbook download " + contentInfo.Name + " " + contentInfo.ToString()); try { //Generate the directory name we want to download into var pathToSaveTo = FileIOHelper.EnsureProjectBasedPath( _localSavePath, _downloadToProjectDirectories, contentInfo, this.StatusLog); var fileDownloaded = this.DownloadFile(urlDownload, pathToSaveTo, contentInfo.Name, typeMapper); var fileDownloadedNoPath = System.IO.Path.GetFileName(fileDownloaded); statusLog.AddStatus("Finished Workbook download " + fileDownloadedNoPath); //Add to the list of our downloaded workbooks, and save metadata if (!string.IsNullOrWhiteSpace(fileDownloaded)) { downloadedContent.Add(contentInfo); //Generate the metadata file that has additional server provided information about the workbook if (_generateInfoFile) { WorkbookPublishSettings.CreateSettingsFile(contentInfo, 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 Workbook download " + contentInfo.Name + "\r\n " + urlDownload + "\r\n " + ex.ToString()); } } //foreach return(downloadedContent); }
/// <summary> /// /// </summary> /// <param name="thisFilePath"></param> /// <param name="projectIdForUploads"></param> /// <param name="dbCredentials">If not NULL, then these are the DB credentials we want to associate with the content we are uploading</param> /// <param name="publishSettings">Workbook publish settings (e.g. whether to show tabs in vizs)</param> private bool AttemptUploadSingleFile( string thisFilePath, string projectIdForUploads, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { return(AttemptUploadSingleFile_Inner(thisFilePath, projectIdForUploads, dbCredentials, publishSettings)); }
/// <summary> /// Makes a copy of the file; remaps the Workbook references to the server, uploads the remapped file /// </summary> /// <param name="thisFilePath"></param> /// <param name="projectIdForUploads"></param> private bool AttemptUploadSingleFile_ReferencesRemapped( string thisFilePath, string projectIdForUploads, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { bool success = false; string filename = Path.GetFileName(thisFilePath); string pathToRemapFile = Path.Combine(_localPathTempWorkspace, filename); File.Copy(thisFilePath, pathToRemapFile, true); //Copy the file string fileType = Path.GetExtension(filename).ToLower(); if (fileType == ".twb") { //Remap the references in the file var twbRemapper = new TwbDataSourceEditor(pathToRemapFile, pathToRemapFile, _onlineUrls, this.StatusLog); twbRemapper.Execute(); success = AttemptUploadSingleFile_Inner(pathToRemapFile, projectIdForUploads, dbCredentials, publishSettings); } else if (fileType == ".twbx") { //Make sure we have a directory to unzip to var pathUnzip = Path.Combine(_localPathTempWorkspace, "unzipped"); if (Directory.Exists(pathUnzip)) { Directory.Delete(pathUnzip, true); } Directory.CreateDirectory(pathUnzip); var twbxRemapper = new TwbxDataSourceEditor(pathToRemapFile, pathUnzip, _onlineUrls, this.StatusLog); string pathTwbxRemappedOutput = twbxRemapper.Execute(); //Upload the remapped file success = AttemptUploadSingleFile_Inner(pathTwbxRemappedOutput, projectIdForUploads, dbCredentials, publishSettings); //Clean-up and delete the whole unzipped directory Directory.Delete(pathUnzip, true); } else { //We should never hit this... bad content this.StatusLog.AddError("Error Workbook upload - Expected Workbook filetype! " + filename); } //Delete the remap file File.Delete(pathToRemapFile); return(success); }
/// <summary> /// /// </summary> /// <param name="thisFilePath"></param> /// <param name="projectIdForUploads"></param> /// <param name="dbCredentials">If not NULL, then these are the DB credentials we want to associate with the content we are uploading</param> /// <param name="publishSettings">Workbook publish settings (e.g. whether to show tabs in vizs)</param> private bool AttemptUploadSingleFile( string thisFilePath, string projectIdForUploads, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { //Assume it's a file we should try to upload if (_remapWorkbookReferences) { return(AttemptUploadSingleFile_ReferencesRemapped(thisFilePath, projectIdForUploads, dbCredentials, publishSettings)); } else { return(AttemptUploadSingleFile_Inner(thisFilePath, projectIdForUploads, dbCredentials, publishSettings)); } }
/// <summary> /// Save Datasource metadata in a XML file along-side the workbook file /// </summary> /// <param name="wb">Information about the workbook we have downloaded</param> /// <param name="localDatasourcePath">Local path to the twb/twbx of the workbook</param> /// <param name="userLookups">If non-NULL contains the mapping of users/ids so we can look up the owner</param> internal static void CreateSettingsFile(SiteDatasource ds, string localDatasourcePath, KeyedLookup <SiteUser> userLookups) { string contentOwnerName = null; //Start off assuming we have no content owner information if (userLookups != null) { contentOwnerName = WorkbookPublishSettings.helper_LookUpOwnerId(ds.OwnerId, userLookups); } var xml = System.Xml.XmlWriter.Create(PathForSettingsFile(localDatasourcePath)); xml.WriteStartDocument(); xml.WriteStartElement(XmlElement_DatasourceInfo); //If we have an owner name, write it out if (!string.IsNullOrWhiteSpace(contentOwnerName)) { XmlHelper.WriteValueElement(xml, WorkbookPublishSettings.XmlElement_ContentOwner, contentOwnerName); } xml.WriteEndElement(); //end: WorkbookInfo xml.WriteEndDocument(); xml.Close(); }
/// <summary> /// Look up settings associated with this content /// </summary> /// <param name="workbookWithPath"></param> /// <returns></returns> WorkbookPublishSettings helper_DetermineContentPublishSettings(string workbookWithPath) { return(WorkbookPublishSettings.GetSettingsForSavedWorkbook(workbookWithPath)); }
/// <summary> /// After a file has been uploaded in chunks, we need to make a call to COMMIT the file to server as a published Workbook /// </summary> /// <param name="uploadSessionId"></param> /// <param name="publishedContentName"></param> private SiteWorkbook FinalizePublish( string uploadSessionId, string publishedContentName, string publishedContentType, string projectId, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { //See definition: http://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook%3FTocPath%3DAPI%2520Reference%7C_____29 var sb = new StringBuilder(); //Build the XML part of the MIME message we will post up to server var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("workbook"); xmlWriter.WriteAttributeString("name", publishedContentName); xmlWriter.WriteAttributeString("showTabs", XmlHelper.BoolToXmlText(publishSettings.ShowTabs)); //If we have an associated database credential, write it out if (dbCredentials != null) { CredentialXmlHelper.WriteCredential( xmlWriter, dbCredentials); } xmlWriter.WriteStartElement("project"); //<project> xmlWriter.WriteAttributeString("id", projectId); xmlWriter.WriteEndElement(); //</project> xmlWriter.WriteEndElement(); // </workbook> //Currently not supporting <connectionCredentials> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Generate the MIME message and pack the XML into it var mimeGenerator = new MimeWriterXml(xmlText); //Create a web request to POST the MIME message to server to finalize the publish var urlFinalizeUpload = _onlineUrls.Url_FinalizeWorkbookPublish(_onlineSession, uploadSessionId, publishedContentType); //NOTE: The publish finalization step can take several minutes, because server needs to unpack the uploaded ZIP and file it away. // For this reason, we pass in a long timeout var webRequest = this.CreateAndSendMimeLoggedInRequest(urlFinalizeUpload, "POST", mimeGenerator, TableauServerWebClient.DefaultLongRequestTimeOutMs); var response = GetWebReponseLogErrors(webRequest, "finalize workbook publish"); using (response) { var xmlDoc = GetWebResponseAsXml(response); //Get all the workbook nodes var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var workbookXml = xmlDoc.SelectSingleNode("//iwsOnline:workbook", nsManager); try { return new SiteWorkbook(workbookXml); } catch(Exception parseXml) { StatusLog.AddError("Workbook upload, error parsing XML response " + parseXml.Message + "\r\n" + workbookXml.InnerXml); return null; } } }
/// <summary> /// Generates the path/filename of the Settings file that corresponds to the datasource path /// </summary> /// <param name="datasourcePath"></param> /// <returns></returns> private static string PathForSettingsFile(string datasourcePath) { return(WorkbookPublishSettings.PathForSettingsFile(datasourcePath)); }
/// <summary> /// Attempts to upload a single file a Tableau Server, and then make it a published workbook /// </summary> /// <param name="localFilePath"></param> /// <returns></returns> private bool AttemptUploadSingleFile_Inner( string localFilePath, string projectId, CredentialManager.Credential dbCredentials, WorkbookPublishSettings 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; } SiteWorkbook workbook; this.StatusLog.AddStatus("File chunks upload successful. Next step, make it a published workbook", -10); try { string fileName = Path.GetFileNameWithoutExtension(localFilePath); string uploadType = RemoveFileExtensionDot(Path.GetExtension(localFilePath).ToLower()); workbook = FinalizePublish( uploadSessionId, FileIOHelper.Undo_GenerateWindowsSafeFilename(fileName), //[2016-05-06] If the name has escapted characters, unescape them uploadType, projectId, dbCredentials, publishSettings); StatusLog.AddStatus("Upload content details: " + workbook.ToString(), -10); StatusLog.AddStatus("Success! Uploaded workbook " + Path.GetFileName(localFilePath)); } catch (Exception exPublishFinalize) { this.StatusLog.AddError("Unexpected error finalizing publish of file " + localFilePath + ", " + exPublishFinalize.Message); LogManualAction_UploadWorkbook(localFilePath); throw exPublishFinalize; } //See if we want to reassign ownership of the workbook if(_attemptOwnershipAssignment) { try { AttemptOwnerReassignment(workbook, publishSettings, _siteUsers); } catch (Exception exOwnershipAssignment) { this.StatusLog.AddError("Unexpected error reassigning ownership of published workbook " + workbook.Name + ", " + exOwnershipAssignment.Message); LogManualAction_ReassignOwnership(workbook.Name); throw exOwnershipAssignment; } } return true; //Success }
/// <summary> /// /// </summary> /// <param name="thisFilePath"></param> /// <param name="projectIdForUploads"></param> /// <param name="dbCredentials">If not NULL, then these are the DB credentials we want to associate with the content we are uploading</param> /// <param name="publishSettings">Workbook publish settings (e.g. whether to show tabs in vizs)</param> private bool AttemptUploadSingleFile( string thisFilePath, string projectIdForUploads, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { //Assume it's a file we should try to upload if (_remapWorkbookReferences) { return AttemptUploadSingleFile_ReferencesRemapped(thisFilePath, projectIdForUploads, dbCredentials, publishSettings); } else { return AttemptUploadSingleFile_Inner(thisFilePath, projectIdForUploads, dbCredentials, publishSettings); } }
/// <summary> /// Assign ownership /// </summary> /// <param name="workbook"></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(SiteWorkbook workbook, WorkbookPublishSettings publishSettings, IEnumerable<SiteUser> siteUsers) { this.StatusLog.AddStatusHeader("Attempting ownership assignement for Workbook " + workbook.Name + "/" + workbook.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 " + workbook.Name); LogManualAction_ReassignOwnership(workbook.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: " + workbook.Name + "/" + desiredOwnerName + ", but this user does not exist on the target site"); LogManualAction_ReassignOwnership(workbook.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 == workbook.OwnerId) { this.StatusLog.AddStatus("Workbook " + workbook.Name + "/" + workbook.Id + ", already has correct ownership. No update requried"); return true; } //Lets tell server to update the owner var changeOwnership = new SendUpdateWorkbookOwner(_onlineUrls, _onlineSession, workbook.Id, desiredServerUser.Id); SiteWorkbook updatedWorkbook; try { this.StatusLog.AddStatus("Server request to change Workbook ownership, wb: " + workbook.Name + "/" + workbook.Id + ", user:"******"/" + desiredServerUser.Id); updatedWorkbook = 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(updatedWorkbook.OwnerId != desiredServerUser.Id) { this.StatusLog.AddError("Unexpected server error! Updated workbook Owner Id does not match expected. wb: " + workbook.Name + "/" + workbook.Id + ", " + "expected user: "******", " + "actual user: " + updatedWorkbook.OwnerId ); } return true; }
/// <summary> /// After a file has been uploaded in chunks, we need to make a call to COMMIT the file to server as a published Workbook /// </summary> /// <param name="uploadSessionId"></param> /// <param name="publishedContentName"></param> private SiteWorkbook FinalizePublish( string uploadSessionId, string publishedContentName, string publishedContentType, string projectId, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { if (projectId == null) { projectId = ""; } //See definition: http://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook%3FTocPath%3DAPI%2520Reference%7C_____29 var sb = new StringBuilder(); //Build the XML part of the MIME message we will post up to server var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("workbook"); xmlWriter.WriteAttributeString("name", publishedContentName); xmlWriter.WriteAttributeString("showTabs", XmlHelper.BoolToXmlText(publishSettings.ShowTabs)); //If we have an associated database credential, write it out if (dbCredentials != null) { CredentialXmlHelper.WriteCredential( xmlWriter, dbCredentials); } xmlWriter.WriteStartElement("project"); //<project> xmlWriter.WriteAttributeString("id", projectId); xmlWriter.WriteEndElement(); //</project> xmlWriter.WriteEndElement(); // </workbook> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Dispose(); var xmlText = sb.ToString(); //Get the XML text out //Generate the MIME message and pack the XML into it var mimeGenerator = new MimeWriterXml(xmlText); //Create a web request to POST the MIME message to server to finalize the publish var urlFinalizeUpload = Urls.Url_FinalizeWorkbookPublish(Login, uploadSessionId, publishedContentType); //NOTE: The publish finalization step can take several minutes, because server needs to unpack the uploaded ZIP and file it away. // For this reason, we pass in a long timeout var response = CreateAndSendMimeLoggedInRequest(urlFinalizeUpload, HttpMethod.Post, mimeGenerator); using (response) { var xmlDoc = GetHttpResponseAsXml(response); var xDoc = xmlDoc.ToXDocument(); var workbookXml = xDoc.Root.Descendants(XName.Get("workbook", XmlNamespace)).FirstOrDefault(); try { return(new SiteWorkbook(workbookXml.ToXmlNode(), XmlNamespace)); } catch (Exception parseXml) { Login.Logger.Error("Workbook upload, error parsing XML response " + parseXml.Message + "\r\n" + workbookXml.ToXmlNode().InnerXml); return(null); } } }
/// <summary> /// Assign ownership /// </summary> /// <param name="workbook"></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(SiteWorkbook workbook, WorkbookPublishSettings publishSettings, IEnumerable <SiteUser> siteUsers) { this.StatusLog.AddStatusHeader("Attempting ownership assignement for Workbook " + workbook.Name + "/" + workbook.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 " + workbook.Name); LogManualAction_ReassignOwnership(workbook.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: " + workbook.Name + "/" + desiredOwnerName + ", but this user does not exist on the target site"); LogManualAction_ReassignOwnership(workbook.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 == workbook.OwnerId) { this.StatusLog.AddStatus("Workbook " + workbook.Name + "/" + workbook.Id + ", already has correct ownership. No update requried"); return(true); } //Lets tell server to update the owner var changeOwnership = new SendUpdateWorkbookOwner(_onlineSession, workbook.Id, desiredServerUser.Id); SiteWorkbook updatedWorkbook; try { this.StatusLog.AddStatus("Server request to change Workbook ownership, wb: " + workbook.Name + "/" + workbook.Id + ", user:"******"/" + desiredServerUser.Id); updatedWorkbook = 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 (updatedWorkbook.OwnerId != desiredServerUser.Id) { this.StatusLog.AddError("Unexpected server error! Updated workbook Owner Id does not match expected. wb: " + workbook.Name + "/" + workbook.Id + ", " + "expected user: "******", " + "actual user: " + updatedWorkbook.OwnerId ); } return(true); }
/// <summary> /// After a file has been uploaded in chunks, we need to make a call to COMMIT the file to server as a published Workbook /// </summary> /// <param name="uploadSessionId"></param> /// <param name="publishedContentName"></param> private SiteWorkbook FinalizePublish( string uploadSessionId, string publishedContentName, string publishedContentType, string projectId, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { //See definition: http://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook%3FTocPath%3DAPI%2520Reference%7C_____29 var sb = new StringBuilder(); //Build the XML part of the MIME message we will post up to server var xmlWriter = XmlWriter.Create(sb, XmlHelper.XmlSettingsForWebRequests); xmlWriter.WriteStartElement("tsRequest"); xmlWriter.WriteStartElement("workbook"); xmlWriter.WriteAttributeString("name", publishedContentName); xmlWriter.WriteAttributeString("showTabs", XmlHelper.BoolToXmlText(publishSettings.ShowTabs)); //If we have an associated database credential, write it out if (dbCredentials != null) { CredentialXmlHelper.WriteCredential( xmlWriter, dbCredentials); } xmlWriter.WriteStartElement("project"); //<project> xmlWriter.WriteAttributeString("id", projectId); xmlWriter.WriteEndElement(); //</project> xmlWriter.WriteEndElement(); // </workbook> //Currently not supporting <connectionCredentials> xmlWriter.WriteEndElement(); // </tsRequest> xmlWriter.Close(); var xmlText = sb.ToString(); //Get the XML text out //Generate the MIME message and pack the XML into it var mimeGenerator = new MimeWriterXml(xmlText); //Create a web request to POST the MIME message to server to finalize the publish var urlFinalizeUpload = _onlineUrls.Url_FinalizeWorkbookPublish(_onlineSession, uploadSessionId, publishedContentType); //NOTE: The publish finalization step can take several minutes, because server needs to unpack the uploaded ZIP and file it away. // For this reason, we pass in a long timeout var webRequest = this.CreateAndSendMimeLoggedInRequest(urlFinalizeUpload, "POST", mimeGenerator, TableauServerWebClient.DefaultLongRequestTimeOutMs); var response = GetWebReponseLogErrors(webRequest, "finalize workbook publish"); using (response) { var xmlDoc = GetWebResponseAsXml(response); //Get all the workbook nodes var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var workbookXml = xmlDoc.SelectSingleNode("//iwsOnline:workbook", nsManager); try { return(new SiteWorkbook(workbookXml)); } catch (Exception parseXml) { StatusLog.AddError("Workbook upload, error parsing XML response " + parseXml.Message + "\r\n" + workbookXml.InnerXml); return(null); } } }
/// <summary> /// Makes a copy of the file; remaps the Workbook references to the server, uploads the remapped file /// </summary> /// <param name="thisFilePath"></param> /// <param name="projectIdForUploads"></param> private bool AttemptUploadSingleFile_ReferencesRemapped( string thisFilePath, string projectIdForUploads, CredentialManager.Credential dbCredentials, WorkbookPublishSettings publishSettings) { bool success = false; string filename = Path.GetFileName(thisFilePath); string pathToRemapFile = Path.Combine(_localPathTempWorkspace, filename); File.Copy(thisFilePath, pathToRemapFile, true); //Copy the file string fileType = Path.GetExtension(filename).ToLower(); if(fileType == ".twb") { //Remap the references in the file var twbRemapper = new TwbDataSourceEditor(pathToRemapFile, pathToRemapFile, _onlineUrls, this.StatusLog); twbRemapper.Execute(); success = AttemptUploadSingleFile_Inner(pathToRemapFile, projectIdForUploads, dbCredentials, publishSettings); } else if(fileType == ".twbx") { //Make sure we have a directory to unzip to var pathUnzip = Path.Combine(_localPathTempWorkspace, "unzipped"); if(Directory.Exists(pathUnzip)) { Directory.Delete(pathUnzip, true); } Directory.CreateDirectory(pathUnzip); var twbxRemapper = new TwbxDataSourceEditor(pathToRemapFile, pathUnzip, _onlineUrls, this.StatusLog); string pathTwbxRemappedOutput = twbxRemapper.Execute(); //Upload the remapped file success = AttemptUploadSingleFile_Inner(pathTwbxRemappedOutput, projectIdForUploads, dbCredentials, publishSettings); //Clean-up and delete the whole unzipped directory Directory.Delete(pathUnzip, true); } else { //We should never hit this... bad content this.StatusLog.AddError("Error Workbook upload - Expected Workbook filetype! " + filename); } //Delete the remap file File.Delete(pathToRemapFile); return success; }