import { Inject , Injectable } from '@angular/core' ; import * as path from 'path' ; import { from , Observable , of , throwError , zip } from 'rxjs' ; import { catchError , filter , map , mapTo , switchMap , tap } from 'rxjs/operators' ; import { GitError , GitErrorCodes , GitGetHistoryOptions , GitSyncWithRemoteResult } from '../../core/git' ; import { VcsAccount , VcsAuthenticationInfo , VcsCommitItem , VcsFileChange , VcsFileChangeStatusTypes , VcsRemoteRepository , } from '../../core/vcs' ; import { toPromise } from '../../libs/rx' ; import { FsService , GitService , WorkspaceService } from '../shared' ; import { VCS_ACCOUNT_DATABASE , VcsAccountDatabase } from './vcs-account-database' ; import { VcsRemoteProvider , VcsRemoteProviderFactory , VcsRemoteProviderType } from './vcs-remote' ; export class VcsRemoteLoginOption { /** If its set, authentication info will not stores in database. */ instanceLogin ? : boolean = false ; } export class VcsCloneRepositoryOption { useAuthenticationInfoInStoreIfExists ? : boolean = true ; } @ Injectable ( ) export class VcsService { private _commitHistoryFetchingSize : number = 50 ; get commitHistoryFetchingSize ( ) : number { return this . _commitHistoryFetchingSize ; } _removeProvider : VcsRemoteProvider | null = null ; private nextCommitHistoryFetchingOptions : GitGetHistoryOptions | null = null ; constructor ( private remoteProviderFactory , private git , @ Inject ( VCS_ACCOUNT_DATABASE ) private accountDB , private workspace , private fs ) { } setRemoveProvider ( type ) { this . _removeProvider = this . remoteProviderFactory . create ( type ) ; return this ; } setCommitHistoryFetchingSize ( size ) { this . _commitHistoryFetchingSize = size ; return this ; } async isAuthenticationInfoExists ( ) { try { const count = await this . accountDB . accounts . count ( ) ; return count > 0 ; } catch ( error ) { return false ; } } isRemoteRepositoryUrlValid ( url ) { this . checkIfRemoteProviderIsProvided ( ) ; return this . _removeProvider . isRepositoryUrlValid ( url ) ; } loginRemoteWithBasicAuthorization ( username , password , option ? ) { this . checkIfRemoteProviderIsProvided ( ) ; const opt = { ... new VcsRemoteLoginOption ( ) , ... option , } as VcsRemoteLoginOption ; if ( opt . instanceLogin ) { return this . _removeProvider . authorizeByBasic ( username , password ) ; } else { return this . _removeProvider . authorizeByBasic ( username , password ) . pipe ( switchMap ( account => this . storeAccountInfo ( account ) . pipe ( mapTo ( account ) ) ) ) ; } } loginRemoteWithOauth2TokenAuthorization ( token , option ? ) { this . checkIfRemoteProviderIsProvided ( ) ; const opt = { ... new VcsRemoteLoginOption ( ) , ... option , } as VcsRemoteLoginOption ; if ( opt . instanceLogin ) { return this . _removeProvider . authorizeByOauth2Token ( token ) ; } else { return this . _removeProvider . authorizeByOauth2Token ( token ) . pipe ( switchMap ( account => this . storeAccountInfo ( account ) . pipe ( mapTo ( account ) ) ) ) ; } } getPrimaryEmailFromRemote ( account ) { this . checkIfRemoteProviderIsProvided ( ) ; return this . _removeProvider . getPrimaryEmail ( account . authentication ) ; } fetchFileChanges ( ) { return this . git . getFileChanges ( this . workspace . configs . rootDirPath ) ; } fetchCommitHistory ( ) { this . nextCommitHistoryFetchingOptions = null ; return this . git . getCommitHistory ( { workspaceDirPath : this . workspace . configs . rootDirPath , size : this . _commitHistoryFetchingSize , } ) . pipe ( tap ( result => this . nextCommitHistoryFetchingOptions = result . next ) , map ( result => result . history ) ) ; } fetchMoreCommitHistory ( ) { if ( ! this . nextCommitHistoryFetchingOptions ) { return of ( [ ] ) ; } return this . git . getCommitHistory ( this . nextCommitHistoryFetchingOptions ) . pipe ( tap ( result => this . nextCommitHistoryFetchingOptions = result . next ) , map ( result => result . history ) ) ; } async keepDirectory ( directoryPath ) { const workspaceDirPath = this . workspace . configs . rootDirPath ; const keepFilePath = path . resolve ( directoryPath , '.gitkeep' ) ; if ( await toPromise ( this . fs . isPathExists ( keepFilePath ) ) ) { return ; } try { await toPromise ( this . fs . writeFile ( keepFilePath , '' ) ) ; await toPromise ( this . git . commit ( workspaceDirPath , { name : 'Geeks Diary' , email : '(BLANK)' , authentication : null , } , { summary : 'Keep Directory' , description : '' } , [ { status : VcsFileChangeStatusTypes . NEW , filePath : path . relative ( workspaceDirPath , keepFilePath ) , absoluteFilePath : keepFilePath , workingDirectoryPath : workspaceDirPath , } ] ) ) ; } catch ( error ) { await toPromise ( this . fs . removeFile ( keepFilePath ) ) ; throw error ; } } cloneRepository ( sourceUrl , distLocalPath , option ? ) { const opt = { ... new VcsCloneRepositoryOption ( ) , ... option , } as VcsCloneRepositoryOption ; if ( opt . useAuthenticationInfoInStoreIfExists ) { return from ( this . accountDB . accounts . toCollection ( ) . last ( ) ) . pipe ( switchMap ( account => this . git . cloneRepository ( sourceUrl , distLocalPath , account ? account . authentication : undefined ) ) ) ; } else { return this . git . cloneRepository ( sourceUrl , distLocalPath ) ; } } async canSyncRepository ( ) { const fetchAccount = await this . accountDB . getRepositoryFetchAccount ( ) ; const isRemoteExists = await toPromise ( this . git . isRemoteExists ( { workspaceDirPath : this . workspace . configs . rootDirPath , remoteName : 'origin' , } ) ) ; return ( fetchAccount !== undefined ) && isRemoteExists ; } getRepositoryFetchAccount ( ) { return from ( this . accountDB . getRepositoryFetchAccount ( ) ) . pipe ( map ( fetchAccount => fetchAccount ? fetchAccount : null ) ) ; } getRemoteRepositoryUrl ( ) { return this . git . getRemoteUrl ( { workspaceDirPath : this . workspace . configs . rootDirPath , remoteName : 'origin' , } ) . pipe ( catchError ( ( error ) => { if ( ( error as GitError ) . code === GitErrorCodes . REMOTE_NOT_FOUND ) { return of ( null ) ; } else { return throwError ( error ) ; } } ) ) ; } findRemoteRepository ( remoteUrl , authentication ? ) { this . checkIfRemoteProviderIsProvided ( ) ; return this . _removeProvider . findRepository ( remoteUrl , authentication ) ; } setRemoteRepository ( fetchAccount , remoteUrl ) { return zip ( from ( this . accountDB . setRepositoryFetchAccountAs ( fetchAccount ) ) , this . git . setRemote ( { workspaceDirPath : this . workspace . configs . rootDirPath , remoteName : 'origin' , remoteUrl , } ) ) . pipe ( mapTo ( null ) ) ; } syncRepository ( ) { return from ( this . accountDB . getRepositoryFetchAccount ( ) ) . pipe ( filter ( fetchAccount => fetchAccount !== undefined ) , switchMap ( fetchAccount => this . git . syncWithRemote ( { workspaceDirPath : this . workspace . configs . rootDirPath , remoteName : 'origin' , authentication : fetchAccount . authentication , author : fetchAccount , } ) ) ) ; } private checkIfRemoteProviderIsProvided ( ) { if ( ! this . _removeProvider ) { throw new Error ( 'No vcs remote provider!' ) ; } } private storeAccountInfo ( authInfo ) { return from ( this . accountDB . accounts . add ( authInfo ) ) ; } }