'use strict' ; import * as vscode from 'vscode' ; import { Reporter } from '../util/Reporter' ; import { CommandUtil } from '../util/CommandUtil' ; import { VSCodeBlockchainOutputAdapter } from '../logging/VSCodeBlockchainOutputAdapter' ; import { UserInputUtil } from '../commands/UserInputUtil' ; import * as path from 'path' ; import * as fs from 'fs-extra' ; import Axios from 'axios' ; import * as showdown from 'showdown' ; import * as os from 'os' ; import { RepositoryRegistry } from '../registries/RepositoryRegistry' ; import * as shell from 'shelljs' ; import { ExtensionUtil } from '../util/ExtensionUtil' ; import * as ejs from 'ejs' ; import { LogType } from 'ibm-blockchain-platform-common' ; import { View } from './View' ; import { RepositoryRegistryEntry } from '../registries/RepositoryRegistryEntry' ; import { TimerUtil } from '../util/TimerUtil' ; export class SampleView extends View { private repoName : string ; private sampleName : string ; constructor ( context , repoName , sampleName ) { super ( context , sampleName , `${ sampleName } Sample` ) ; this . repoName = repoName ; this . sampleName = sampleName ; } loadComponent ( _panel ) { return ; } async openPanelInner ( panel ) { Reporter . instance ( ) . sendTelemetryEvent ( 'openedView' , { openedView : panel . title } ) ; const extensionPath = ExtensionUtil . getExtensionPath ( ) ; const whitePanelIcon = vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'github_black.svg' ) ) ; const blackPanelIcon = vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'github_white.svg' ) ) ; panel . iconPath = { light : whitePanelIcon , dark : blackPanelIcon } ; panel . webview . onDidReceiveMessage ( async ( message ) => { if ( message . command === 'clone' ) { await this . cloneRepository ( message . recloning ) ; panel . webview . html = await this . getHTMLString ( panel . webview ) ; Reporter . instance ( ) . sendTelemetryEvent ( 'Sample Cloned' , { sample : this . sampleName } ) ; } else if ( message . command === 'open' ) { const fileName = message . fileName ; const fileType = message . fileType ; const language = message . language ; await this . openFile ( fileType , fileName , language ) ; panel . webview . html = await this . getHTMLString ( panel . webview ) ; Reporter . instance ( ) . sendTelemetryEvent ( 'Sample Opened' , { sample : this . sampleName , name : fileName , type : fileType , language : language } ) ; } else if ( message . command === 'getLanguageVersion' ) { const contractName = message . contractName ; const languageType = message . languageType ; const version = await this . getLanguageVersion ( contractName , languageType ) ; panel . webview . postMessage ( { version : version } ) ; } } , undefined , this . context . subscriptions ) ; } async getHTMLString ( webview ) { const extensionPath = ExtensionUtil . getExtensionPath ( ) ; const marketplaceIcon = webview . asWebviewUri ( vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'blockchain_marketplace.png' ) ) ) ; const contractsIconWhite = webview . asWebviewUri ( vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'contracts_icon_white.svg' ) ) ) ; const contractsIconBlack = webview . asWebviewUri ( vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'contracts_icon_black.svg' ) ) ) ; const applicationsIconWhite = webview . asWebviewUri ( vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'applications_icon_white.svg' ) ) ) ; const applicationsIconBlack = webview . asWebviewUri ( vscode . Uri . file ( path . join ( extensionPath , 'resources' , 'applications_icon_black.svg' ) ) ) ; const repository = await this . getRepository ( this . repoName ) ; const sample = this . getSample ( repository , this . sampleName ) ; const images = { marketplaceIcon : marketplaceIcon , contractsIconWhite : contractsIconWhite , contractsIconBlack : contractsIconBlack , applicationsIconWhite : applicationsIconWhite , applicationsIconBlack : applicationsIconBlack , } ; const repositoryName = repository . name ; const repositoryRegistry = RepositoryRegistry . instance ( ) ; let repositoryConfig ; try { repositoryConfig = await repositoryRegistry . get ( repositoryName ) ; } catch ( error ) { repositoryConfig = undefined ; } const options = { repositoryName : repositoryName , repositoryOrgName : repository . orgName , sample : sample , repositoryConfig : repositoryConfig , images : images } ; const samplePageHtml = await this . getSamplePage ( options ) ; return samplePageHtml ; } private async getSamplePage ( options ) { let validInternetConnection = true ; const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; const templatePath = path . join ( __dirname , '..' , '..' , '..' , 'templates' , 'SampleView.ejs' ) ; const converter = new showdown . Converter ( ) ; try { const response = await Axios . get ( options . sample . readme ) ; const text = response . data ; const html = converter . makeHtml ( text ) ; options . html = html ; } catch ( error ) { validInternetConnection = false ; } if ( ! validInternetConnection ) { const repositoryRegistry = RepositoryRegistry . instance ( ) ; const repoIsCloned = await repositoryRegistry . exists ( options . repositoryName ) ; if ( repoIsCloned ) { const clonedRepo = await repositoryRegistry . get ( options . repositoryName ) ; const repoPath = clonedRepo . path ; const readMeOnJSON = options . sample . readme ; const splitPath = readMeOnJSON . split ( options . repositoryOrgName ) ; const branchedPath = splitPath [ 1 ] ; const endOfBranchName = branchedPath . lastIndexOf ( '/' ) ; const slicedPath = branchedPath . slice ( endOfBranchName + 1 , branchedPath . length ) ; const readMe = await fs . readFile ( path . join ( repoPath , slicedPath ) , 'utf8' ) ; const html = converter . makeHtml ( readMe ) ; options . html = html ; } else { options . html = 'No internet connection - unable to retrieve the README.' ; outputAdapter . log ( LogType . WARNING , 'Unable to retrieve README' ) ; } } return await new Promise ( ( resolve , reject ) : any => { ejs . renderFile ( templatePath , options , { async : true } , ( error , data ) => { if ( error ) { reject ( error ) ; } else { resolve ( data ) ; } } ) ; } ) ; } private async cloneRepository ( recloning = false ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; const repository = await this . getRepository ( this . repoName ) ; const url = repository . remote ; const defaultPath = path . join ( os . homedir ( ) , this . repoName ) ; const savePath = await vscode . window . showSaveDialog ( { defaultUri : vscode . Uri . file ( defaultPath ) , saveLabel : 'Clone Repository' } ) ; if ( ! savePath ) { return ; } const index = savePath . fsPath . lastIndexOf ( path . sep ) ; const saveDir = savePath . fsPath . substring ( index + 1 ) ; const command = `git clone ${ url } ${ saveDir }` ; try { const cloneOutput = await CommandUtil . sendCommandWithProgress ( command , path . dirname ( savePath . fsPath ) , `Cloning ${ this . repoName } repository` ) ; outputAdapter . log ( LogType . INFO , undefined , cloneOutput ) ; } catch ( error ) { outputAdapter . log ( LogType . ERROR , `Could not clone sample: ${ error . message }` ) ; return ; } const repositoryRegistry = RepositoryRegistry . instance ( ) ; if ( recloning ) { await repositoryRegistry . update ( { name : this . repoName , path : savePath . fsPath } ) ; } else { await repositoryRegistry . add ( { name : this . repoName , path : savePath . fsPath } ) ; } outputAdapter . log ( LogType . SUCCESS , `Successfully cloned repository!` ) ; } param repoName { String } param sampleName { String } param fileType { String } param fileName { String } param language { String } returns { void } private async openFile ( fileType , fileName , language ? ) { const repository = await this . getRepository ( this . repoName ) ; const sample = this . getSample ( repository , this . sampleName ) ; let samplePath ; let branch ; let workspaceLabel ; let onOpen ; if ( fileType === 'contracts' ) { const contract = this . getContract ( sample , fileName ) ; const foundLanguage = contract . languages . find ( ( _language ) => { if ( _language . type === language ) { samplePath = _language . remote . path ; branch = _language . remote . branch ; workspaceLabel = _language . workspaceLabel ; onOpen = _language . onOpen ; return true ; } } ) ; if ( ! foundLanguage ) { throw new Error ( 'Language type not supported' ) ; } } else if ( fileType === 'applications' ) { const download = this . getApplication ( sample , fileName , language ) ; samplePath = download . remote . path ; branch = download . remote . branch ; workspaceLabel = download . workspaceLabel ; onOpen = download . onOpen ; } else { throw new Error ( 'File type not supported' ) ; } let folderUri ; folderUri = await this . cloneAndOpenRepository ( samplePath , branch , workspaceLabel , onOpen ) ; if ( ! folderUri ) { return ; } } private async getLanguageVersion ( contractName , languageType ) { const repository = await this . getRepository ( this . repoName ) ; const sample = this . getSample ( repository , this . sampleName ) ; const contract = this . getContract ( sample , contractName ) ; const language = contract . languages . find ( ( lang ) => { return lang . type === languageType ; } ) ; const version = language . version ; return version ; } private getContract ( sample , contractName ) { const contracts = sample . category . contracts ; const contract = contracts . find ( ( cont ) => { return cont . name === contractName ; } ) ; return contract ; } private getApplication ( sample , applicationName , applicationLanguage ) { const applications = sample . category . applications ; const application = applications . find ( ( app ) => { return app . name === applicationName && app . language === applicationLanguage ; } ) ; return application ; } private async cloneAndOpenRepository ( samplePath , branch , workspaceLabel , onOpen ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; const repositoryRegistry = RepositoryRegistry . instance ( ) ; let repositoryData ; try { repositoryData = await repositoryRegistry . get ( this . repoName ) ; } catch ( error ) { outputAdapter . log ( LogType . ERROR , `The location of the cloned repository on the disk is unknown. Try re-cloning the sample repository.` ) ; return ; } const baseDirectory = repositoryData . path ; const directoryExists = await fs . pathExists ( baseDirectory ) ; if ( ! directoryExists ) { await repositoryRegistry . delete ( this . repoName ) ; outputAdapter . log ( LogType . ERROR , `The location of the file(s) you're trying to open is unknown. The sample repository has either been deleted or moved. Try re-cloning the sample repository.` ) ; return ; } else { shell . cd ( baseDirectory ) ; } const command = `git checkout -b ${ branch } origin/${ branch }` ; let fastFail = false ; try { await CommandUtil . sendCommand ( command ) ; } catch ( firstError ) { try { if ( firstError . message . includes ( 'already exists' ) ) { const checkoutCommand = `git checkout ${ branch }` ; await CommandUtil . sendCommand ( checkoutCommand ) ; } else { fastFail = true ; throw new Error ( `Could not retrieve file(s) from repository: ${ firstError . message }` ) ; } } catch ( secondError ) { if ( ! fastFail ) { throw new Error ( `Couldn't automatically checkout '${ branch }' branch. Please checkout branch manually. Error: ${ secondError . message }` ) ; } else { throw secondError ; } } } const folderPath = path . join ( baseDirectory , samplePath ) ; const folderUri = vscode . Uri . file ( folderPath ) ; if ( onOpen ) { for ( const item of onOpen ) { outputAdapter . log ( LogType . INFO , null , `Starting command "${ item . command }" with arguments "${ item . arguments }" for sample "${ this . sampleName }"` ) ; await CommandUtil . sendCommandWithOutputAndProgress ( item . command , item . arguments , item . message , folderUri . fsPath , null , outputAdapter ) ; outputAdapter . log ( LogType . INFO , null , `Finished command "${ item . command }" with arguments "${ item . arguments }" for sample "${ this . sampleName }"` ) ; } } await TimerUtil . sleep ( 500 ) ; const openMethod = await UserInputUtil . showFolderOptions ( 'Choose how to open the sample files' ) ; if ( ! openMethod ) { return ; } await UserInputUtil . openNewProject ( openMethod , folderUri , workspaceLabel ) ; return folderUri ; } }