1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* 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.
@todo:
Derive this PLUGIN from KICAD_PLUGIN so we can use its FootprintSave().
Support local footprints if they are present in an optional directory.
Possibly cache the zip file locally. Use HTTP's "have changed" or whatever it is called.
*/
#ifndef WIN32_LEAN_AND_MEAN
// when WIN32_LEAN_AND_MEAN is defined, some useless includes in <window.h>
// are skipped, and this avoid some compil issues
#define WIN32_LEAN_AND_MEAN
#endif
#ifdef WIN32
// defines needed by avhttp
// Minimal Windows version is XP: Google for _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#endif
#include <sstream>
#include <boost/ptr_container/ptr_map.hpp>
#include <wx/zipstrm.h>
#include <wx/mstream.h>
#include <wx/uri.h>
#include <fctsys.h>
// Under Windows Mingw/msys, avhttp.hpp should be included after fctsys.h
// in fact after wx/wx.h, included by fctsys.h,
// to avoid issues (perhaps due to incompatible defines)
#include <avhttp.hpp> // chinese SSL magic
#include <io_mgr.h>
#include <richio.h>
#include <pcb_parser.h>
#include <class_board.h>
#include <github_plugin.h>
#include <class_module.h>
#include <macros.h>
using namespace std;
typedef boost::ptr_map<string, wxZipEntry> MODULE_MAP;
typedef MODULE_MAP::iterator MODULE_ITER;
typedef MODULE_MAP::const_iterator MODULE_CITER;
/**
* Class GH_CACHE
* assists only within GITHUB_PLUGIN and holds a map of footprint name to wxZipEntry
*/
struct GH_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, const PROPERTIES* aProperties )
{
//D(printf("%s: this:%p aLibraryPath:'%s'\n", __func__, this, TO_UTF8(aLibraryPath) );)
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, const PROPERTIES* aProperties )
{
// D(printf("%s: this:%p aLibraryPath:'%s'\n", __func__, this, TO_UTF8(aLibraryPath) );)
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();
// Dude, the footprint name comes from the file name in
// a github library. Zero out the library name, we don't know it here.
// Caller always has to set the library nickname if it knows it.
ret->SetFPID( fp_name );
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::FootprintLibOptions( PROPERTIES* aListToAppendTo ) const
{
// inherit options supported by all PLUGINs.
PLUGIN::FootprintLibOptions( aListToAppendTo );
(*aListToAppendTo)["allow_pretty_writing_to_this_dir"] = wxString( _(
"Set this property to a directory where footprints are to be written as pretty "
"footprints when saving to this library. Anything saved will take precedence over "
"footprints by the same name in the github repo. These saved footprints can then "
"be sent to the library maintainer as updates. "
"<p>The directory should have a <b>.pretty</b> file extension because the "
"Kicad plugin is used to do the saving.</p>"
)).utf8_str();
(*aListToAppendTo)["cache_github_zip_in_this_dir"] = wxString( _(
"Set this property to a directory where the github *.zip file will be cached. "
"This should speed up subsequent visits to this library."
)).utf8_str();
}
void GITHUB_PLUGIN::cacheLib( const wxString& aLibraryPath ) throw( IO_ERROR )
{
if( !m_cache || m_lib_path != aLibraryPath )
{
// operator==( wxString, wxChar* ) does not exist, construct wxString once here.
const wxString kicad_mod( wxT( "kicad_mod" ) );
//D(printf("%s: this:%p m_lib_path:'%s' aLibraryPath:'%s'\n", __func__, this, TO_UTF8( m_lib_path), TO_UTF8(aLibraryPath) );)
delete m_cache;
m_cache = new GH_CACHE();
// INIT_LOGGER( "/tmp", "test.log" );
remote_get_zip( aLibraryPath );
// UNINIT_LOGGER();
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() ); // chop long name into parts
if( fn.GetExt() == kicad_mod )
{
string fp_name = TO_UTF8( fn.GetName() ); // omit extension & path
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://" ) );
#if 0 // Github issues a redirect for this "master.zip". i.e.
// "https://github.com/liftoff-sr/pretty_footprints/archive/master.zip"
// would be redirected to:
// "https://codeload.github.com/liftoff-sr/pretty_footprints/zip/master"
// The alternate code path below uses the redirected URL on first attempt
// to save one HTTP GET hit. avhttp does the redirect behind the scenes normally.
zip_url += repo.GetServer();
zip_url += repo.GetPath();
zip_url += wxT( '/' );
zip_url += wxT( "archive/master.zip" );
#else
zip_url += wxT( "codeload.github.com" );
zip_url += repo.GetPath();
zip_url += wxT( '/' );
zip_url += wxT( "zip/master" );
#endif
*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:\n'%s'" ), GetChars( aRepoURL ) );
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
{
ostringstream os;
h.open( zip_url ); // only one file, therefore do it synchronously.
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( boost::system::system_error& e )
{
// https "GET" has faild, report this to API caller.
wxString fmt( _( "Cannot GET zip: '%s'\nfor lib-path: '%s'.\nWhat: '%s'" ) );
string msg = StrPrintf( TO_UTF8( fmt ),
zip_url.c_str(),
TO_UTF8( aRepoURL ),
e.what() );
THROW_IO_ERROR( msg );
}
}
#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