/* libcmis * Version: MPL 1.1 / GPLv2+ / LGPLv2+ * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License or as specified alternatively below. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Major Contributor(s): * Copyright (C) 2011 SUSE * * * All Rights Reserved. * * For minor contributions see the git repository. * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPLv2+"), or * the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"), * in which case the provisions of the GPLv2+ or the LGPLv2+ are applicable * instead of those above. */ #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost::program_options; using libcmis::PropertyPtrMap; namespace { char* lcl_queryAuthCode( const char* url, const char* /*username*/, const char* /*password*/ ) { string code; cout << "Copy the following link to your browser and take the code: " << endl << endl << url << endl << endl; cout << "Enter the code:" << endl; cin >> code; return strdup( code.c_str() ); } class AuthCodeProvider { private: static string m_authCode; public: static void setAuthCode( string authCode ); static char* getAuthCode( const char* /*url*/, const char* /*username*/, const char* /*password*/ ); }; string AuthCodeProvider::m_authCode = string( ); void AuthCodeProvider::setAuthCode( string authCode ) { m_authCode = authCode; } char* AuthCodeProvider::getAuthCode( const char* /*url*/, const char* /*username*/, const char* /*password*/ ) { return strdup( m_authCode.c_str( ) ); } class CinAuthProvider : public libcmis::AuthProvider { private: string m_user; string m_pass; public: CinAuthProvider( ) : m_user( ), m_pass( ) { } ~CinAuthProvider( ) { } virtual bool authenticationQuery( string& username, string& password ); }; bool CinAuthProvider::authenticationQuery( string& username, string& password ) { bool cancelled = false; bool askUsername = username.empty(); if ( askUsername ) { if ( m_user.empty() ) { cout << "Username (empty to cancel): "; getline( cin, m_user ); } cancelled = m_user.empty(); username = m_user; } if ( !cancelled && ( askUsername || password.empty( ) ) ) { if ( m_pass.empty() ) { cout << "Password (empty to cancel): "; getline( cin, m_pass ); } cancelled = m_pass.empty(); password = m_pass; } return !cancelled; } class CinCertValidationHandler : public libcmis::CertValidationHandler { private: map< string, bool > m_answers; public: CinCertValidationHandler( ) : m_answers( ) { } ~CinCertValidationHandler( ) { } virtual bool validateCertificate( vector< string > certificates ) { if ( certificates.empty( ) ) return false; // Should never happen // Show the first certificate (even base64-encoded) string cert = certificates.front(); map< string, bool >::iterator it = m_answers.find( cert ); if ( it != m_answers.end( ) ) return it->second; cout << "Invalid SSL certificate:" << endl << cert << endl; cout << "'openssl x509 -noout -text' can show you the details of this certificate." << endl << endl; // Ask whether to validate cout << "Do you want to ignore this problem and go on? yes/no [default: no]: "; string answer; getline( cin, answer ); m_answers[cert] = answer == "yes"; return answer == "yes"; } }; } class CommandException : public exception { private: string m_msg; public: CommandException( string msg ) : m_msg( msg ) { } ~CommandException( ) noexcept { } CommandException( const CommandException& copy ) : m_msg( copy.m_msg ) { } CommandException& operator=( const CommandException& copy ) { if ( this != © ) m_msg = copy.m_msg; return *this; } virtual const char* what() const noexcept { return m_msg.c_str(); } }; class CmisClient { private: variables_map& m_vm; public: CmisClient( variables_map& vm ) : m_vm( vm ) { } libcmis::Session* getSession( bool inGetRepositories = false ); void execute( ); void printHelp( ); static options_description getOptionsDescription( ); private: map< int, string > getSessionParams(); map< string, string > getObjectProperties( ); }; map< string, string > CmisClient::getObjectProperties( ) { map< string, string > result; if ( m_vm.count( "object-property" ) > 0 ) { vector< string > params = m_vm["object-property"].as< vector< string > >( ); for ( vector< string >::iterator it = params.begin( ); it != params.end( ); ++it ) { size_t pos = it->find( "=" ); if ( pos != string::npos ) { string name = it->substr( 0, pos ); string value = it->substr( pos + 1 ); result.insert( pair< string, string >( name, value ) ); } } } return result; } libcmis::Session* CmisClient::getSession( bool inGetRepositories ) { if ( m_vm.count( "url" ) == 0 ) throw CommandException( "Missing binding URL" ); // Setup the authentication provider in case we have missing credentials libcmis::AuthProviderPtr provider( new CinAuthProvider( ) ); libcmis::SessionFactory::setAuthenticationProvider( provider ); libcmis::CertValidationHandlerPtr certValidator( new CinCertValidationHandler( ) ); libcmis::SessionFactory::setCertificateValidationHandler( certValidator ); string url = m_vm["url"].as(); // Look for the credentials string username; string password; if ( m_vm.count( "username" ) > 0 ) { username = m_vm["username"].as< string >(); if ( m_vm.count( "password" ) > 0 ) password = m_vm["password"].as< string >(); } // Look for proxy settings string proxyUrl; string proxyUser; string proxyPass; string noproxy; if ( m_vm.count( "proxy" ) > 0 ) { proxyUrl = m_vm["proxy"].as< string >(); if ( m_vm.count( "proxy-user" ) > 0 ) proxyUser = m_vm["proxy-user"].as< string >(); if ( m_vm.count( "proxy-password" ) > 0 ) proxyPass = m_vm["proxy-password"].as< string >(); if ( m_vm.count( "noproxy" ) > 0 ) noproxy = m_vm["noproxy"].as< string >(); libcmis::SessionFactory::setProxySettings( proxyUrl, noproxy, proxyUser, proxyPass ); } bool verbose = m_vm.count( "verbose" ) > 0; libcmis::Session* session = NULL; string repoId; // The repository ID is needed to initiate a session if ( m_vm.count( "repository" ) == 0 && !inGetRepositories ) { // Do we have a single repository on the server? session = getSession( true ); if ( session != NULL ) { vector< libcmis::RepositoryPtr > repos = session->getRepositories(); if ( repos.size() == 1 ) { repoId = repos.front( )->getId( ); session->setRepository( repoId ); } } // We couldn't auto-guess the repository, then throw an error if ( repoId.empty( ) ) { delete session; throw CommandException( "Missing repository ID" ); } } else if ( m_vm.count( "repository" ) > 0 ) { repoId = m_vm["repository"].as< string >(); } if ( session == NULL ) { bool noSslCheck = m_vm.count( "no-ssl-check" ) > 0; // Should we use OAuth2? string oauth2ClientId; string oauth2ClientSecret; string oauth2AuthUrl; string oauth2TokenUrl; string oauth2RedirectUri; string oauth2Scope; if ( m_vm.count( "oauth2-client-id" ) > 0 ) oauth2ClientId = m_vm["oauth2-client-id"].as< string >(); if ( m_vm.count( "oauth2-client-secret" ) > 0 ) oauth2ClientSecret = m_vm["oauth2-client-secret"].as< string >(); if ( m_vm.count( "oauth2-auth-url" ) > 0 ) oauth2AuthUrl = m_vm["oauth2-auth-url"].as< string >(); if ( m_vm.count( "oauth2-token-url" ) > 0 ) oauth2TokenUrl = m_vm["oauth2-token-url"].as< string >(); if ( m_vm.count( "oauth2-redirect-uri" ) > 0 ) oauth2RedirectUri = m_vm["oauth2-redirect-uri"].as< string >(); if ( m_vm.count( "oauth2-scope" ) > 0 ) oauth2Scope = m_vm["oauth2-scope"].as< string >(); if ( m_vm.count( "oauth2-auth-code" ) > 0 ) AuthCodeProvider::setAuthCode( m_vm["oauth2-auth-code"].as< string >() ); libcmis::OAuth2DataPtr oauth2Data( new libcmis::OAuth2Data( oauth2AuthUrl, oauth2TokenUrl, oauth2Scope, oauth2RedirectUri, oauth2ClientId, oauth2ClientSecret) ); if ( oauth2Data->isComplete( ) ) { // Set the fallback AuthCode provider if ( m_vm.count( "oauth2-auth-code" ) > 0 ) { libcmis::SessionFactory::setOAuth2AuthCodeProvider( AuthCodeProvider::getAuthCode ); } else { libcmis::SessionFactory::setOAuth2AuthCodeProvider( lcl_queryAuthCode ); } } else { oauth2Data.reset( ); } session = libcmis::SessionFactory::createSession( url, username, password, repoId, noSslCheck, oauth2Data, verbose ); } return session; } void CmisClient::execute( ) { if ( ( m_vm.count( "help" ) > 0 ) || m_vm.count( "command" ) != 1 ) { printHelp(); return; } if ( m_vm.count( "command" ) == 1 ) { string command = m_vm["command"].as(); if ( "list-repos" == command ) { libcmis::Session* session = getSession( true ); if ( session != NULL ) { vector< libcmis::RepositoryPtr > repos = session->getRepositories(); cout << "Repositories: name (id)" << endl; for ( vector< libcmis::RepositoryPtr >::iterator it = repos.begin(); it != repos.end(); ++it ) cout << "\t" << ( *it )->getName( ) << " (" << ( *it )->getId( ) << ")" << endl; } else { cerr << "Couldn't create a session for some reason" << endl; } } else if ( "show-root" == command ) { unique_ptr session( getSession( ) ); libcmis::FolderPtr root = session->getRootFolder(); if ( root.get() ) { cout << "------------------------------------------------" << endl; cout << root->toString() << endl; } } else if ( "repo-infos" == command ) { unique_ptr session( getSession( ) ); libcmis::RepositoryPtr repo = session->getRepository( ); if ( repo ) { cout << "------------------------------------------------" << endl; cout << repo->toString() << endl; } else throw CommandException( "Please select a repository" ); } else if ( "type-by-id" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the types to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node ids to show as command args" ); vector< string > ids = m_vm["args"].as< vector< string > >( ); for ( vector< string >::iterator it = ids.begin(); it != ids.end(); ++it ) { cout << "------------------------------------------------" << endl; try { libcmis::ObjectTypePtr type = session->getType( *it ); cout << type->toString() << endl; } catch ( const libcmis::Exception& e ) { cout << e.what() << endl; } } } else if ( "show-by-id" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node ids to show as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); for ( vector< string >::iterator it = objIds.begin(); it != objIds.end(); ++it ) { libcmis::ObjectPtr cmisObj = session->getObject( *it ); cout << "------------------------------------------------" << endl; if ( cmisObj.get() ) cout << cmisObj->toString() << endl; else cout << "No such node: " << *it << endl; } } else if ( "show-by-path" == command ) { unique_ptr session( getSession( ) ); // Get the paths of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node paths to show as command args" ); vector< string > objPaths = m_vm["args"].as< vector< string > >( ); for ( vector< string >::iterator it = objPaths.begin(); it != objPaths.end(); ++it ) { libcmis::ObjectPtr cmisObj = session->getObjectByPath( *it ); cout << "------------------------------------------------" << endl; if ( cmisObj.get() ) cout << cmisObj->toString() << endl; else cout << "No such node: " << *it << endl; } } else if ( "get-content" == command ) { unique_ptr session( getSession( ) ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); if ( objIds.empty( ) ) throw CommandException( "Please provide a content object Id" ); libcmis::ObjectPtr cmisObj = session->getObject( objIds.front() ); libcmis::Document* document = dynamic_cast< libcmis::Document* >( cmisObj.get() ); if ( NULL != document ) { // TODO Handle name clashes string streamId; if ( m_vm.count( "stream-id" ) > 0 ) streamId = m_vm["stream-id"].as(); boost::shared_ptr< istream > in = document->getContentStream( streamId ); ofstream out( document->getContentFilename().c_str() ); out << in->rdbuf(); out.close(); } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else if ( "set-content" == command ) { unique_ptr session( getSession( ) ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); if ( objIds.empty( ) ) throw CommandException( "Please provide a content object Id" ); libcmis::ObjectPtr cmisObj = session->getObject( objIds.front() ); libcmis::Document* document = dynamic_cast< libcmis::Document* >( cmisObj.get() ); if ( NULL != document ) { if ( m_vm.count( "input-file" ) == 0 ) throw CommandException( "Missing --input-file" ); if ( m_vm.count( "input-type" ) == 0 ) throw CommandException( "Missing --input-type" ); string type = m_vm["input-type"].as(); string filename; if ( m_vm.count( "input-name" ) > 0 ) filename = m_vm["input-name"].as(); string file = m_vm["input-file"].as(); ifstream is( file.c_str(), ifstream::in ); boost::shared_ptr< ostream > os ( new ostream ( is.rdbuf( ) ) ); if ( is.fail( ) ) throw CommandException( string( "Unable to open file " ) + file ); document->setContentStream( os, type, filename ); is.close( ); } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else if ( "create-folder" == command ) { unique_ptr session( getSession( ) ); vector< string > args = m_vm["args"].as< vector< string > >( ); if ( args.size() < 2 ) throw CommandException( "Please provide a parent Id and folder name" ); libcmis::FolderPtr parent = session->getFolder( args[0] ); // Get the folder type to create string folderType( "cmis:folder" ); if ( m_vm.count( "object-type" ) != 0 ) folderType = m_vm["object-type"].as( ); libcmis::ObjectTypePtr type = session->getType( folderType ); if ( "cmis:folder" != type->getBaseType( )->getId( ) ) throw CommandException( string( "Not a folder type: " ) + folderType ); PropertyPtrMap properties; map< string, libcmis::PropertyTypePtr >& propertiesTypes = type->getPropertiesTypes( ); // Set the name map< string, libcmis::PropertyTypePtr >::iterator typeIt = propertiesTypes.find( string( "cmis:name" ) ); if ( typeIt == propertiesTypes.end( ) ) throw CommandException( string( "No cmis:name on the object type... weird" ) ); vector< string > nameValues; string newFolderName = args[1]; for ( unsigned int i = 2; i < args.size( ); i++ ) { newFolderName += " " + args[i]; } nameValues.push_back( newFolderName ); libcmis::PropertyPtr nameProperty( new libcmis::Property( typeIt->second, nameValues ) ); properties.insert( pair< string, libcmis::PropertyPtr >( string( "cmis:name" ), nameProperty ) ); // Set the objectTypeId typeIt = propertiesTypes.find( string( "cmis:objectTypeId" ) ); if ( typeIt == propertiesTypes.end( ) ) throw CommandException( string( "No cmis:objectTypeId on the object type... weird" ) ); vector< string > typeIdValues; typeIdValues.push_back( folderType ); libcmis::PropertyPtr typeIdProperty( new libcmis::Property( typeIt->second, typeIdValues ) ); properties.insert( pair< string, libcmis::PropertyPtr >( string( "cmis:objectTypeId" ), typeIdProperty ) ); // Checks for the properties to set if any map< string, string > propsToSet = getObjectProperties( ); for ( map< string, string >::iterator it = propsToSet.begin(); it != propsToSet.end(); ++it ) { // Create the CMIS property if it exists typeIt = propertiesTypes.find( it->first ); if ( typeIt != propertiesTypes.end( ) ) { vector< string > values; values.push_back( it->second ); libcmis::PropertyPtr cmisProperty( new libcmis::Property( typeIt->second, values ) ); properties.insert( pair< string, libcmis::PropertyPtr >( it->first, cmisProperty ) ); } } libcmis::FolderPtr created = parent->createFolder( properties ); cout << "------------------------------------------------" << endl; cout << created->toString() << endl; } else if ( "create-document" == command ) { unique_ptr session( getSession( ) ); vector< string > args = m_vm["args"].as< vector< string > >( ); if ( args.size() < 2 ) throw CommandException( "Please provide a parent Id and document name" ); libcmis::FolderPtr parent = session->getFolder( args[0] ); // Get the document type to create string documentType( "cmis:document" ); if ( m_vm.count( "object-type" ) != 0 ) documentType = m_vm["object-type"].as( ); libcmis::ObjectTypePtr type = session->getType( documentType ); if ( "cmis:document" != type->getBaseType( )->getId( ) ) throw CommandException( string( "Not a document type: " ) + documentType ); PropertyPtrMap properties; map< string, libcmis::PropertyTypePtr >& propertiesTypes = type->getPropertiesTypes( ); // Set the name map< string, libcmis::PropertyTypePtr >::iterator typeIt = propertiesTypes.find( string( "cmis:name" ) ); if ( typeIt == propertiesTypes.end( ) ) throw CommandException( string( "No cmis:name on the object type... weird" ) ); vector< string > nameValues; string newDocumentName = args[1]; for ( unsigned int i = 2; i < args.size( ); i++ ) { newDocumentName += " " + args[i]; } nameValues.push_back( newDocumentName ); libcmis::PropertyPtr nameProperty( new libcmis::Property( typeIt->second, nameValues ) ); properties.insert( pair< string, libcmis::PropertyPtr >( string( "cmis:name" ), nameProperty ) ); // Set the objectTypeId typeIt = propertiesTypes.find( string( "cmis:objectTypeId" ) ); if ( typeIt == propertiesTypes.end( ) ) throw CommandException( string( "No cmis:objectTypeId on the object type... weird" ) ); vector< string > typeIdValues; typeIdValues.push_back( documentType ); libcmis::PropertyPtr typeIdProperty( new libcmis::Property( typeIt->second, typeIdValues ) ); properties.insert( pair< string, libcmis::PropertyPtr >( string( "cmis:objectTypeId" ), typeIdProperty ) ); // Checks for the properties to set if any map< string, string > propsToSet = getObjectProperties( ); for ( map< string, string >::iterator it = propsToSet.begin(); it != propsToSet.end(); ++it ) { // Create the CMIS property if it exists typeIt = propertiesTypes.find( it->first ); if ( typeIt != propertiesTypes.end( ) ) { vector< string > values; values.push_back( it->second ); libcmis::PropertyPtr cmisProperty( new libcmis::Property( typeIt->second, values ) ); properties.insert( pair< string, libcmis::PropertyPtr >( it->first, cmisProperty ) ); } } // Get the content type and stream boost::shared_ptr< ostream > contentStream; string contentType; string filename; bool hasInputFile = m_vm.count( "input-file" ) != 0; bool hasInputType = m_vm.count( "input-type" ) != 0; bool hasInputName = m_vm.count( "input-name" ) != 0; if ( hasInputType && !hasInputFile ) throw CommandException( "Missing --input-file" ); if ( hasInputFile && !hasInputType ) throw CommandException( "Missing --input-type" ); if ( hasInputFile && hasInputType ) { contentType = m_vm["input-type"].as(); string file = m_vm["input-file"].as(); fstream is( file.c_str() ); if ( is.fail( ) ) throw CommandException( string( "Unable to open file " ) + file ); contentStream.reset( new ostringstream( ios_base::out | ios_base::in ) ); *contentStream << is.rdbuf(); } if ( hasInputName ) filename = m_vm[ "input-name" ].as< string >( ); // Actually create the document libcmis::DocumentPtr created = parent->createDocument( properties, contentStream, contentType, filename ); cout << "------------------------------------------------" << endl; cout << created->toString() << endl; } else if ( "update-object" == command ) { unique_ptr session( getSession( ) ); vector< string > args = m_vm["args"].as< vector< string > >( ); if ( args.size() != 1 ) throw CommandException( "Please provide an object id" ); libcmis::ObjectPtr object = session->getObject( args[0] ); libcmis::ObjectTypePtr type = session->getType( object->getType( ) ); map< string, libcmis::PropertyTypePtr >& propertiesTypes = type->getPropertiesTypes( ); PropertyPtrMap properties; // Checks for the properties to set if any map< string, string > propsToSet = getObjectProperties( ); for ( map< string, string >::iterator it = propsToSet.begin(); it != propsToSet.end(); ++it ) { // Create the CMIS property if it exists map< string, libcmis::PropertyTypePtr >::iterator typeIt = propertiesTypes.find( it->first ); if ( typeIt != propertiesTypes.end( ) && typeIt->second->isUpdatable( ) ) { vector< string > values; values.push_back( it->second ); libcmis::PropertyPtr property( new libcmis::Property( typeIt->second, values ) ); properties[ it->first ] = property; } } libcmis::ObjectPtr updated = object->updateProperties( properties ); cout << "------------------------------------------------" << endl; // Output updated instead of object as it may be different depending on the server cout << updated->toString() << endl; } else if ( "move-object" == command ) { unique_ptr session( getSession( ) ); vector< string > args = m_vm["args"].as< vector< string > > ( ); if ( args.size() != 3 ) throw CommandException( "Please provide an object id and source and destination folder ids" ); string& objId = args[0]; string& srcId = args[1]; string& dstId = args[2]; libcmis::ObjectPtr obj = session->getObject( objId ); libcmis::ObjectPtr src = session->getObject( srcId ); libcmis::FolderPtr srcFolder = boost::dynamic_pointer_cast< libcmis::Folder > ( src ); if ( !srcFolder ) throw CommandException( "Source object is not a folder" ); libcmis::ObjectPtr dst = session->getObject( dstId ); libcmis::FolderPtr dstFolder = boost::dynamic_pointer_cast< libcmis::Folder > ( dst ); if ( !dstFolder ) throw CommandException( "Destinaton object is not a folder" ); obj->move( srcFolder, dstFolder ); cout << "------------------------------------------------" << endl; cout << obj->toString( ) << endl; } else if ( "delete" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node ids to delete as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); vector< string > errors; for ( vector< string >::iterator it = objIds.begin(); it != objIds.end(); ++it ) { libcmis::ObjectPtr cmisObj = session->getObject( *it ); libcmis::Folder* folder = dynamic_cast< libcmis::Folder* >( cmisObj.get() ); if ( NULL != folder ) { try { vector< string > failed = folder->removeTree( ); string error; for ( vector< string >::iterator dumpIt = failed.begin( ); dumpIt != failed.end( ); ++dumpIt ) { if ( dumpIt == failed.begin( ) ) error += "Failed to remove children nodes: "; else error += ", "; error = *dumpIt; } if ( !error.empty( ) ) errors.push_back( error ); } catch ( const libcmis::Exception& e ) { string msg = *it + ": " + e.what( ); errors.push_back( msg ); } } else if ( cmisObj.get() ) { try { cmisObj->remove( ); } catch ( const libcmis::Exception& e ) { string msg = *it + ": " + e.what( ); errors.push_back( msg ); } } else { string msg = "No such node: " + *it; errors.push_back( msg ); } } // Show the errors if ( !errors.empty( ) ) { cout << "Errors:" << endl; for ( vector< string >::iterator it = errors.begin( ); it != errors.end( ); ++it ) { cout << "\t" << *it << endl; } } else { cout << "All nodes have been removed" << endl; } } else if ( "checkout" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node id to checkout as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); libcmis::ObjectPtr cmisObj = session->getObject( objIds.front() ); if ( cmisObj.get() ) { libcmis::Document* doc = dynamic_cast< libcmis::Document* >( cmisObj.get() ); if ( NULL != doc ) { libcmis::DocumentPtr pwc = doc->checkOut( ); if ( pwc.get( ) ) { cout << "------------------------------------------------" << endl; cout << pwc->toString() << endl; } else cout << "No Private Working Copy returned?" << endl; } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else cout << "No such node: " << objIds.front() << endl; } else if ( "cancel-checkout" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the private working copy object id to cancel as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); libcmis::ObjectPtr cmisObj = session->getObject( objIds.front() ); cout << "------------------------------------------------" << endl; if ( cmisObj.get() ) { libcmis::Document* doc = dynamic_cast< libcmis::Document* >( cmisObj.get() ); if ( NULL != doc ) { doc->cancelCheckout( ); cout << "Checkout cancelled" << endl; } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else cout << "No such node: " << objIds.front() << endl; } else if ( "checkin" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node id to checkin as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); libcmis::ObjectPtr object = session->getObject( objIds.front() ); if ( object.get() ) { // Create the properties map PropertyPtrMap properties; map< string, string > propsToSet = getObjectProperties( ); libcmis::ObjectTypePtr type = session->getType( object->getType( ) ); map< string, libcmis::PropertyTypePtr > propertyTypes = type->getPropertiesTypes( ); for ( map< string, string >::iterator propIt = propsToSet.begin(); propIt != propsToSet.end(); ++propIt ) { string name = propIt->first; map< string, libcmis::PropertyTypePtr >::iterator typeIt = propertyTypes.find( name ); if ( typeIt != propertyTypes.end( ) ) { vector< string > values; values.push_back( propIt->second ); libcmis::PropertyPtr prop( new libcmis::Property( typeIt->second, values ) ); properties.insert( pair< string, libcmis::PropertyPtr >( name, prop ) ); } } // Get the content stream if any string contentType; string filename; boost::shared_ptr< ostream > stream; if ( m_vm.count( "input-file" ) > 0 ) { string file = m_vm["input-file"].as(); ifstream is( file.c_str(), ios_base::in ); stringstream os; os << is.rdbuf( ); if ( is.fail( ) ) throw CommandException( string( "Unable to open file " ) + file ); string content = os.str( ); stream.reset( new stringstream( content ) ); is.close( ); if ( m_vm.count( "input-type" ) > 0 ) { contentType = m_vm["input-type"].as(); } if ( m_vm.count( "input-name" ) > 0 ) { filename = m_vm["input-name"].as(); } } bool major = false; if ( m_vm.count( "major" ) > 0 ) major = "yes"; string comment; if ( m_vm.count( "message" ) > 0 ) comment = m_vm["message"].as< string >( ); libcmis::Document* doc = dynamic_cast< libcmis::Document* >( object.get() ); if ( NULL != doc ) { libcmis::DocumentPtr newDoc = doc->checkIn( major, comment, properties, stream, contentType, filename ); cout << "------------------------------------------------" << endl; cout << newDoc->toString() << endl; } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else cout << "No such node: " << objIds.front() << endl; } else if ( "get-versions" == command ) { unique_ptr session( getSession( ) ); // Get the ids of the objects to fetch if ( m_vm.count( "args" ) == 0 ) throw CommandException( "Please provide the node id to get versions from as command args" ); vector< string > objIds = m_vm["args"].as< vector< string > >( ); libcmis::ObjectPtr object = session->getObject( objIds.front() ); if ( object.get() ) { libcmis::Document* doc = dynamic_cast< libcmis::Document* >( object.get() ); if ( NULL != doc ) { vector< libcmis::DocumentPtr > versions = doc->getAllVersions( ); for ( vector< libcmis::DocumentPtr >::iterator it = versions.begin( ); it != versions.end( ); ++it ) { cout << "------------------------------------------------" << endl; cout << ( *it )->toString() << endl; } } else throw CommandException( string( "Not a document object id: " ) + objIds.front() ); } else cout << "No such node: " << objIds.front() << endl; } else if ( "help" == command ) { printHelp(); } else { cerr << "------------------------------------------------" << endl; cerr << "ERROR: Unknown command: " << command << endl; cerr << "------------------------------------------------" << endl; printHelp( ); } // TODO Add some more useful commands here } } options_description CmisClient::getOptionsDescription( ) { options_description desc( "Allowed options" ); desc.add_options( ) ( "help", "Produce help message and exists" ) ( "verbose,v", "Show loads of useful messages for debugging" ) ( "url", value< string >(), "URL of the binding of the server" ) ( "repository,r", value< string >(), "Name of the repository to use" ) ( "username,u", value< string >(), "Username used to authenticate to the repository" ) ( "password,p", value< string >(), "Password used to authenticate to the repository" ) ( "no-ssl-check", "Disable the verification of SSL certificates. This may come handy" "for self-signed certificates for example, though it lowers the security" ) ( "proxy", value< string >(), "HTTP proxy url to override the system settings" ) ( "noproxy", value< string >(), "Coma separated list if host and domain names not going" "through the proxy" ) ( "proxy-username", value< string >(), "Username to authenticate on the proxy" ) ( "proxy-password", value< string >(), "Password to authenticate on the proxy" ) ( "oauth2-client-id", value< string >(), "OAuth2 application client_id" ) ( "oauth2-client-secret", value< string >(), "OAuth2 application client_secret" ) ( "oauth2-auth-url", value< string >(), "URL to authenticate in the OAuth2 flow" ) ( "oauth2-token-url", value< string >(), "URL to convert code to tokens in the OAuth2 flow" ) ( "oauth2-redirect-uri", value< string >(), "redirect URI indicating that the authentication is finished in OAuth2 flow" ) ( "oauth2-scope", value< string >(), "The authentication scope in OAuth2" ) ( "oauth2-auth-code", value< string >(), "The authentication code required to get the access token" ) ; options_description setcontentOpts( "modification operations options" ); setcontentOpts.add_options( ) ( "input-file", value< string >(), "File to push to the repository" ) ( "input-type", value< string >(), "Mime type of the file to push to the repository" ) ( "input-name", value< string >(), "Name of the file to push to the repository" "(may be different from the local name)" ) ( "object-type", value< string >(), "CMIS type of the object to create" ) ( "object-property", value< vector< string > >(), "under the form prop-id=prop-value, defines a property" "to be set on the object" ) ( "message,m", value< string >(), "Check in message" ) ( "major", "The version to create during the check in will be a major version." ) ( "stream-id", value< string >(), "streamId of the rendition to get content." ) ; desc.add( setcontentOpts ); return desc; } void CmisClient::printHelp( ) { cerr << "cmis-client [options] [command] arguments" << endl; cerr << endl << "Commands" << endl; cerr << " list-repos\n" " Lists the repositories available on the server" << endl; cerr << " repo-infos\n" " Show the informations and capabilities of the selected repository" << endl; cerr << " show-root\n" " Dump the root node of the repository." << endl; cerr << " type-by-id [... ]\n" " Dumps the type informations for all the ids." << endl; cerr << " show-by-id [... ]\n" " Dumps the objects informations for all the ids." << endl; cerr << " show-by-path [... ]\n" " Dumps the objects informations for all the paths." << endl; cerr << " get-content \n" " Saves the stream of the content object in the\n" " current folder. Any existing file is overwritten.\n" " streamId can be used to get the desired rendition with --stream-id"<< endl; cerr << " set-content \n" " Replaces the stream of the content object by the\n" " file selected with --input-file." << endl; cerr << " create-folder \n" " Creates a new folder inside the folder named ." << endl; cerr << " create-document \n" " Creates a new document inside the folder \n" " named .\n" " Note that --input-file and --input-type may be requested if\n" " the server requires a content stream." << endl; cerr << " update-object \n" " Update the object matching id with the properties\n" " defined with --object-property." << endl; cerr << " move-object \n" " Move the object matching id from the\n" " folder to the folder ." << endl; cerr << " delete [... ]\n" " Delete the objects corresponding to the ids. If the node\n" " is a folder, its content will be removed as well." << endl; cerr << " checkout \n" " Check out the document corresponding to the id and shows the\n" " Private Working Copy document infos." << endl; cerr << " cancel-checkout \n" " Cancel the Private Working Copy corresponding to the id" << endl; cerr << " checkin \n" " Check in the Private Working copy corresponding to the id.\n" " Use the --message and --major parameters to give more\n" " details about the new version.\n" " The modification options may be needed to set the new\n" " version properties and content stream if the repository\n" " doesn't allow to change the private working copies." << endl; cerr << " get-versions \n" " Show all the versions of a document." << endl; cerr << " help\n" " Prints this help message and exits (like --help option)." << endl; cerr << endl << getOptionsDescription() << endl; } int main ( int argc, char* argv[] ) { options_description hidden( "Hidden options" ); hidden.add_options( ) ( "command", value< string >(), "Command" ) ( "args", value< vector< string > >(), "Arguments for the command" ) ; options_description allOptions = CmisClient::getOptionsDescription( ); allOptions.add( hidden ); positional_options_description pd; pd.add( "command", 1 ); pd.add( "args", -1 ); variables_map vm; store( command_line_parser( argc, argv ).options( allOptions ).positional( pd ).run( ), vm ); notify( vm ); CmisClient client( vm ); try { client.execute( ); } catch ( const CommandException& e ) { cerr << "------------------------------------------------" << endl; cerr << "ERROR: " << e.what() << endl; cerr << "------------------------------------------------" << endl; client.printHelp(); return 1; } catch ( const libcmis::Exception& e ) { cerr << "------------------------------------------------" << endl; cerr << "ERROR: " << e.what() << endl; cerr << "------------------------------------------------" << endl; return 1; } catch ( const exception& e ) { cerr << "------------------------------------------------" << endl; cerr << "ERROR: " << e.what() << endl; cerr << "------------------------------------------------" << endl; return 1; } return 0; }