/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2013 KiCad Developers, see CHANGELOG.TXT for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /* This is a pcbnew PLUGIN which supports some of the PLUGIN::Footprint*() functions in the PLUGIN interface, and could do so by utilizing the version 3 github.com API documented here: http://developer.github.com https://help.github.com/articles/creating-an-access-token-for-command-line-use but it does not. Rather it simply reads in a zip file of the repo and unzips it from RAM as needed. Therefore the PLUGIN is read only for accessing remote pretty libraries. If you want to support writing to the repo, then you could use the above API. */ #include #include #include // chinese SSL magic #include #include #include #include #include #include #include #include #include #include using namespace std; typedef boost::ptr_map MODULE_MAP; typedef MODULE_MAP::iterator MODULE_ITER; typedef MODULE_MAP::const_iterator MODULE_CITER; /** * Class FPL_CACHE * assists only within GITHUB_PLUGIN and hold a map of footprint name to wxZipEntry */ struct FPL_CACHE : public MODULE_MAP { // MODULE_MAP is a boost::ptr_map template, made into a class hereby. }; GITHUB_PLUGIN::GITHUB_PLUGIN() : m_cache( 0 ) { } GITHUB_PLUGIN::~GITHUB_PLUGIN() { delete m_cache; } const wxString& GITHUB_PLUGIN::PluginName() const { static wxString name( wxT( "Github" ) ); return name; } const wxString& GITHUB_PLUGIN::GetFileExtension() const { static wxString empty_ext; return empty_ext; } wxArrayString GITHUB_PLUGIN::FootprintEnumerate( const wxString& aLibraryPath, PROPERTIES* aProperties ) { cacheLib( aLibraryPath ); wxArrayString ret; for( MODULE_ITER it = m_cache->begin(); it!=m_cache->end(); ++it ) { ret.Add( FROM_UTF8( it->first.c_str() ) ); } return ret; } MODULE* GITHUB_PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, PROPERTIES* aProperties ) { cacheLib( aLibraryPath ); string fp_name = TO_UTF8( aFootprintName ); MODULE_CITER it = m_cache->find( fp_name ); if( it != m_cache->end() ) // fp_name is present { wxMemoryInputStream mis( &m_zip_image[0], m_zip_image.size() ); // This decoder should always be UTF8, since it was saved that way by git. // That is, since pretty footprints are UTF8, and they were pushed to the // github repo, they are still UTF8. wxZipInputStream zis( mis, wxConvUTF8 ); wxZipEntry* entry = (wxZipEntry*) it->second; // remove "const"-ness if( zis.OpenEntry( *entry ) ) { INPUTSTREAM_LINE_READER reader( &zis ); PCB_PARSER parser( &reader ); MODULE* ret = (MODULE*) parser.Parse(); return ret; } } return NULL; // this API function returns NULL for "not found", per spec. } bool GITHUB_PLUGIN::IsFootprintLibWritable( const wxString& aLibraryPath ) { return false; } void GITHUB_PLUGIN::cacheLib( const wxString& aLibraryPath ) throw( IO_ERROR ) { if( !m_cache || m_lib_path != aLibraryPath ) { delete m_cache; m_cache = new FPL_CACHE(); remote_get_zip( aLibraryPath ); m_lib_path = aLibraryPath; wxMemoryInputStream mis( &m_zip_image[0], m_zip_image.size() ); // @todo: generalize this name encoding from a PROPERTY (option) later wxZipInputStream zis( mis, wxConvUTF8 ); wxZipEntry* entry; while( (entry = zis.GetNextEntry()) != NULL ) { wxFileName fn( entry->GetName() ); if( fn.GetExt() == wxT( "kicad_mod" ) ) { string fp_name = TO_UTF8( fn.GetName() ); m_cache->insert( fp_name, entry ); } else delete entry; } } } bool GITHUB_PLUGIN::repoURL_zipURL( const wxString& aRepoURL, string* aZipURL ) { // e.g. "https://github.com/liftoff-sr/pretty_footprints" D(printf("aRepoURL:%s\n", TO_UTF8( aRepoURL ) );) wxURI repo( aRepoURL ); if( repo.HasServer() && repo.HasPath() ) { // goal: "https://github.com/liftoff-sr/pretty_footprints/archive/master.zip" wxString zip_url( wxT("https://") ); zip_url += repo.GetServer(); zip_url += repo.GetPath(); zip_url += wxT('/'); zip_url += wxT( "archive/master.zip" ); *aZipURL = zip_url.utf8_str(); return true; } return false; } void GITHUB_PLUGIN::remote_get_zip( const wxString& aRepoURL ) throw( IO_ERROR ) { string zip_url; if( !repoURL_zipURL( aRepoURL, &zip_url ) ) { wxString msg = wxString::Format( _("Unable to parse URL: %s"), GetChars( m_lib_path ) ); THROW_IO_ERROR( msg ); } boost::asio::io_service io; avhttp::http_stream h( io ); avhttp::request_opts options; options.insert( "Accept", "application/zip" ); options.insert( "User-Agent", "http://kicad-pcb.org" ); // THAT WOULD BE ME. h.request_options( options ); try { h.open( zip_url ); // only one file, therefore do it synchronously. ostringstream os; os << &h; // Keep zip file byte image in RAM. That plus the MODULE_MAP will constitute // the cache. We don't cache the MODULEs per se, we parse those as needed from // this zip file image. m_zip_image = os.str(); // 4 lines, using SSL, top that. } catch( std::exception& e ) { THROW_IO_ERROR( e.what() ); } } #if 0 && defined(STANDALONE) int main( int argc, char** argv ) { INIT_LOGGER( ".", "test.log" ); GITHUB_PLUGIN gh; try { wxArrayString fps = gh.FootprintEnumerate( wxT( "https://github.com/liftoff-sr/pretty_footprints" ), NULL ); for( int i=0; i<(int)fps.Count(); ++i ) { printf("[%d]:%s\n", i, TO_UTF8( fps[i] ) ); } } catch( IO_ERROR ioe ) { printf( "%s\n", TO_UTF8(ioe.errorText) ); } UNINIT_LOGGER(); return 0; } #endif