git-svn-id: svn://euphorik.ch/pompage@23 02bbb61a-6d21-0410-aba0-cb053bdfd66a
[pompage.git] / src / film.rb
1 require 'rexml/document'
2 require 'net/http'
3 require 'thread'
4 require 'thwait'
5 require 'cgi'
6
7 require 'pays.rb'
8 require 'genre.rb'
9 require 'personne.rb'
10
11 require 'constantes.rb'
12
13 class String
14 def virerBalisesHTML
15 return self.gsub(/<(.*?)>/, '')
16 end
17 end
18
19 class Film
20 attr_accessor :id, :titre, :fichiers, :annee, :realisateurs, :acteurs, :pays, :duree, :critiquePresse, :critiqueSpectateur, :genres, :synopsis, :budget
21
22 # Les films indexés par leur titre
23 @@films = {}
24
25 # Les films indexés par leur nom de fichier, deux fichiers différents peuvent pointer sur le même film
26 @@filmsFichier = {}
27
28 # Les films qui ont plusieurs réponses lors de la recherche, traités à la fin
29 @@filmsPlusieursReponses = []
30
31 @@mutex = Mutex::new
32 @@threadsWait = ThreadsWait::new
33 @@nbConn = 0
34
35 # le prochain id disponible
36 @@idDisponible = 1
37
38 # retourne un nouvel id, utilisé lors de la création d'un nouveau film
39 def Film::getNewId
40 id = @@idDisponible
41 @@idDisponible += 1
42 return id
43 end
44
45 # Lit un repertoire de manière recursive
46 def Film::litRepertoire(r)
47 Film::litRepertoireR(r)
48 # on attends que les threads se terminent
49 @@threadsWait.all_waits
50
51 # traite les films qui avaient plusieurs réponses lors de la recherche
52 # l'utilisateur doit faire un choix
53 @@filmsPlusieursReponses.each{|f|
54 if f.reglerConflitPlusieursReponses
55 Film::ajouterFilm(f)
56 end
57 }
58 end
59
60 # Charge les films contenus dans un fichier XML.
61 def Film::loadFilmsXml(xmlFile)
62 # si le fichier n'existe pas il n'y a rien à charger
63 if !File.exists?(xmlFile)
64 return
65 end
66
67 racine = REXML::Document::new(File::new(xmlFile)).root
68 racine.each_element{|e|
69 id = e.attribute('id').to_s.to_i
70
71 if id > @@idDisponible
72 @@idDisponible = id + 1
73 end
74
75 titre = e.get_elements('titre')[0].get_text
76
77 fichiers = e.get_elements('fichiers')[0].get_elements('fichier')
78 annee = e.get_elements('annee')[0].get_text
79 duree = e.get_elements('duree')[0].get_text
80 critiquePresse = e.get_elements('critiquePresse')[0].get_text
81 critiqueSpectateur = e.get_elements('critiqueSpectateur')[0].get_text
82 synopsis = e.get_elements('synopsis')[0].get_text
83 budget = e.get_elements('budget')[0].get_text
84 realisateurs = e.get_elements('realisateurs')[0].get_elements('realisateur')
85 acteurs = e.get_elements('acteurs')[0].get_elements('acteur')
86 pays = e.get_elements('lespays')[0].get_elements('pays')
87 genres = e.get_elements('genres')[0].get_elements('genre')
88
89 film = Film::new(fichiers[0].get_text.value)
90
91 film.titre = titre.value unless titre.nil?
92 film.id = id
93 fichiers.each{|e|
94 film.addFichier(e.get_text.value)
95 @@filmsFichier[e.get_text.value] = film
96 }
97 film.annee = annee.value unless annee.nil?
98 acteurs.each{|e|
99 film.acteurs << Personne::ajouter(e.get_text.value)
100 }
101 pays.each{|e|
102 film.pays << Pays::ajouter(e.get_text.value)
103 }
104 film.duree = duree.value unless duree.nil?
105 film.critiquePresse = critiquePresse.value unless critiquePresse.nil?
106 film.critiqueSpectateur = critiqueSpectateur.value unless critiqueSpectateur.nil?
107 genres.each{|e|
108 film.genres << Genre::ajouter(e.get_text.value) if e.get_text != nil
109 }
110 film.synopsis = synopsis.value unless synopsis.nil?
111 film.budget = budget.value unless budget.nil?
112 @@films[film.titre] = film
113 }
114 end
115
116 # Renvoie tous les films sous la forme d'un document XML.
117 def Film::getFilmsXml
118 # le document
119 docXml = REXML::Document::new
120 docXml.xml_decl().encoding = "UTF-8" # normalement UTF-8
121 docXml.xml_decl().dowrite
122
123 # la racine du document
124 racine = REXML::Element::new('filmographie')
125 docXml.add(racine)
126 pi = REXML::Instruction.new("xml-stylesheet", "type=\"text/xsl\" href=\"../xsl/yopyop.xsl\"")
127 racine.previous_sibling = pi
128
129 # on ajoute chaque film à la racine
130 @@films.each{|nom, f|
131 racine.add(f.getXml)
132 }
133
134 # revoie le document
135 docXml
136 end
137
138 private
139
140 def Film::filmsFactory(fichier)
141 Film::new(fichier).loadData
142 end
143
144 def Film::litRepertoireR(r)
145 Dir::foreach(r){|f|
146 next if f == '.' or f == '..'
147 fichier = r + "/" + f
148 if File::directory?(fichier)
149 litRepertoireR(fichier)
150 else
151
152 # vérification de l'extension
153 /^.*?\.(.{3,4})$/ =~ fichier
154 if !FILMS_EXTENSIONS.include?($1)
155 next
156 end
157
158 fichier = CGI::escapeHTML(fichier.unpack("C*").pack("U*"))
159
160 # on skip si le film est déjàa dans la BD
161 if film = @@filmsFichier[fichier]
162 puts "[i] Already exists in DB : #{film.titre} (#{fichier})"
163 next
164 end
165
166 #p fichier
167
168 film = nil
169
170 @@nbConn += 1
171 @@threadsWait.join_nowait(
172 Thread::new{
173 begin
174 @@mutex.lock if @@nbConn >= NB_CONN_MAX
175 film = Film::filmsFactory(fichier)
176 unless film.nil? # le film a été correctement construit
177 Film::ajouterFilm(film)
178 end
179 @@nbConn -= 1
180 @@mutex.unlock
181 rescue Exception => e
182 puts e.message
183 puts e.backtrace
184 end
185 }
186 )
187 end
188 }
189 end
190
191 def Film::ajouterFilm(film)
192 if film.plusieursReponses?
193 @@filmsPlusieursReponses << film
194 return
195 end
196
197 # le film existe déjà
198 if @@films.has_key?(film.titre)
199 # le fichier n'est pas connu -> nième partie d'un film
200 if !@@filmsFichier.has_key?(film.fichiers[0])
201 puts "[i] movie #{film.titre} has a another file part : #{film.fichiers[0]}"
202 @@films[film.titre].addFichier(film.fichiers[0])
203 @@filmsFichier[film.fichiers[0]] = @@films[film.titre]
204 else
205 puts "[!] Duplicate movie : #{film.titre} (#{film.fichiers[0]})"
206 end
207 else
208 puts "[i] movie added : #{film.titre} (#{film.fichiers[0]})"
209 @@films[film.titre] = film
210 @@filmsFichier[film.fichiers[0]] = film
211 end
212 end
213
214
215 def initialize(fichier)
216 @fichiers = [fichier]
217
218 @id = 0
219 @titre = ''
220 @annee = nil
221 @realisateurs = []
222 @acteurs = []
223 @pays = []
224 @duree = nil
225 @critiquePresse = nil
226 @critiqueSpectateur = nil
227 @genres = []
228 @synopsis = nil
229 @budget = nil
230 @budgetUnite = 'euro'
231 @url
232
233 @aPlusieursReponses = false
234 # mémorise les tuples {nom => id} dans le cas ou il y a plusieurs réponses
235 @idsAllocine = {}
236 end
237
238 public
239
240 def plusieursReponses?
241 return @aPlusieursReponses
242 end
243
244 # demande à l'utilisateur de faire un choix
245 # ret : true si le conflit à été résolu sinon false
246 def reglerConflitPlusieursReponses
247
248 @aPlusieursReponses = false # pour faire les choses bien
249
250 puts
251 puts "Plop, ya un conflit : #{@fichiers[0]}"
252 puts "Fais ton choix jeune padawan (un caractère et pas plus)"
253 tabNoms = @idsAllocine.keys
254 choix = 1
255 loop do
256 i = 1
257 tabNoms.each{|n|
258 puts "#{i}. #{n}"
259 i += 1
260 }
261 puts "A. Passer et l'ajouter"
262 puts "B. Ignorer"
263 choix = STDIN.gets
264
265 if /A/i =~ choix
266 return true
267 elsif /B/i =~ choix
268 return false
269 end
270
271 choix = choix.to_i
272 if choix > 0 && choix <= tabNoms.length
273 break;
274 else
275 puts
276 puts "Choix pas bon !!"
277 end
278 end
279
280 loadDepuisIdAllocine(@idsAllocine[tabNoms[choix-1]])
281
282 return true
283 end
284
285 def addFichier(fichier)
286 if !@fichiers.include?(fichier)
287 @fichiers << fichier
288 end
289 end
290
291 # Charge les informations du films à partir d'allocine.fr
292 # ret [Film]
293 def loadData
294 unless LOAD_DATA
295 @titre = @fichiers[0]
296 return self
297 end
298
299 @id = Film::getNewId
300
301 connexionHttp = Net::HTTP::new('www.allocine.fr')
302
303 #extrait le nom à partir du nom du fichier
304 /^.*?([^\/]*?)\.(.{3,4})$/ =~ @fichiers[0]
305 #remplace undescores et points par des espaces
306 titre = $1.gsub(/[_\.]/, ' ')
307 #remplace les suites d'espaces par un seul
308 titre.gsub!(/ {2,}/,' ')
309 titre.gsub!(/\[.*?\]/,'')
310 titre.gsub!(/\(.*?\)/,'')
311 titre.gsub!(/\{.*?\}/,'')
312 #vire les espaces au début et à la fin
313 titre.strip!
314
315 @titre = titre.dup
316
317 donneesHtml = nil
318 begin
319 begin
320 reponse, donneesHtml = connexionHttp.get("/recherche/?motcle=#{CGI::escape(titre.unpack("U*").pack("C*"))}")
321 rescue Exception => e
322 p e
323 puts "[!] Connexion lost, retry.."
324 retry
325 end
326
327 #convertit le code latin-1 en UTF8
328 donneesHtml = donneesHtml.unpack("C*").pack("U*")
329
330 #si pas trouvé alors on enlève un mot à la fin
331 if /.*?Pas de résultats.*?/ =~ donneesHtml || ! donneesHtml.include?("<h3><b>Films <h4>")
332 /(.*?)[^ ]+?$/ =~ titre.strip
333 titre = $1
334 titre.strip!
335 else
336 break;
337 end
338 end while not titre.nil? and not titre.empty?
339
340 unless titre.nil? or titre.empty?
341
342 #/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">/ =~ donneesHtml
343 #r = donneesHtml.scan(/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">(.*?)<\/a>/)
344 r = donneesHtml.scan(/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">(.*?)<\/a>(?:<\/h4><h5 style="color: #666666">&nbsp;(.*?)<\/h5>){0,1}/)
345
346 if r.length > 1
347 @aPlusieursReponses = true
348 r.each{|f|
349 @idsAllocine[f[1].virerBalisesHTML + (f[2] != nil ? " " + f[2].virerBalisesHTML : "")] = f[0]
350 }
351 elsif r.length == 1
352 loadDepuisIdAllocine(r[0][0], connexionHttp)
353 else
354 puts "[!] Movie not found : #{@titre} (#{@fichier})"
355 end
356 end
357 self
358 end
359
360 private
361 def loadDepuisIdAllocine(id, connexionHttp = nil)
362 if (connexionHttp == nil)
363 connexionHttp = Net::HTTP::new('www.allocine.fr')
364 end
365
366 r, ficheHtml = connexionHttp.get("/film/fichefilm_gen_cfilm=#{id}.html")
367
368 #convertit le code latin-1 en UTF8
369 ficheHtml = ficheHtml.unpack("C*").pack("U*")
370
371 #url
372 @url = "http://www.allocine.fr/film/fichefilm_gen_cfilm=#{id}.html"
373
374 # Titre
375 /<title>(.*?)<\/title>/ =~ ficheHtml
376 @titre = $1 unless $1.nil?
377
378 puts "Movie found : #{@titre} (#{@fichiers[0]})"
379
380 # Année
381 /<h4>Année de production : (\d+)<\/h4>/ =~ ficheHtml
382 @annee = $1.to_i unless $1.nil?
383
384 # Réalisateurs
385 /<h4>Réalisé par(.*?)<\/h4>/ =~ ficheHtml
386 $1.scan(/<a class="link1" href=".*?">(.*?)<\/a>/m){|a|
387 @realisateurs << Personne::ajouter(a[0]) unless a[0].nil?
388 } unless $1.nil?
389
390 # Acteurs
391 /<h4>Avec(.*?)<\/h4>/ =~ ficheHtml
392 $1.scan(/<a class="link1" href="\/personne\/fichepersonne_gen_cpersonne=\d+\.html">(.+?)<\/a>/m){|a|
393 @acteurs << Personne::ajouter(a[0]) unless a[0].nil?
394 } unless $1.nil?
395
396
397 # Pays
398 /<h4>Film (.*?)\.&nbsp;<\/h4>/ =~ ficheHtml
399 $1.split(',').each{|pays|
400 @pays << Pays::ajouter(pays) unless pays.nil?
401 } unless $1.nil?
402
403 # Duree
404 /<h4>Durée : (\d+)h (\d+)min./ =~ ficheHtml
405 @duree = $1.nil? ? $2.to_i : $1.to_i * 60 + $2.to_i
406
407 # Critiques presse et spectateur
408 /Presse.*etoile_([012345]).*Spectateurs.*etoile_([012345])"/m =~ ficheHtml
409 @critiquePresse = $1.to_i unless $1.nil?
410 @critiqueSpectateur = $2.to_i unless $2.nil?
411
412 # Genre
413 /<h4>Genre : (.*?)<\/h4>/ =~ ficheHtml
414 $1.scan(/<a href="\/film\/alaffiche_genre_gen_genre=.*?" class="link1">(.+?)<\/a>/m){|g|
415 @genres << Genre::ajouter(g[0]) unless g[0].nil?
416 } unless $1.nil?
417
418 # Synopsis
419 /Synopsis.*?<h4>(.+?)<\/h4>/m =~ ficheHtml
420 @synopsis = $1 unless $1.nil?
421
422 # Budget
423 /Budget<\/b> : (.+?) millions d'euros<\/h4>/ =~ ficheHtml
424 @budget = $1.to_i unless $1.nil?
425 end
426
427 public
428
429 # Renvoie un film sous la forme d'un élément XML de type REXML
430 def getXml
431
432 racine = REXML::Element::new('film')
433 racine.add_attribute('id', @id.to_s)
434
435 fichiers = REXML::Element::new('fichiers')
436 @fichiers.each{|f|
437 fichiers.add(REXML::Element::new('fichier').add_text(f))
438 }
439 racine.add(fichiers)
440
441 racine.add(REXML::Element::new('titre').add_text(@titre))
442 racine.add(REXML::Element::new('annee').add_text(@annee.to_s))
443
444 realisateurs = REXML::Element::new('realisateurs')
445 @realisateurs.each{|r|
446 realisateurs.add(REXML::Element::new('realisateur').add_text(r.nom))
447 }
448 racine.add(realisateurs)
449
450 acteurs = REXML::Element::new('acteurs')
451 @acteurs.each{|a|
452 acteurs.add(REXML::Element::new('acteur').add_text(a.nom))
453 }
454 racine.add(acteurs)
455
456 lespays = REXML::Element::new('lespays')
457 @pays.each{|p|
458 lespays.add(REXML::Element::new('pays').add_text(p.nom))
459 }
460 racine.add(lespays)
461
462 racine.add(REXML::Element::new('duree').add_text(@duree.to_s))
463
464 racine.add(REXML::Element::new('critiquePresse').add_text(@critiquePresse.to_s))
465 racine.add(REXML::Element::new('critiqueSpectateur').add_text(@critiqueSpectateur.to_s))
466
467 genres = REXML::Element::new('genres')
468 @genres.each{|g|
469 genres.add(REXML::Element::new('genre').add_text(g.nom))
470 }
471 racine.add(genres)
472
473 racine.add(REXML::Element::new('synopsis').add_text(@synopsis))
474 budgetElement = REXML::Element::new('budget')
475 budgetElement.add_text(@budget.to_s)
476 budgetElement.add_attribute('unite', @budgetUnite)
477 racine.add(budgetElement)
478
479 racine.add(REXML::Element::new('url').add_text(@url))
480
481 racine
482 end
483 end
484