#!/usr/bin/ruby
# encoding: utf-8

class PhotorecScreen	
	def initialize(extlayers, buttons)
		@layers = Array.new
		### Are we in the mode for mobile phones?
		@mobilemode = false
		### Selected source device
		@selectedsource = nil
		### ComboBox for drives 
		@drivecombo = nil
		@drivelist = Array.new
		### ComboBox for targets
		@targetcombo = nil
		@targetdrives = Array.new
		@targetlist = Array.new
		### ComboBox for mobile drives
		@mobilecombo = nil
		@mobilelist = Array.new
		### Filetypes, long and short
		@checkbuttons = Hash.new
		@allchecked = nil
		@ftypes = {
			"office" => "Office-Dokumente (auch OpenOffice, PDF und RTF)",
			"image" => "Fotos und Bilder",
			"video" => "Videos",
			"audio" => "Musik",
			"mail" => "Email-Postfächer (bspw. von Outlook und Thunderbird)",
			"archive" => "Gepackte Dateien (.zip, .rar, .7z, .iso)"
		}
		### Progressbar for PhotoRec
		@photorec_progress = nil
		@photorec_exp = nil
		### VTE for running photorec
		@photorec_vte = Vte::Terminal.new
		@vte_shown = false
		@photorec_running = false
		
		@photorec_vte.signal_connect('child-exited') { @photorec_running = false }
		
		fixed = Gtk::Fixed.new
		text5 = Gtk::Label.new
		text5.width_request = 250
		text5.wrap = true
		text5.set_markup("<span color='white'>Klicken Sie hier, um wiederherzu-\nstellende Dateitypen auszuwählen.</span>")
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
		
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		# reread = Gtk::EventBox.new.add Gtk::Image.new("buttongreen.png")
		reread = Gtk::Button.new("Neu einlesen")
		reread.width_request = 120
		reread.height_request = 32
		
		@drivecombo = Gtk::ComboBox.new
		# @drivecombo.append_text("Yoyodyne Frobolator 500GB SATA (sda)") 
		# drivecombo.active = 0
		@drivecombo.height_request = 32
		@drivecombo.width_request = 380
		
		deltext = TileHelpers.create_label("<b>Quell-Laufwerk auswählen</b>\n\nAuf welchem Laufwerk wollen Sie nach verlorenen Dateien suchen? Falls sich diese Daten auf einem externen Laufwerk befanden, schließen Sie dieses jetzt an und klicken auf \"Neu einlesen\". Anschließend wählen Sie das Laufwerk aus. Das gesamte Laufwerk wird durchsucht.", 510)
		
		fixed.put(forw, 853, 282)
		fixed.put(text5, 605, 288)
		fixed.put(orangetile, 0, 0)
		fixed.put(deltext, 130, 0)
		fixed.put(reread, 520, 108)
		fixed.put(@drivecombo, 130, 108)
		TileHelpers.place_back(fixed, extlayers)
		extlayers["photorecstart"] = fixed
		@layers[0] = fixed
		buttons[0].signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				@drivelist = reread_drivelist(@drivecombo)
				#if @driveelist.size < 1 
				#	TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
				#else
					extlayers.each { |k,v|v.hide_all }
					@mobilemode = false
					extlayers["photorecstart"].show_all
				#end
			end
		}
		buttons[1].signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				@mobilelist = reread_drivelist(@mobilecombo, true)
				#if @mobilelist.size < 1 
				#	TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
				#else
					extlayers.each { |k,v|v.hide_all }
					@mobilemode = true
					extlayers["mobilerecstart"].show_all
				#end
			end
		}
		reread.signal_connect('clicked') {
			@drivelist = reread_drivelist(@drivecombo)
		}
		forw.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				extlayers.each { |k,v|v.hide_all }
				extlayers["photorectypes"].show_all
			end
		}
		#### Screen for filetypes
		photorectypes = create_ftype_screen(extlayers)
		extlayers["photorectypes"] = photorectypes
		@layers[1] = photorectypes
		
		#### Screen for target
		photorectarget = create_target_screen(extlayers)
		extlayers["photorectarget"] = photorectarget
		@layers[2] = photorectarget
		
		#### Screen for progress
		photorecprog = create_progress_screen(extlayers)
		extlayers["photorecprog"] = photorecprog
		@layers[3] = photorecprog
		
		#### Screen for summary
		photorecsummary = create_summary_screen(extlayers)
		extlayers["photorecsummary"] = photorecsummary
		@layers[4] = photorecsummary
		
		#### Screen for mobile recovery
		mobilerecscreen = create_mobilerec_screen(extlayers)
		extlayers["mobilerecstart"] = mobilerecscreen
		@layers[5] = mobilerecscreen
		
	end
	
	attr_reader :layers
	
	def create_mobilerec_screen(extlayers)
		fixed = Gtk::Fixed.new
		TileHelpers.place_back(fixed, extlayers)
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("mobilerecgreen.png")
		text5 = Gtk::Label.new
		text5.width_request = 230
		text5.wrap = true
		text5.set_markup("<span color='white'>Klicken Sie hier, um die Wiederherstellung zu starten.</span>")
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
		deltext = TileHelpers.create_label("<b>Quell-Laufwerk auswählen</b>\n\nAuf welchem Laufwerk möchten Sie nach verlorenen Dateien suchen? Falls noch nicht geschehen, legen Sie die Speicherkarte nun ein oder schließen die Kamera beziehungsweise das Smartphone per USB-Kabel an und aktivieren dort den \"Massenspeichermodus\". Klicken Sie dann auf \"Neu einlesen\". Anschließend wählen Sie das Laufwerk aus.", 510)
		@mobilecombo = Gtk::ComboBox.new
		@mobilecombo.height_request = 32
		@mobilecombo.width_request = 380
		reread = Gtk::Button.new("Neu einlesen")
		reread.width_request = 120
		reread.height_request = 32
		reread.signal_connect("clicked") {
			@mobilelist = reread_drivelist(@mobilecombo, true)
		}
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
			forw.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				if @mobilelist.size < 1 
					TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
				else
					@selectedsource = @mobilelist[@mobilecombo.active].device
					@targetdrives, @targetlist = reread_targetlist(@targetcombo)
					if @targetlist.size < 1 
						TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke als Ziel für die Datenrettung gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
					else
						extlayers.each { |k,v|v.hide_all }
						extlayers["photorectarget"].show_all
					end
				end
			end
		}
		fixed.put(forw, 853, 282)
		fixed.put(text5, 605, 288)
		fixed.put(orangetile, 0, 0)
		fixed.put(deltext, 130, 0)
		fixed.put(reread, 520, 128)
		fixed.put(@mobilecombo, 130, 128)
		TileHelpers.place_back(fixed, extlayers)
		return fixed
	end
	
	def create_ftype_screen(extlayers)
		fixed = Gtk::Fixed.new
		TileHelpers.place_back(fixed, extlayers)
		text5 = Gtk::Label.new
		text5.width_request = 230
		text5.wrap = true
		text5.set_markup("<span color='white'>Klicken Sie hier, um zur Auswahl des Ziellaufwerkes zu gelangen.</span>")
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		deltext = TileHelpers.create_label("<b>Nach welchen Dateitypen soll gesucht werden?</b>", 510)
		
		deltab = Gtk::Table.new(@ftypes.size + 1, 2)
		ct = 0
		@ftypes.each_key { |t|
			l = Gtk::Label.new
			l.set_markup("<span color='white'>" + @ftypes[t] + "</span>")
			a = Gtk::Alignment.new(0, 0, 0, 0.6)
			a.add l 
			c = Gtk::CheckButton.new
			c.active = true
			@checkbuttons[t] = c
			deltab.attach(a, 1, 2, ct, ct+1)
			deltab.attach(c, 0, 1, ct, ct+1)
			ct += 1
		}
		@allchecked = Gtk::CheckButton.new
		dellab = Gtk::Label.new
		dellab.set_markup("<span color='white'>Alle wiederherstellbaren Dateitypen</span>")
		delalg = Gtk::Alignment.new(0, 0, 0, 0.6)
		delalg.add dellab
		deltab.attach(@allchecked, 0, 1, ct, ct+1)
		deltab.attach(delalg, 1, 2, ct, ct+1)
		@allchecked.signal_connect("clicked") { 
			if @allchecked.active? 
				@checkbuttons.each { |k,v| 
					v.active = true
					v.sensitive = false
				}
			else
				@checkbuttons.each { |k,v|  v.sensitive = true }
			end
		}
		
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
			forw.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				if @drivelist.size < 1 
					TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
				else
					@selectedsource = @drivelist[@drivecombo.active].device
					@targetdrives, @targetlist = reread_targetlist(@targetcombo)
					if @targetlist.size < 1 
						TileHelpers.error_dialog("Es wurden keine geeigneten Laufwerke als Ziel für die Datenrettung gefunden. Bitte ggf. USB-Laufwerke anschließen und auf \"Neu einlesen\" klicken.", "Keine Laufwerke gefunden")
					else
						extlayers.each { |k,v|v.hide_all }
						extlayers["photorectarget"].show_all
					end
				end
			end
		}
		fixed.put(orangetile, 0, 0)
		fixed.put(deltext, 130, 0)
		fixed.put(forw, 853, 282)
		fixed.put(text5, 605, 288)
		fixed.put(deltab, 130, 33)
		return fixed
	end
	
	def create_target_screen(extlayers)
		fixed = Gtk::Fixed.new
		TileHelpers.place_back(fixed, extlayers)
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		text5 = Gtk::Label.new
		text5.width_request = 230
		text5.wrap = true
		text5.set_markup("<span color='white'>Klicken Sie hier, um die Wiederherstellung zu starten.</span>")
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
		deltext = TileHelpers.create_label("<b>Ziel-Laufwerk auswählen</b>\n\nAuf welchem Laufwerk sollen die gefundenen Dateien gespeichert werden? Da alle zu Ihrer Auswahl passenden Dateien wiederhergestellt werden, wird die Datenmenge möglicherweise sehr groß. Das Ziellaufwek muss daher über ausreichend Speicherplatz verfügen. Da Quell- und Ziellaufwerk nicht identisch sein dürfen, wird das Quelllaufwerk hier nicht angezeigt.\n\nFalls Sie die wiederhergestellten Daten auf ein externes Laufwerk speichern möchten, schließen Sie es jetzt an und klicken dann auf \"Neu einlesen\".", 760)
		@targetcombo = Gtk::ComboBox.new
		@targetcombo.height_request = 32
		@targetcombo.width_request = 380
		reread = Gtk::Button.new("Neu einlesen")
		reread.width_request = 120
		reread.height_request = 32
		forw.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				extlayers.each { |k,v|v.hide_all }
				extlayers["photorecprog"].show_all
				run_photorec
				extlayers.each { |k,v|v.hide_all }
				extlayers["photorecsummary"].show_all
			end
		}
		reread.signal_connect('clicked') {
			@targetdrives, @targetlist = reread_targetlist(@targetcombo)
		}
		fixed.put(@targetcombo, 130, 150)
		fixed.put(reread, 520, 150)
		fixed.put(orangetile, 0, 0)
		fixed.put(deltext, 130, 0)
		fixed.put(forw, 853, 282)
		fixed.put(text5, 605, 288)
		return fixed
	end
	
	def create_progress_screen(extlayers)
		fixed = Gtk::Fixed.new
		cancelbutton = TileHelpers.place_back(fixed, extlayers, false)
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		@photorec_progress = Gtk::ProgressBar.new
		@photorec_progress.width_request = 510
		@photorec_progress.height_request = 32
		progresstext = TileHelpers.create_label("<b>Suche läuft</b>\n\nDie Suche nach wiederherstellbaren Dateien läuft. Sie kann je nach Laufwerksgröße, Lesegeschwindigkeit und Auswahl der Dateitypen wenige Minuten bis mehrere Stunden dauern. Der Fortschrittsbalken gibt Auskunft über die Anzahl wiederhergestellter Dateien.", 510)
		@photorec_vte.set_font("fixed 6")
		@photorec_exp = Gtk::Expander.new('')
		@photorec_exp.add @photorec_vte
		@photorec_exp.expanded = false
		@photorec_exp.label_widget = TileHelpers.create_label("Details", 150)
		cancelbutton.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				system("killall photorec")
				system("killall -9 photorec") 
				TileHelpers.error_dialog("Die Suche nach verlorenen Dateien wurde abgebrochen!", "Suche abgebrochen") 
			end
		}
		fixed.put(@photorec_progress, 130, 108)
		fixed.put(progresstext, 130, 0)
		fixed.put(orangetile, 0, 0)
		fixed.put(@photorec_exp, 130, 153)
		
		return fixed
	end
	
	def create_summary_screen(extlayers)
		fixed = Gtk::Fixed.new
		TileHelpers.place_back(fixed, extlayers)
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		summary = TileHelpers.create_label("<b>Wiederherstellung abgeschlossen</b>\n\nDie Wiederherstellung der Dateien ist abgeschlossen. Sie finden Ihre Daten nun auf dem Ziellaufwerk in fortlaufend nummerierten Ordnern \"Recovery\".", 510)
		recovery = Gtk::Image.new("recovery.png")
		fixed.put(recovery, 130, 130)
		fixed.put(orangetile, 0, 0)
		fixed.put(summary, 130, 0)
		return fixed
	end
	
	def reread_drivelist(combobox, usbonly=false)
		TileHelpers.umount_all
		100.downto(0) { |i| 
			begin
				combobox.remove_text(i) 
			rescue
			end
		}
		drives = Array.new
		auxdrives = Array.new
		Dir.entries("/sys/block").each { |l|
			if l =~ /[a-z]$/ 
				d = MfsDiskDrive.new(l, false)
				auxdrives.push(d) unless d.mounted  
			end
		}
		if usbonly == true
			auxdrives.each { |d| drives.push d if d.usb == true }  
		else
			drives = auxdrives
		end
		
		drives.each { |d|
			label = label = "#{d.vendor} #{d.model} - #{d.human_size} - #{d.device}"
			if d.usb == true
				label = label + " (USB)"
			else
				label = label + " (SATA/eSATA/IDE)"
			end
			combobox.append_text(label)
		}
		combobox.active = 0
		return drives
	end
	
	def reread_targetlist(combobox)
		100.downto(0) { |i| 
			begin
				combobox.remove_text(i) 
			rescue
			end
		}
		drives = Array.new
		partitions = Array.new
		Dir.entries("/sys/block").each { |l|
			drives.push(MfsDiskDrive.new(l, false)) if l =~ /[a-z]$/ 
		}
		drives.each { |d|
			d.partitions.each  { |p|
				label = label = "#{d.vendor} #{d.model}"
				if d.usb == true
					label = label + " (USB) "
				else
					label = label + " (SATA/eSATA/IDE) "
				end
				label = label + "#{p.device} #{p.human_size}"
				
				if ( p.fs =~ /vfat/ || p.fs =~ /ntfs/ || p.fs =~ /ext/ || p.fs =~ /btrfs/ ) && p.mount_point.nil? 
					label = label + " #{p.fs}"
					partitions.push p
					combobox.append_text(label)
				end
			} unless d.device == @selectedsource
		}
		combobox.active = 0
		return drives, partitions
	end
	
	def run_photorec
		filegroups = Array.new
		suffixes = Array.new
		@ftypes.each { |k,v|
			puts k + " " + @checkbuttons[k].active?.to_s
			filegroups.push(k) if @checkbuttons[k].active?
		}
		if @mobilemode == false
			source = @drivelist[@drivecombo.active]
			
		else
			source = @mobilelist[@mobilecombo.active]
			filegroups = [ "video", "image" ] 
		end
		if !@allchecked.active? || @mobilemode 
			puts "Reading file"
			File.open("photorec_suffixes.txt").each { |l|
				ltoks = l.split
				suffixes.push ltoks[0].strip if filegroups.include?(ltoks[1]) 
			}
		end
		target = @targetlist[@targetcombo.active]
		targetdir = "/cobi/photorec"
		system("mkdir -p #{targetdir}")
		target.mount("rw", targetdir) 
		#puts "filegroups? " + filegroups.join(", ")
		#puts "source?     " + source.device
		#puts "target?     " + target.device 
		#puts "allchecked? " + @allchecked.active?.to_s 
		#puts "mobilemode? " + @mobilemode.to_s 
		#puts "suffixes?   " + suffixes.join(", ")
		@photorec_running = true
		params = [ "photorec" ]
		recupdir = Time.now.strftime("Recovery-%Y%m%d-%H%M%S")
		fileopts = "everything,disable" 
		suffixes.uniq.each { |p|
			fileopts = fileopts + "," + p + ",enable"
		}
		fileopts = "everything,enable" if @allchecked.active? && @mobilemode == false
		fileopts = "everything,enable" if filegroups.size == 0
		params = [ "photorec", "/d", targetdir + "/" + recupdir, "/cmd", "/dev/#{source.device}",
			"partition_none,fileopt," + fileopts + ",search" ]
		puts params.join(" ") 
		@photorec_vte.fork_command("photorec", params )
		sleepcount = 0
		@photorec_progress.text = "Die Datenrettung läuft..."
		while @photorec_running == true
			@photorec_progress.pulse
			sleep 0.2
			if sleepcount % 50 == 20
				filecount = 0
				Dir.entries(targetdir).each { |d|
					if d =~ Regexp.new(recupdir)
						Dir.entries(targetdir + "/" + d).each { |f|
							filecount += 1 if File.file?(targetdir + "/" + d + "/" + f)
						}
					end
				}
				@photorec_progress.text = "#{filecount.to_s} Dateien wiederhergestellt"
			end
			while (Gtk.events_pending?)
				Gtk.main_iteration
			end
			sleepcount += 1 
		end
		@photorec_progress.text = "Die Datenrettung ist abgeschlossen."
		df_out = ` df -k #{targetdir} | tail -n1 `
		freespace = df_out.split[3].to_i
		if freespace < 100_000
			@photorec_exp.expanded = true
			TileHelpers.error_dialog("Der Platz auf dem Zieldatenträger ist knapp. Bitte prüfen Sie im Fenster \"Details\", ob die Wiederherstellung erfolgreich beendet wurde.")
			@photorec_exp.expanded = false
		end
		target.umount
		target.force_umount
		
	end
end