diff --git a/library/system/src/Makefile.am b/library/system/src/Makefile.am index 4a72f1e87..e22bf1371 100644 --- a/library/system/src/Makefile.am +++ b/library/system/src/Makefile.am @@ -36,7 +36,13 @@ ylib_DATA = \ lib/yast2/target_file.rb \ lib/yast2/system_time.rb +ylibclientsdir = @ylibdir@/yast2/clients +ylibclients_DATA = \ + lib/yast2/clients/view_anymsg.rb -EXTRA_DIST = $(module_DATA) $(client_DATA) $(ynclude_DATA) $(scrconf_DATA) $(desktop_DATA) $(ylib_DATA) + + + +EXTRA_DIST = $(module_DATA) $(client_DATA) $(ynclude_DATA) $(scrconf_DATA) $(desktop_DATA) $(ylib_DATA) $(ylibclients_DATA) include $(top_srcdir)/Makefile.am.common diff --git a/library/system/src/clients/view_anymsg.rb b/library/system/src/clients/view_anymsg.rb index 88378d652..fc56429b3 100644 --- a/library/system/src/clients/view_anymsg.rb +++ b/library/system/src/clients/view_anymsg.rb @@ -21,265 +21,7 @@ # you may find current contact information at www.novell.com # # *************************************************************************** -# view_anymsg.ycp -# -# small script for easy /var/log/* and /proc/* viewing -# -# Author: Klaus Kaempf -# -# $Id$ - -require "yast/core_ext" - -# Reads a \n separated list of filenames from -# /var/lib/YaST2/filenames -# Lines starting with "#" are ignored (comments) -# A line starting with "*" is taken as the default filename, the "*" is stripped -# -# All files are listed in an editable combo box, where the user can -# easily switch between files and even add a new file -# -# At finish, the list of filenames is written back to -# /var/lib/YaST2/filenames -# adapting the default line (starting with "*") accordingly. -# -# The default is either given as WFM::Args(0) or is the file last viewed. -module Yast - class ViewAnymsgClient < Client - using Yast::CoreExt::AnsiString - - # [String] Default list of log files - DEFAULT_FILENAMES = [ - "/var/log/boot.log", - "/var/log/messages", - "/var/log/YaST2/y2log" - ].freeze - - def main - Yast.import "UI" - textdomain "base" - - Yast.import "CommandLine" - Yast.import "Directory" - Yast.import "FileUtils" - Yast.import "Label" - - @vardir = Directory.vardir - - # Check if the filename list is present - if !FileUtils.Exists(Ops.add(@vardir, "/filenames")) - SCR.Execute( - path(".target.bash"), - Ops.add( - Ops.add( - Ops.add(Ops.add("/bin/cp ", Directory.ydatadir), "/filenames "), - @vardir - ), - "/filenames" - ) - ) - end - - # get filename list - @filenames = Convert.to_string( - SCR.Read(path(".target.string"), Ops.add(@vardir, "/filenames")) - ) - - @filenames ||= "" - - # convert \n separated string to ycp list. - - @all_files = Builtins.splitstring(@filenames, "\n") - @all_files |= DEFAULT_FILENAMES - - @set_default = false - @combo_files = [] - - # check if default given as argument - - @filename = "" - if Ops.greater_than(Builtins.size(WFM.Args), 0) && - Ops.is_string?(WFM.Args(0)) - @filename = Convert.to_string(WFM.Args(0)) - if @filename != "" - @combo_files = [Item(Id(@filename), @filename, true)] - @set_default = true - end - end - - # the command line description map - @cmdline = { "id" => "view_anymsg" } - return CommandLine.Run(@cmdline) if @filename == "help" - - # build up ComboBox - - Builtins.foreach(@all_files) do |name| - # empty lines or lines starting with "#" are ignored - if name != "" && Builtins.substring(name, 0, 1) != "#" - # the default is either given via WFM::Args() -> filename != "" - # or by a filename starting with "*" - if Builtins.substring(name, 0, 1) == "*" - name = Builtins.substring(name, 1) # strip leading "*" - if name != @filename # do not add it twice - @combo_files = Builtins.add( - @combo_files, - Item(Id(name), name, !@set_default) - ) - end - if !@set_default - @filename = name if @filename == "" - @set_default = true - end - elsif name != @filename # do not add it twice - @combo_files = Builtins.add(@combo_files, Item(Id(name), name)) - end - end - end - - if !@set_default && @filename != "" - @all_files = Builtins.add(@all_files, Ops.add("*", @filename)) - @combo_files = Builtins.add( - @combo_files, - Item(Id(@filename), @filename) - ) - end - - # set up dialogue - - UI.OpenDialog( - Opt(:decorated, :defaultsize), - VBox( - HSpacing(70), # force width - HBox( - HSpacing(1.0), - ComboBox( - Id(:custom_file), - Opt(:editable, :notify, :hstretch), - "", - @combo_files - ), - HStretch() - ), - VSpacing(0.3), - VWeight( - 1, - HBox( - VSpacing(18), # force height - HSpacing(0.7), - LogView( - Id(:log), - "", - 3, # height - 0 - ), # number of lines to show - HSpacing(0.7) - ) - ), - VSpacing(0.3), - PushButton(Id(:ok), Label.OKButton), - VSpacing(0.3) - ) - ) - - @go_on = true - - # wait until user clicks "OK" - # check if ComboBox selected and change view accordingly - - while @go_on - - # read file content - file_content = SCR.Read(path(".target.string"), @filename) - - if file_content - # replace invalid byte sequences with Unicode "replacement character" - file_content.scrub!("�") - # remove ANSI color escape sequences - file_content.remove_ansi_sequences - # remove remaining ASCII control characters (ASCII 0-31 and 127 (DEL)) - # except new line (LF = 0xa) and carriage return (CR = 0xd) - file_content.tr!("\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f", "") - else - file_content = _("File not found.") - end - - # Fill the LogView with file content - UI.ChangeWidget(Id(:log), :Value, file_content) - - heading = Builtins.sformat(_("System Log (%1)"), @filename) - UI.ChangeWidget(Id(:log), :Label, heading) - - # wait for user input - - @ret = Convert.to_symbol(UI.UserInput) - - # clicked "OK" -> exit - - if @ret == :ok - @go_on = false - elsif @ret == :cancel # close window - UI.CloseDialog - return true - elsif @ret == :custom_file - # adapt to combo box settings - - @new_file = Convert.to_string( - UI.QueryWidget(Id(:custom_file), :Value) - ) - @filename = @new_file if !@new_file.nil? - else - Builtins.y2milestone("bad UserInput (%1)", @ret) - end - end - - # write new list of filenames - - @new_files = [] - @set_default = false - - # re-build list to get new default correct - Builtins.foreach(@all_files) do |file| - if Builtins.substring(file, 0, 1) == "*" - old_default = Builtins.substring(file, 1) # strip leading "*" - if old_default == @filename # default unchanged - @new_files = Builtins.add(@new_files, file) - @set_default = true # new default - else - @new_files = Builtins.add(@new_files, old_default) - end - elsif file != "" - if file == @filename # mark new default - @new_files = Builtins.add(@new_files, Ops.add("*", @filename)) - @set_default = true - else - @new_files = Builtins.add(@new_files, file) - end - end - end - # if we don't have a default by now, it wasn't in the list before - # so add it here. - - if !@set_default && @filename != "" - @new_files = Builtins.add(@new_files, Ops.add("*", @filename)) - end - - @new_files = Builtins.toset(@new_files) - - # convert ycp list back to \n separated string - - @filenames = Ops.add(Builtins.mergestring(@new_files, "\n"), "\n") - - SCR.Write( - path(".target.string"), - Ops.add(@vardir, "/filenames"), - @filenames - ) - - UI.CloseDialog - true - end - end -end +require "yast2/clients/view_anymsg" Yast::ViewAnymsgClient.new.main diff --git a/library/system/src/lib/yast2/clients/view_anymsg.rb b/library/system/src/lib/yast2/clients/view_anymsg.rb new file mode 100644 index 000000000..692836c87 --- /dev/null +++ b/library/system/src/lib/yast2/clients/view_anymsg.rb @@ -0,0 +1,286 @@ +# encoding: utf-8 + +# *************************************************************************** +# +# Copyright (c) 2002 - 2012 Novell, Inc. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# 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, contact Novell, Inc. +# +# To contact Novell about this file by physical or electronic mail, +# you may find current contact information at www.novell.com +# +# *************************************************************************** + +require "yast/core_ext" + +require "yast2/popup" + +Yast.import "UI" +Yast.import "CommandLine" +Yast.import "Directory" +Yast.import "FileUtils" +Yast.import "Label" +Yast.import "Package" + +module Yast + # Reads a \n separated list of filenames from + # /var/lib/YaST2/filenames + # Lines starting with "#" are ignored (comments) + # A line starting with "*" is taken as the default filename, the "*" is stripped + # + # All files are listed in an editable combo box, where the user can + # easily switch between files and even add a new file + # + # At finish, the list of filenames is written back to + # /var/lib/YaST2/filenames + # adapting the default line (starting with "*") accordingly. + # + # The default is either given as WFM::Args(0) or is the file last viewed. + class ViewAnymsgClient < Client + using Yast::CoreExt::AnsiString + + # [String] Default list of log files + DEFAULT_FILENAMES = [ + "/var/log/boot.log", + "/var/log/messages", + "/var/log/YaST2/y2log" + ].freeze + + def main + textdomain "base" + + # the command line description map + return CommandLine.Run("id" => "view_anymsg") if WFM.Args.first == "help" + + # set up dialogue + UI.OpenDialog( + Opt(:decorated, :defaultsize), + dialog_content + ) + + # wait until user clicks "OK" + # check if ComboBox selected and change view accordingly + res = nil + + loop do + # Fill the LogView with file content + UI.ChangeWidget(Id(:log), :Value, file_content(selected_filename)) + + heading = Builtins.sformat(_("System Log (%1)"), selected_filename) + UI.ChangeWidget(Id(:log), :Label, heading) + + if start_journal? + res = :journal + break + end + + # wait for user input + res = UI.UserInput + + case res + when :ok, :cancel then break + when :custom_file + # adapt to combo box settings + new_file = UI.QueryWidget(Id(:custom_file), :Value) + self.selected_filename = new_file if !new_file.nil? + else + Builtins.y2milestone("bad UserInput (%1)", res) + end + end + + write_new_filenames if res == :ok + UI.CloseDialog + + Yast::WFM.CallFunction("journal") if res == :journal + + true + end + + private + + def start_journal? + return false unless [nil, 0, -1].include?(FileUtils.GetSize(selected_filename)) + + res = Yast2::Popup.show( + _( + "Selected log file does not exist or is empty.\n" \ + "Many system components now log into systemd journal.\n" \ + "Do you want to start YaST module for systemd journal?" + ), + buttons: :yes_no, + focus: :no + ) == :yes + + return false unless res + + Package.Install("yast2-journal") + end + + def dialog_content + VBox( + HSpacing(70), # force width + HBox( + HSpacing(1.0), + ComboBox( + Id(:custom_file), + Opt(:editable, :notify, :hstretch), + "", + combobox_items + ), + HStretch() + ), + VSpacing(0.3), + VWeight( + 1, + HBox( + VSpacing(18), # force height + HSpacing(0.7), + LogView( + Id(:log), + "", + 3, # height + 0 + ), # number of lines to show + HSpacing(0.7) + ) + ), + VSpacing(0.3), + PushButton(Id(:ok), Label.OKButton), + VSpacing(0.3) + ) + end + + def write_new_filenames + result = [] + + to_write = (available_filenames + [selected_filename]).uniq + + # re-build list to get new default correct + filenames_content.lines.each do |line| + line.strip! + result << line if line.empty? || line.start_with?("#") + + line = line[1..-1] if line.start_with?("*") + to_write.delete(line) # remember that we already write it + line = "*" + line if selected_filename == line + result << line + end + to_write.each do |line| + line = "*" + line if selected_filename == line + result << line + end + + SCR.Write( + path(".target.string"), + filenames_path, + result.join("\n") + ) + end + + def filenames_path + @filenames_path ||= ::File.join(Directory.vardir, "filenames") + end + + def ensure_filenames_exist + # Check if the filename list is present + return if FileUtils.Exists(filenames_path) + + SCR.Execute( + path(".target.bash"), + "/bin/cp #{::File.join(Directory.ydatadir, "filenames")} #{filenames_path}" + ) + end + + attr_writer :selected_filename + + def selected_filename + return @selected_filename if @selected_filename + + @selected_filename = default_filename + end + + def file_content(filename) + # read file content + result = SCR.Read(path(".target.string"), filename) + + if result + # replace invalid byte sequences with Unicode "replacement character" + result.scrub!("�") + # remove ANSI color escape sequences + result.remove_ansi_sequences + # remove remaining ASCII control characters (ASCII 0-31 and 127 (DEL)) + # except new line (LF = 0xa) and carriage return (CR = 0xd) + result.tr!("\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f", "") + else + result = _("File not found.") + end + + result + end + + def filenames_list + @filenames_list ||= filenames_content.lines.each_with_object([]) do |line, result| + line.strip! + next if line.empty? + next if line.start_with?("#") + + line = line[1..-1] if line.start_with?("*") + result << line + end + end + + def available_filenames + return @available_filenames if @available_filenames + + result = filenames_list + DEFAULT_FILENAMES + [arg_filename] + @available_filenames = result.uniq.compact + end + + def arg_filename + arg = WFM.Args.first + return arg if arg.is_a?(::String) && !arg.empty? + end + + def filenames_content + return @filenames_content if @filenames_content + + ensure_filenames_exist + + # get filename list + @filenames_content = Convert.to_string( + SCR.Read(path(".target.string"), filenames_path) + ) + + @filenames_content ||= "" + end + + def default_filename + return @default_filename if @default_filename + + return @default_filename = arg_filename if arg_filename + + default_line = filenames_content.lines.find { |l| l.start_with?("*") } + + return @default_filename = available_filenames.first unless default_line + + @default_filename = default_line[1..-1].strip + end + + def combobox_items + available_filenames.map do |filename| + Item(Id(filename), filename, filename == default_filename) + end + end + end +end diff --git a/library/system/test/Makefile.am b/library/system/test/Makefile.am index 3b54c3128..58867fac5 100644 --- a/library/system/test/Makefile.am +++ b/library/system/test/Makefile.am @@ -1,4 +1,5 @@ TESTS = \ + clients/view_anymsg_test.rb \ execute_test.rb \ kernel_test.rb \ hw_detection_test.rb \ diff --git a/library/system/test/clients/view_anymsg_test.rb b/library/system/test/clients/view_anymsg_test.rb new file mode 100755 index 000000000..1c166bce0 --- /dev/null +++ b/library/system/test/clients/view_anymsg_test.rb @@ -0,0 +1,207 @@ +#!/usr/bin/env rspec + +require_relative "../test_helper" + +require "yast2/clients/view_anymsg" + +describe Yast::ViewAnymsgClient do + describe "#main" do + before do + # UI mockup + allow(Yast::UI).to receive(:OpenDialog) + allow(Yast::UI).to receive(:ChangeWidget) + allow(Yast::UI).to receive(:QueryWidget) + allow(Yast::UI).to receive(:UserInput).and_return(:ok) + allow(Yast::UI).to receive(:CloseDialog) + + allow(Yast2::Popup).to receive(:show) + + # SCR mock + allow(Yast::SCR).to receive(:Execute) + allow(Yast::SCR).to receive(:Read) + allow(Yast::SCR).to receive(:Write) + + # WFM mock + allow(Yast::WFM).to receive(:Args).and_return([]) + allow(Yast::WFM).to receive(:CallFunction) + + allow(Yast::FileUtils).to receive(:GetSize).and_return(1) + end + + it "returns true" do + expect(subject.main).to eq true + end + + context "filenames list does not exist yet" do + it "copies filenames list from ydata dir to var dir" do + allow(Yast::FileUtils).to receive(:Exists).and_return(false) + expect(Yast::SCR).to receive(:Execute).with( + path(".target.bash"), + "/bin/cp /usr/share/YaST2/data/filenames /var/lib/YaST2/filenames" + ) + + subject.main + end + end + + it "shows help text for command line if help parameter is passed" do + allow(Yast::WFM).to receive(:Args).with(no_args).and_return(["help"]) + allow(Yast::WFM).to receive(:Args).with(0).and_return("help") + + expect(Yast::CommandLine).to receive(:Run).and_return(true) + + subject.main + end + + context "filenames list" do + def combobox_items(&block) + expect(Yast::UI).to receive(:OpenDialog) do |_opts, term| + items = term.nested_find do |i| + i.is_a?(::Array) && i.first.is_a?(Yast::Term) && i.first.value == :item + end + + expect(items).to(be_a(Array), "Not found ComboBox items #{term.inspect}") + + block.call(items) + end + end + + def expect_to_include(*filenames) + combobox_items do |items| + filenames.each do |path| + result = items.find do |i| + i.value == :item && i.params.include?(path) + end + + expect(result).to_not(eq(nil), "Not found path '#{path}' in Items - #{items.inspect}") + end + end + end + + it "reads filenames list from var dir" do + expect(Yast::SCR).to receive(:Read).with(path(".target.string"), "/var/lib/YaST2/filenames") + .and_return("") + + subject.main + end + + it "uses empty list if read failed" do + expect(Yast::SCR).to receive(:Read).with(path(".target.string"), "/var/lib/YaST2/filenames") + .and_return(nil) + + subject.main + end + + it "merge list with default files" do + expect_to_include(*Yast::ViewAnymsgClient::DEFAULT_FILENAMES) + + subject.main + end + + it "adds to ComboBox all file from filenames list" do + filenames = ["/tmp/test", "/tmp/lest"] + expect(Yast::SCR).to receive(:Read).with(path(".target.string"), "/var/lib/YaST2/filenames") + .and_return(filenames.join("\n")) + expect_to_include(*filenames) + + subject.main + end + + it "skips lines with # at the beginning" do + filenames = ["/tmp/test", "#/tmp/lest"] + + combobox_items do |items| + items.each do |item| + value = item.params[1] + expect(value).to_not match(/lest/) + end + end + + expect(Yast::SCR).to receive(:Read).with(path(".target.string"), "/var/lib/YaST2/filenames") + .and_return(filenames.join("\n")) + + subject.main + end + + it "strips the leading * and mark item as default" do + filenames = ["/tmp/test", "*/tmp/lest"] + expect(Yast::SCR).to receive(:Read).with(path(".target.string"), "/var/lib/YaST2/filenames") + .and_return(filenames.join("\n")) + + combobox_items do |items| + res = items.find do |item| + item.params[1] == "/tmp/lest" + end + + expect(res).to_not(be_nil, "Not found /tmp/lest item in #{items.inspect}") + expect(res.params[2]).to(eq(true), "Item is not marked as default #{items.inspect}") + end + + subject.main + end + + it "writes new default to filenames list" do + allow(Yast::UI).to receive(:UserInput).and_return(:custom_file, :ok) + allow(Yast::UI).to receive(:QueryWidget).with(Id(:custom_file), :Value) + .and_return("/var/log/boot.log") + + expect(Yast::SCR).to receive(:Write) do |scr_path, filepath, content| + expect(scr_path).to eq path(".target.string") + expect(filepath).to eq "/var/lib/YaST2/filenames" + expect(content).to include("*/var/log/boot.log") + end + + subject.main + end + + it "writes new custom file to filenames list" do + allow(Yast::UI).to receive(:UserInput).and_return(:custom_file, :ok) + allow(Yast::UI).to receive(:QueryWidget).with(Id(:custom_file), :Value) + .and_return("/var/log/not_seen_before.log") + + expect(Yast::SCR).to receive(:Write) do |scr_path, filepath, content| + expect(scr_path).to eq path(".target.string") + expect(filepath).to eq "/var/lib/YaST2/filenames" + expect(content).to include("*/var/log/not_seen_before.log") + end + + subject.main + + end + + it "does not write anything when user click cancel" do + allow(Yast::UI).to receive(:UserInput).and_return(:cancel) + + expect(Yast::SCR).to_not receive(:Write) + + subject.main + end + end + + context "log file does not exist or is empty" do + before do + allow(Yast::FileUtils).to receive(:GetSize).and_return(-1) + allow(Yast::Package).to receive(:Install).and_return(true) + allow(Yast2::Popup).to receive(:show).and_return(:yes) + end + + it "ask if open journal instead" do + expect(Yast2::Popup).to receive(:show).and_return(:no) + + subject.main + end + + it "ensures yast2-journal is installed" do + expect(Yast::Package).to receive(:Install).and_return(true) + + subject.main + end + + it "switches to yast2-journal module if user want and all goes well" do + expect(Yast::WFM).to receive(:CallFunction).with("journal") + + subject.main + end + end + end +end diff --git a/package/yast2.changes b/package/yast2.changes index 4d26ab870..cd7a74b06 100644 --- a/package/yast2.changes +++ b/package/yast2.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Fri Oct 26 11:46:16 UTC 2018 - jreidinger@suse.com + +- view_anymsg: allow user to switch to yast2-journal if file does + not exist or is empty (bsc#948729) +- 4.1.32 + ------------------------------------------------------------------- Wed Oct 24 16:55:08 UTC 2018 - schubi@suse.de diff --git a/package/yast2.spec b/package/yast2.spec index d0d7419c8..73886aff5 100644 --- a/package/yast2.spec +++ b/package/yast2.spec @@ -17,7 +17,7 @@ Name: yast2 -Version: 4.1.31 +Version: 4.1.32 Release: 0 Summary: YaST2 - Main Package License: GPL-2.0-only