/// <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);
    }
Example #5
0
 /// <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));
 }
Example #10
0
    /// <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));
 }
Example #12
0
    /// <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
    }
Example #13
0
 /// <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);
     }
 }
Example #14
0
    /// <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;
    }
Example #15
0
        /// <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);
            }
        }
    }
Example #18
0
    /// <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;
    }