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

require 'exifr'
require 'fileutils'
require 'id3tag'

class PhotorecScreen	
	def initialize(extlayers, buttons)
		lang = ENV['LANGUAGE'][0..1]
		lang = ENV['LANG'][0..1] if lang.nil?
		lang = "en" if lang.nil?
		@tl = MfsTranslator.new(lang, "PhotorecScreen.xml")
		@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" => @tl.get_translation("ftypeoffice"),
			"image" => @tl.get_translation("ftypeimage"),
			"video" => @tl.get_translation("ftypevideo"),
			"audio" => @tl.get_translation("ftypeaudio"),
			"mail" => @tl.get_translation("ftypeemail"),
			"archive" => @tl.get_translation("ftypearchive")
		}
		### 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'>" + @tl.get_translation("gotoftypes") + "</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(@tl.get_translation("reread"))
		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>" + @tl.get_translation("sourcehead") + "</b>\n\n" +@tl.get_translation("sourcebody") , 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
	attr_accessor :drivecombo, :drivelist
	
	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'>" + @tl.get_translation("gotophotorec") + "</span>")
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
		deltext = TileHelpers.create_label("<b>" + @tl.get_translation("sourcehead") + "</b>\n\n" + @tl.get_translation("sourcemobile") , 510)
		@mobilecombo = Gtk::ComboBox.new
		@mobilecombo.height_request = 32
		@mobilecombo.width_request = 380
		reread = Gtk::Button.new(@tl.get_translation("reread"))
		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(@tl.get_translation("nosuitabledrives"), @tl.get_translation("nosuitablehead"))
				else
					@selectedsource = @mobilelist[@mobilecombo.active].device
					@targetdrives, @targetlist = reread_targetlist(@targetcombo)
					if @targetlist.size < 1 
						TileHelpers.error_dialog(@tl.get_translation("nosuitabletarget"), @tl.get_translation("nosuitablehead"))
					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'>" + @tl.get_translation("gototarget") + "</span>")
		orangetile = Gtk::EventBox.new.add Gtk::Image.new("photorecgreen.png")
		deltext = TileHelpers.create_label("<b>" + @tl.get_translation("ftypeshead") + "</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'>" + @tl.get_translation("ftypesall") + "</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(@tl.get_translation("nosuitabledrives"), @tl.get_translation("nosuitablehead"))
				else
					@selectedsource = @drivelist[@drivecombo.active].device
					@targetdrives, @targetlist = reread_targetlist(@targetcombo)
					if @targetlist.size < 1 
						TileHelpers.error_dialog(@tl.get_translation("nosuitabletarget"), @tl.get_translation("nosuitablehead"))
					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'>" + @tl.get_translation("gotophotorec") + "</span>")
		sortradio = Gtk::RadioButton.new
		sortlabel = TileHelpers.create_label(@tl.get_translation("sortfiles"), 400)
		forw  = Gtk::EventBox.new.add Gtk::Image.new("forward.png")
		deltext = TileHelpers.create_label("<b>" + @tl.get_translation("targethead") + "</b>\n\n" + @tl.get_translation("targetbody"), 760)
		@targetcombo = Gtk::ComboBox.new
		@targetcombo.height_request = 32
		@targetcombo.width_request = 380
		reread = Gtk::Button.new(@tl.get_translation("reread"))
		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(sortradio.active?)
				extlayers.each { |k,v|v.hide_all }
				extlayers["photorecsummary"].show_all
			end
		}
		reread.signal_connect('clicked') {
			@targetdrives, @targetlist = reread_targetlist(@targetcombo)
		}
		fixed.put(sortradio, 130, 150)
		fixed.put(sortlabel, 156, 156)
		fixed.put(@targetcombo, 130, 182)
		fixed.put(reread, 520, 182)
		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>" + @tl.get_translation("rescuehead") + "</b>\n\n" + @tl.get_translation("rescuebody"), 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(@tl.get_translation("details"), 150)
		cancelbutton.signal_connect('button-release-event') { |x, y|
			if y.button == 1 
				system("killall photorec")
				system("killall -9 photorec") 
				TileHelpers.error_dialog(@tl.get_translation("cancelledbody"), @tl.get_translation("cancelledhead")) 
			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>" + @tl.get_translation("successhead") + "</b>\n\n"+ @tl.get_translation("successbody") , 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, blockdevice=nil)
		TileHelpers.umount_all if blockdevice.nil? 
		100.downto(0) { |i| 
			begin
				combobox.remove_text(i) 
			rescue
			end
		}
		drives = Array.new
		auxdrives = Array.new
		if blockdevice.nil?
			Dir.entries("/sys/block").each { |l|
				if l =~ /[a-z]$/ 
					d = MfsDiskDrive.new(l, false)
					auxdrives.push(d) unless d.mounted  
				end
			}
		else
			auxdrives.push MfsDiskDrive.new(blockdevice, false)
		end
		if usbonly == true
			auxdrives.each { |d| drives.push d if d.usb == true }  
		else
			drives = auxdrives
		end
		
		drives.each { |d|
			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}"
				pmnt = p.mount_point
				if ( p.fs =~ /vfat/ || p.fs =~ /ntfs/ || p.fs =~ /ext/ || p.fs =~ /btrfs/ ) && ( pmnt.nil? || pmnt[1].include?("rw") )
					unless p.label == "LessLinuxBlob"
						label = label + " #{p.fs}"
						label = "Backup-Medium " + label if p.label == "USBDATA"
						partitions.push p
						combobox.append_text(label)
					end
				end
			} unless d.device == @selectedsource
		}
		combobox.active = 0
		return drives, partitions
	end
	
	def run_photorec(sortfiles=true)
		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"
		tgt_mounted = true unless target.mount_point.nil?
		targetdir = target.mount_point[0] if tgt_mounted == true
		system("mkdir -p #{targetdir}")
		target.mount("rw", targetdir) unless tgt_mounted == true
		#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
		FileUtils::mkdir_p targetdir + "/" + recupdir
		params = [ "photorec", "/d", targetdir + "/" + recupdir + "/rettung" , "/cmd", "/dev/#{source.device}",
			"partition_none,fileopt," + fileopts + ",search" ]
		puts params.join(" ") 
		@photorec_vte.fork_command("photorec", params )
		sleepcount = 0
		@photorec_progress.text = @tl.get_translation("rescuehead") 
		while @photorec_running == true
			@photorec_progress.pulse
			sleep 0.2
			if sleepcount % 50 == 20
				filecount = 0
				Dir.entries(targetdir + "/" + recupdir).each { |d|
					if d =~ /rettung/ 
						Dir.entries(targetdir + "/" + recupdir + "/" + d).each { |f|
							filecount += 1 if File.file?(targetdir + "/" + recupdir + "/" + d + "/" + f)
						}
					end
				}
				@photorec_progress.text = @tl.get_translation("pgcount").gsub("FILECOUNT", filecount.to_s)  
			end
			while (Gtk.events_pending?)
				Gtk.main_iteration
			end
			sleepcount += 1 
		end
		# Now sort files if requested
		sortfiles(targetdir + "/" + recupdir) 
		@photorec_progress.text = @tl.get_translation("pgdone")
		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(@tl.get_translation("spaceproblem"))
			@photorec_exp.expanded = false
		end
		target.umount unless tgt_mounted == true
		target.force_umount unless tgt_mounted == true
		
	end
	
	def sortfiles(startfolder)
		traverse_dir(startfolder, startfolder, @photorec_progress)
	end
	
	def traverse_dir(startdir, basedir, pgbar)
		return true if startdir == "#{basedir}/sortiert"
		now = Time.now.to_f 
		if now > $lastpulse + 0.3
			pgbar.pulse
			$lastpulse = now
		end
		while (Gtk.events_pending?)
			Gtk.main_iteration
		end
		Dir.entries(startdir).each { |e|
			puts "Parsing #{startdir}/#{e}"
			if e == "." || e == ".."
				puts "Ignore directory #{e}"
			elsif File.symlink? "#{startdir}/#{e}"
				puts "Ignore symlink #{e}"
			elsif File.directory? "#{startdir}/#{e}" 
				traverse_dir("#{startdir}/#{e}", basedir, pgbar) 
			elsif File.file? "#{startdir}/#{e}"
				rename_file("#{startdir}/#{e}", basedir, pgbar) 
			end
		}
	end

	def rename_file(filepath, basedir, pgbar) 
		now = Time.now.to_f
		if now > $lastpulse + 0.3
			pgbar.pulse
			$lastpulse = now
		end
		while (Gtk.events_pending?)
			Gtk.main_iteration
		end
		if filepath =~ /\.jpg$/i || filepath =~ /\.jpeg$/i
			img = EXIFR::JPEG.new(filepath)
			# Try to read some exif infos
			if img.exif? && !img.date_time.nil? && img.model.to_s != "" && (img.width + img.height > 1119)
				puts "Renaming #{filepath}"
				begin
					puts img.model
					puts img.width.to_s + " " + img.height.to_s
					puts img.date_time.to_i.to_s
					dest = "#{basedir}/sortiert/Fotos/#{img.date_time.year}/#{img.date_time.month}/#{img.model}"
					FileUtils::mkdir_p dest 
					if $actionmove == true
						FileUtils::mv(filepath, "#{dest}/#{img.date_time.year}_#{img.date_time.month}_#{img.date_time.day}_#{File.basename(filepath)}" )
					else
						FileUtils::cp(filepath, "#{dest}/#{img.date_time.year}_#{img.date_time.month}_#{img.date_time.day}_#{File.basename(filepath)}" )
					end
				rescue 
					puts "Broken EXIF tag!"
				end
			end
		elsif filepath =~ /\.mp3$/
			audio = File.open(filepath, "r") 
			tag = ID3Tag.read(audio)
			puts "Renaming #{filepath}"
			artist = tag.artist.to_s.gsub(":", ".").gsub("/", "_")
			artist = "uknown artist" if artist.to_s == ""
			album = tag.album.to_s.gsub(":", ".").gsub("/", "_")
			album = "uknown album" if album.to_s == ""
			begin
				title = tag.title.to_s.gsub(":", ".").gsub("/", "_")
			rescue 
				title = ` uuidgen `
			end
			if title.to_s == ""
				title = "uknown title" 
			end
			begin
				if tag.track_nr.to_i > 0
					title = tag.track_nr.to_i.to_s + " " + title
				end
			rescue
			end
			title = title + " " + File.basename(filepath)
			begin
				if tag.year.to_i > 0
					album = tag.year.to_i.to_s + " " + album
				end
			rescue
			end
			dest = "#{basedir}/sortiert/MP3/#{artist}/#{album}"
			FileUtils::mkdir_p dest 
			audio.close
			FileUtils::cp(filepath, "#{dest}/#{title}" )
		end
	end
end