1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Tools for reading out magma lists.
23
24 Usage: magma_list.py CMD [OPT]
25
26 Commands:
27 - magnets_from_magma MAGMA_FILE
28 - magma_from_files MAGMA FILE1 FILE2 ...
29
30 Examples:
31 - magma_list.py magnets_from_magma example-0.4.magma
32 - magma_list.py magma_from_files example-0.4.magma blah.txt foo.txt gam.txt
33 """
34
35 __depends__ = "yaml"
36
37 try:
38 from create_simple_magma_list import FILE_LIST_NAME, URN_PARAMETER_NAME, MAGMA_0_4_HEADER, FILENAME_PARAMETER
39 except:
40 from magma.create_simple_magma_list import FILE_LIST_NAME, URN_PARAMETER_NAME, MAGMA_0_4_HEADER, FILENAME_PARAMETER
41
42
43
44 from yaml import load, dump
45
46
47
49 """A Magma List."""
50 - def __init__(self, magma_file=None, input_files=None, creator=None, data=None, yaml_data=None, *args, **kwds):
51 """A Magma list.
52
53 @param magma_file: The path to the magma file, either absolute or relative the the location this script is invoked from.
54 @type magma_file: String
55
56 @param input_files: A list of file_paths of input files which should be included in the magma list.
57 @type input_files: List
58
59 @param creator: An identifier for the creator of the Magma file.
60 @type creator: String
61
62 @param yaml_data: Raw file data as read from a Magma file.
63 @type yaml_data: String
64
65 @param data: A native Python dict with the data.
66 @type data: Dict
67
68 """
69
70 self.data = None
71
72 if data is not None:
73 self.data = data
74
75 elif yaml_data is not None:
76 self.data = self.readout_yaml(yaml_data)
77
78 elif magma_file is not None:
79 self.data = self.readout_magma_from_file(magma_file)
80
81 elif input_files is not None:
82 self.data = self.create_magma_from_files(file_paths=input_files)
83 else:
84 raise Exception("No input specified. Youl need to pass one of data=DATA, magma_file=PATH or input_files=[PATH1, PATH2, ...]")
85
86
87 if creator is not None:
88 self.data["creator"] = creator
89
90
91 self.files = self.files()
92
93
94 self.magnets = self.get_file_magnets()
95
96
97 self.metadata = self.get_magma_metadata()
98
100 """Return a human readable representation of the yaml file.
101
102 In effect return the yaml data with the magic header.
103
104 @return: A human readable representation of the Magma file in yaml format (String)."""
105
106 return self.dump()
107
108
110 """Remove empty entries in the Magma, for example empty alt-locs.
111
112 We remove each entry by hand, because we should know which entries can be created by this program - and if we don't we know that have to change our coding practice :) ."""
113
114
115 for i in self.files:
116 i.remove_empty_entries()
117
119 """Create a magma file from given file paths.
120
121 @return: The Python dict for a Magma file - the magma data.
122 """
123 try:
124 from create_simple_magma_list import Magma as SimpleMagma
125 except:
126 from magma.create_simple_magma_list import Magma as SimpleMagma
127 magma = SimpleMagma(file_paths)
128 return magma.data
129
131 """Check, if a given yaml data begins with the Magma header.
132
133 @param yaml_data: Data in yaml format
134 @type yaml_data: String
135 @return: True or False
136 """
137 return yaml_data[:len(MAGMA_0_4_HEADER)] == MAGMA_0_4_HEADER
138
140 """Readout a magma list from data in yaml format.
141
142 @param yaml_data: Data in yaml format.
143 @type yaml_data: String
144 @return: The Python dict for a Magma file - the magma data.
145 """
146
147 assert self.is_magma(yaml_data), "Doesn't begin with Magma v0.4 header " + MAGMA_0_4_HEADER
148
149 data = load(yaml_data)
150
151 if data is None:
152 data = {}
153 return data
154
156 """Readout a magma list from the file_path
157
158 @param file_path: The path to the magma file, either absolute or relative the the location this script is invoked from.
159 @type file_path: String
160 @return: The Python dict for a Magma file - the magma data. """
161
162 file_handle = open(file_path, "r")
163
164 file_data = file_handle.read()
165
166 file_handle.close()
167
168 return self.readout_yaml(file_data)
169
171 """Get the files from the magma data.
172
173 @return: A list of all MagmaFile objects from the Magma.
174 """
175
176
177 file_list = []
178
179
180 if FILE_LIST_NAME in self.data.keys():
181
182 for i in self.data[FILE_LIST_NAME]:
183 file_list.append(MagmaFile(i))
184
185 return file_list
186
188 """Get the magnet links of the files.
189
190 # TODO: Include Alt-Locs.
191
192 @return: A list of all magnet links inside the Magma.
193 """
194
195 magnets = []
196
197 for i in self.files:
198 magnets.append(i.magnet)
199 return magnets
200
214
216 """@return: A list of files which can be read by MAGMAv0.2 compliant programs."""
217
218 data = "list:\n"
219
220
221 for i in self.files:
222 data += ' - "' + i.magnet + '"\n'
223
224 return data
225
227 """@return: Data in Magma v0.2 format"""
228
229 data = "#MAGMAv0.2\n"
230
231
232 data += magma_v0_2_filelist_representation()
233
234 return data
235
236
237 - def save(self, path, magma_versions = [0.2, 0.4]):
238 """Save the magma list to the path.
239
240 If the folder to save in doesn't exist, just raise an Exception.
241
242 TODO: Clean out empty entries in the files and the metadata before saving.
243 self.data["gnutella"]["alt-locs"] = [] should disappear (if there are no other keys than alt-locs in gnutella, the gnutella dict should completely disappear, too).
244
245 @param path: The path to the target file.
246 @type path: String
247
248 @param magma_versions: The versions of the magma specification to be supported.
249 @type magma_versions: List of Floats
250
251 @return: None
252 """
253
254 self.remove_empty_entries()
255
256 file_handle = open(path, "w")
257
258 file_handle.write(self.dump(magma_versions=magma_versions))
259
260 file_handle.close()
261
263 """Print the magma file in the yaml format.
264
265 @return: A string representation of the Magma in yaml format.
266 """
267
268 print self.__str__()
269
271 """Return the yaml representation of the data.
272
273 @return: The yaml representation of the data."""
274 return dump(self.data, default_flow_style=False)
275
276 - def dump(self, magma_versions = [0.2, 0.4]):
277 """Return the yaml data.
278
279 @return: A string representation of the Magma in yaml format.
280 """
281
282 if 0.2 in magma_versions and 0.4 in magma_versions:
283
284 if "list" in self.data.keys():
285 del(self.data["list"])
286
287 return "#MAGMAv0.4\n" + self.get_yaml_data() + "\n# Magmav0.2 compatibility section\n" + self.magma_v0_2_filelist_representation()
288 elif 0.4 in magma_versions:
289 return "#MAGMAv0.4\n" + self.get_yaml_data()
290
291
292
293
295 """A file inside the magma list.
296
297 Provides some abstractions for files inside Magma objects.
298
299 >>> magma_file = MagmaFile({FILENAME_PARAMETER: "input_file.txt", "urn": {"sha1": "3UJCLAOIZVCNAIT7TQYFLAP7ZNFW6G2G"}})
300 >>> print magma_file.data
301 {'urn': {'sha1': '3UJCLAOIZVCNAIT7TQYFLAP7ZNFW6G2G'}, 'Filename': 'input_file.txt'}
302 >>> print magma_file.sha1_hash
303 3UJCLAOIZVCNAIT7TQYFLAP7ZNFW6G2G
304 >>> print magma_file.name
305 input_file.txt
306 >>> print magma_file.magnet
307 magnet:?xt=urn:sha1:3UJCLAOIZVCNAIT7TQYFLAP7ZNFW6G2G&dn=input_file.txt
308
309
310 """
311
312 - def __init__(self, data, *args, **kwds):
328
329
331 """Show nicely readable information about the file.
332
333 @return: Nicely readable information about the file (String). """
334 return self.name + " " + self.sha1_hash
335
337 """Readout alternate urls from the data
338
339 @return: A list of URLs which can be used as alternate fallback sources (which are unsafe, though, since it isn't sure that they will point to the same file when times change). """
340
341 if "urls" in self.data.keys():
342 return self.data["urls"]
343
344 else:
345
346 self.data["urls"] = []
347
348 return self.data["urls"]
349
350
352 """Readout the Alt-Locs from the data
353
354 @return: A list of IPs with Port numbers which can be used together with the sha1 hash to construct alt-locs for Gnutella. """
355 ALT_LOC_STRING = "alt-locs"
356
357 if "gnutella" in self.data.keys() and ALT_LOC_STRING in self.data["gnutella"].keys():
358 return self.data["gnutella"][ALT_LOC_STRING]
359
360 else:
361
362 if not "gnutella" in self.data.keys():
363 self.data["gnutella"] = {}
364 self.data["gnutella"][ALT_LOC_STRING] = []
365
366 return self.data["gnutella"][ALT_LOC_STRING]
367
368
370 """Parse the magnet link from the data inside the file.
371
372 @return: A magnet link for the file (String). """
373
374 xt = "urn:sha1:" + self.sha1_hash
375
376 dn = self.name
377
378
379 params = [("xt", xt ), ("dn", dn )]
380
381
382 gnet_alt_locs = self.readout_gnet_alt_locs()
383
384 for i in gnet_alt_locs:
385 params.append(("xs", "http://" + str(i) + "/uri-res/N2R?" + xt))
386
387
388
389 from urllib import urlencode
390
391 magnet = "magnet:?" + urlencode(params)
392
393 return magnet
394
395
396 magnet = property(fget=parse_magnet, fset=setattr)
397
399 """Remove empty entries from the file, for example empty alt-locs."""
400
401
402
403 for i in self.data:
404 try:
405
406 for j in self.data[i].keys():
407 try:
408
409 if len(self.data[i][j]) == 0:
410 del(self.data[i][j])
411 except: pass
412 except: pass
413
414
415 for i in self.data.keys():
416 try:
417
418 if len(self.data[i]) == 0:
419 del(self.data[i])
420 except:
421 pass
422
423
425 """Usage instructions.
426
427 @return: Usage instructions (String)."""
428
429 usage = __doc__.split("\n\n")[1:]
430 usage_string = "\n\n".join(usage)
431 return usage_string
432
434 """Do all doctests.
435
436 @return: None"""
437 from doctest import testmod
438 testmod()
439
440
441 if __name__ == "__main__":
442
443 from sys import argv
444 if len(argv) < 3:
445 print usage()
446 elif argv[1] == "magnets_from_magma":
447 magma = Magma(magma_file=argv[2])
448 for i in magma.magnets:
449 print i
450 elif argv[1] == "magma_from_files":
451 magma = Magma(input_files=argv[3:])
452 magma.save(argv[2])
453 else:
454 print usage()
455