From 4bb6236d1f08bc02e98d9da0b61e6630b9d750b3 Mon Sep 17 00:00:00 2001 From: Sandroid75 Date: Tue, 27 Dec 2022 22:28:17 +0100 Subject: [PATCH 01/18] improve dir and style add src dir add .clang-format add VSCode JSON files --- .clang-format | 706 +++++++++++++++++ .vscode/launch.json | 27 + .vscode/tasks.json | 32 + rover.c | 1502 ------------------------------------ config.h => src/config.h | 56 +- src/rover.c | 1562 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 2355 insertions(+), 1530 deletions(-) create mode 100644 .clang-format create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json delete mode 100644 rover.c rename config.h => src/config.h (67%) create mode 100644 src/rover.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..000e206 --- /dev/null +++ b/.clang-format @@ -0,0 +1,706 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +BasedOnStyle: GNU +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: + Enabled: true + Consecutive: true + AcrossEmptyLines: false + AcrossComments: false + AcrossEmptyLinesAndComments: false +AlignConsecutiveDeclarations: + Enabled: true + Consecutive: true + AcrossEmptyLines: false + AcrossComments: false + AcrossEmptyLinesAndComments: false +AlignConsecutiveMacros: + Enabled: true + Consecutive: true + AcrossEmptyLines: false + AcrossComments: false + AcrossEmptyLinesAndComments: false +AlignConsecutiveStyle: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | LC_ALL=C sort -u +ForEachMacros: + - '__ata_qc_for_each' + - '__bio_for_each_bvec' + - '__bio_for_each_segment' + - '__evlist__for_each_entry' + - '__evlist__for_each_entry_continue' + - '__evlist__for_each_entry_from' + - '__evlist__for_each_entry_reverse' + - '__evlist__for_each_entry_safe' + - '__for_each_mem_range' + - '__for_each_mem_range_rev' + - '__for_each_thread' + - '__hlist_for_each_rcu' + - '__map__for_each_symbol_by_name' + - '__perf_evlist__for_each_entry' + - '__perf_evlist__for_each_entry_reverse' + - '__perf_evlist__for_each_entry_safe' + - '__rq_for_each_bio' + - '__shost_for_each_device' + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - 'ata_qc_for_each' + - 'ata_qc_for_each_raw' + - 'ata_qc_for_each_with_internal' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - 'bio_for_each_bvec' + - 'bio_for_each_bvec_all' + - 'bio_for_each_folio_all' + - 'bio_for_each_integrity_vec' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'bpf__perf_for_each_map' + - 'bpf__perf_for_each_map_named' + - 'bpf_for_each_spilled_reg' + - 'bpf_object__for_each_map' + - 'bpf_object__for_each_program' + - 'bpf_object__for_each_safe' + - 'bpf_perf_object__for_each' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_efficient_entry_idx' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'damon_for_each_region' + - 'damon_for_each_region_safe' + - 'damon_for_each_scheme' + - 'damon_for_each_scheme_safe' + - 'damon_for_each_target' + - 'damon_for_each_target_safe' + - 'data__for_each_file' + - 'data__for_each_file_new' + - 'data__for_each_file_start' + - 'device_for_each_child_node' + - 'displayid_iter_for_each' + - 'dma_fence_array_for_each' + - 'dma_fence_chain_for_each' + - 'dma_fence_unwrap_for_each' + - 'dma_resv_for_each_fence' + - 'dma_resv_for_each_fence_unlocked' + - 'do_for_each_ftrace_op' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_atomic_for_each_plane_damage' + - 'drm_client_for_each_connector_iter' + - 'drm_client_for_each_modeset' + - 'drm_connector_for_each_possible_encoder' + - 'drm_for_each_bridge_in_chain' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_crtc_reverse' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_for_each_privobj' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'dsa_switch_for_each_available_port' + - 'dsa_switch_for_each_cpu_port' + - 'dsa_switch_for_each_port' + - 'dsa_switch_for_each_port_continue_reverse' + - 'dsa_switch_for_each_port_safe' + - 'dsa_switch_for_each_user_port' + - 'dsa_tree_for_each_user_port' + - 'dso__for_each_symbol' + - 'dsos__for_each_with_build_id' + - 'elf_hash_for_each_possible' + - 'elf_section__for_each_rel' + - 'elf_section__for_each_rela' + - 'elf_symtab__for_each_symbol' + - 'evlist__for_each_cpu' + - 'evlist__for_each_entry' + - 'evlist__for_each_entry_continue' + - 'evlist__for_each_entry_from' + - 'evlist__for_each_entry_reverse' + - 'evlist__for_each_entry_safe' + - 'flow_action_for_each' + - 'for_each_acpi_dev_match' + - 'for_each_active_dev_scope' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_aggr_pgid' + - 'for_each_available_child_of_node' + - 'for_each_bench' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_btf_ext_rec' + - 'for_each_btf_ext_sec' + - 'for_each_bvec' + - 'for_each_card_auxs' + - 'for_each_card_auxs_safe' + - 'for_each_card_components' + - 'for_each_card_dapms' + - 'for_each_card_pre_auxs' + - 'for_each_card_prelinks' + - 'for_each_card_rtds' + - 'for_each_card_rtds_safe' + - 'for_each_card_widgets' + - 'for_each_card_widgets_safe' + - 'for_each_cgroup_storage_type' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_clear_bitrange' + - 'for_each_clear_bitrange_from' + - 'for_each_cmd' + - 'for_each_cmsghdr' + - 'for_each_collection' + - 'for_each_comp_order' + - 'for_each_compatible_node' + - 'for_each_component_dais' + - 'for_each_component_dais_safe' + - 'for_each_console' + - 'for_each_console_srcu' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dapm_widgets' + - 'for_each_dedup_cand' + - 'for_each_dev_addr' + - 'for_each_dev_scope' + - 'for_each_dma_cap_mask' + - 'for_each_dpcm_be' + - 'for_each_dpcm_be_rollback' + - 'for_each_dpcm_be_safe' + - 'for_each_dpcm_fe' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_element' + - 'for_each_element_extid' + - 'for_each_element_id' + - 'for_each_endpoint_of_node' + - 'for_each_event' + - 'for_each_event_tps' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_pfn_range_in_zone' + - 'for_each_free_mem_pfn_range_in_zone_from' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_group_evsel' + - 'for_each_group_member' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_inject_fn' + - 'for_each_insn' + - 'for_each_insn_prefix' + - 'for_each_intid' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_lang' + - 'for_each_link_codecs' + - 'for_each_link_cpus' + - 'for_each_link_platforms' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_mem_pfn_range' + - 'for_each_mem_range' + - 'for_each_mem_range_rev' + - 'for_each_mem_region' + - 'for_each_member' + - 'for_each_memory' + - 'for_each_migratetype_order' + - 'for_each_missing_reg' + - 'for_each_net' + - 'for_each_net_continue_reverse' + - 'for_each_net_rcu' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_continue_reverse' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_mst_mgr_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_plane_in_state_reverse' + - 'for_each_new_private_obj_in_state' + - 'for_each_new_reg' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_nonreserved_multicast_dest_pgid' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_cpu_node' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_old_mst_mgr_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_mst_mgr_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_plane_in_state_reverse' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_path' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pcm_streams' + - 'for_each_physmem_range' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_probe_cache_entry' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_prop_codec_conf' + - 'for_each_prop_dai_codec' + - 'for_each_prop_dai_cpu' + - 'for_each_prop_dlc_codecs' + - 'for_each_prop_dlc_cpus' + - 'for_each_prop_dlc_platforms' + - 'for_each_property_of_node' + - 'for_each_reg' + - 'for_each_reg_filtered' + - 'for_each_registered_fb' + - 'for_each_requested_gpio' + - 'for_each_requested_gpio_in_range' + - 'for_each_reserved_mem_range' + - 'for_each_reserved_mem_region' + - 'for_each_rtd_codec_dais' + - 'for_each_rtd_components' + - 'for_each_rtd_cpu_dais' + - 'for_each_rtd_dais' + - 'for_each_script' + - 'for_each_sec' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_set_bitrange' + - 'for_each_set_bitrange_from' + - 'for_each_set_clump8' + - 'for_each_sg' + - 'for_each_sg_dma_page' + - 'for_each_sg_page' + - 'for_each_sgtable_dma_page' + - 'for_each_sgtable_dma_sg' + - 'for_each_sgtable_page' + - 'for_each_sgtable_sg' + - 'for_each_shell_test' + - 'for_each_sibling_event' + - 'for_each_subelement' + - 'for_each_subelement_extid' + - 'for_each_subelement_id' + - 'for_each_sublist' + - 'for_each_subsystem' + - 'for_each_supported_activate_fn' + - 'for_each_supported_inject_fn' + - 'for_each_test' + - 'for_each_thread' + - 'for_each_token' + - 'for_each_unicast_dest_pgid' + - 'for_each_vsi' + - 'for_each_wakeup_source' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'func_for_each_insn' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'genradix_for_each' + - 'genradix_for_each_from' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hashmap__for_each_entry' + - 'hashmap__for_each_entry_safe' + - 'hashmap__for_each_key_entry' + - 'hashmap__for_each_key_entry_safe' + - 'hctx_for_each_ctx' + - 'hists__for_each_format' + - 'hists__for_each_sort_list' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - 'hlist_for_each_entry_srcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'i3c_bus_for_each_i2cdev' + - 'i3c_bus_for_each_i3cdev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_continue_ul' + - 'idr_for_each_entry_ul' + - 'in_dev_for_each_ifa_rcu' + - 'in_dev_for_each_ifa_rtnl' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk' + - 'inet_lhash2_for_each_icsk_continue' + - 'inet_lhash2_for_each_icsk_rcu' + - 'interval_tree_for_each_double_span' + - 'interval_tree_for_each_span' + - 'intlist__for_each_entry' + - 'intlist__for_each_entry_safe' + - 'iopt_for_each_contig_area' + - 'kcore_copy__for_each_phdr' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_func_safe' + - 'klp_for_each_func_static' + - 'klp_for_each_object' + - 'klp_for_each_object_safe' + - 'klp_for_each_object_static' + - 'kunit_suite_for_each_test_case' + - 'kvm_for_each_memslot' + - 'kvm_for_each_memslot_in_gfn_range' + - 'kvm_for_each_vcpu' + - 'libbpf_nla_for_each_attr' + - 'list_for_each' + - 'list_for_each_codec' + - 'list_for_each_codec_safe' + - 'list_for_each_continue' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_rcu' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_entry_srcu' + - 'list_for_each_from' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'map__for_each_symbol' + - 'map__for_each_symbol_by_name' + - 'map_for_each_event' + - 'map_for_each_metric' + - 'maps__for_each_entry' + - 'maps__for_each_entry_safe' + - 'mci_for_each_dimm' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'msi_for_each_desc' + - 'nanddev_io_for_each_page' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'pci_doe_for_each_off' + - 'pcl_for_each_chunk' + - 'pcl_for_each_segment' + - 'pcm_for_each_format' + - 'perf_config_items__for_each_entry' + - 'perf_config_sections__for_each_entry' + - 'perf_config_set__for_each_entry' + - 'perf_cpu_map__for_each_cpu' + - 'perf_evlist__for_each_entry' + - 'perf_evlist__for_each_entry_reverse' + - 'perf_evlist__for_each_entry_safe' + - 'perf_evlist__for_each_evsel' + - 'perf_evlist__for_each_mmap' + - 'perf_hpp_list__for_each_format' + - 'perf_hpp_list__for_each_format_safe' + - 'perf_hpp_list__for_each_sort_list' + - 'perf_hpp_list__for_each_sort_list_safe' + - 'perf_pmu__for_each_hybrid_pmu' + - 'ping_portaddr_for_each_entry' + - 'ping_portaddr_for_each_entry_rcu' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rb_for_each' + - 'rbtree_postorder_for_each_entry_safe' + - 'rdma_for_each_block' + - 'rdma_for_each_port' + - 'rdma_umem_for_each_dma_block' + - 'resort_rb__for_each_entry' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_entry' + - 'rht_for_each_entry_from' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_from' + - 'rht_for_each_entry_safe' + - 'rht_for_each_from' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_from' + - 'rq_for_each_bvec' + - 'rq_for_each_segment' + - 'rq_list_for_each' + - 'rq_list_for_each_safe' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'sec_for_each_insn' + - 'sec_for_each_insn_continue' + - 'sec_for_each_insn_from' + - 'shdma_for_each_chan' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_array_for_each' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'strlist__for_each_entry' + - 'strlist__for_each_entry_safe' + - 'sym_for_each_insn' + - 'sym_for_each_insn_continue_reverse' + - 'symbols__for_each_entry' + - 'tb_property_for_each' + - 'tcf_act_for_each_action' + - 'tcf_exts_for_each_action' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'virtio_device_for_each_vq' + - 'while_for_each_ftrace_op' + - 'xa_for_each' + - 'xa_for_each_marked' + - 'xa_for_each_range' + - 'xa_for_each_start' + - 'xas_for_each' + - 'xas_for_each_conflict' + - 'xas_for_each_marked' + - 'xbc_array_for_each_value' + - 'xbc_for_each_key_value' + - 'xbc_node_for_each_array_value' + - 'xbc_node_for_each_child' + - 'xbc_node_for_each_key_value' + - 'xbc_node_for_each_subkey' + - 'zorro_for_each_dev' + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 4 +UseTab: AlignWithSpaces +... \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9ebe2bc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/rover", + "args": ["${workspaceFolder}"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Abilita la riformattazione per gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..25d5521 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-std=gnu17", + "-Wall", + "-O0", + "-ggdb", + "-D_DEFAULT_SOURCE", // pkg-config --cflags ncursesw + /* "-D_XOPEN_SOURCE=600", already defined in config.h */// pkg-config --cflags ncursesw + "${workspaceFolder}/src/*c", + "-o", + "${workspaceFolder}/rover", + "-lncursesw", // pkg-config --libs ncursesw + "-ltinfo" // pkg-config --libs ncursesw + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "compiler: /usr/bin/gcc" + } + ] +} \ No newline at end of file diff --git a/rover.c b/rover.c deleted file mode 100644 index 342aa48..0000000 --- a/rover.c +++ /dev/null @@ -1,1502 +0,0 @@ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif -#define _XOPEN_SOURCE_EXTENDED -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include /* pid_t, ... */ -#include -#include /* PATH_MAX */ -#include /* setlocale(), LC_ALL */ -#include /* chdir(), getcwd(), read(), close(), ... */ -#include /* DIR, struct dirent, opendir(), ... */ -#include -#include -#include /* open() */ -#include /* waitpid() */ -#include /* struct sigaction, sigaction() */ -#include -#include -#include - -#include "config.h" - -/* This signal is not defined by POSIX, but should be - present on all systems that have resizable terminals. */ -#ifndef SIGWINCH -#define SIGWINCH 28 -#endif - -/* String buffers. */ -#define BUFLEN PATH_MAX -static char BUF1[BUFLEN]; -static char BUF2[BUFLEN]; -static char INPUT[BUFLEN]; -static char CLIPBOARD[BUFLEN]; -static wchar_t WBUF[BUFLEN]; - -/* Paths to external programs. */ -static char *user_shell; -static char *user_pager; -static char *user_editor; -static char *user_open; - -/* Listing view parameters. */ -#define HEIGHT (LINES-4) -#define STATUSPOS (COLS-16) - -/* Listing view flags. */ -#define SHOW_FILES 0x01u -#define SHOW_DIRS 0x02u -#define SHOW_HIDDEN 0x04u - -/* Marks parameters. */ -#define BULK_INIT 5 -#define BULK_THRESH 256 - -/* Information associated to each entry in listing. */ -typedef struct Row { - char *name; - off_t size; - mode_t mode; - int islink; - int marked; -} Row; - -/* Dynamic array of marked entries. */ -typedef struct Marks { - char dirpath[PATH_MAX]; - int bulk; - int nentries; - char **entries; -} Marks; - -/* Line editing state. */ -typedef struct Edit { - wchar_t buffer[BUFLEN+1]; - int left, right; -} Edit; - -/* Each tab only stores the following information. */ -typedef struct Tab { - int scroll; - int esel; - uint8_t flags; - char cwd[PATH_MAX]; -} Tab; - -typedef struct Prog { - off_t partial; - off_t total; - const char *msg; -} Prog; - -/* Global state. */ -static struct Rover { - int tab; - int nfiles; - Row *rows; - WINDOW *window; - Marks marks; - Edit edit; - int edit_scroll; - volatile sig_atomic_t pending_usr1; - volatile sig_atomic_t pending_winch; - Prog prog; - Tab tabs[10]; -} rover; - -/* Macros for accessing global state. */ -#define ENAME(I) rover.rows[I].name -#define ESIZE(I) rover.rows[I].size -#define EMODE(I) rover.rows[I].mode -#define ISLINK(I) rover.rows[I].islink -#define MARKED(I) rover.rows[I].marked -#define SCROLL rover.tabs[rover.tab].scroll -#define ESEL rover.tabs[rover.tab].esel -#define FLAGS rover.tabs[rover.tab].flags -#define CWD rover.tabs[rover.tab].cwd - -/* Helpers. */ -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define ISDIR(E) (strchr((E), '/') != NULL) - -/* Line Editing Macros. */ -#define EDIT_FULL(E) ((E).left == (E).right) -#define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN-1) -#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] -#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] -#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) -#define EDIT_BACKSPACE(E) (E).left-- -#define EDIT_DELETE(E) (E).right++ -#define EDIT_CLEAR(E) do { (E).left = 0; (E).right = BUFLEN-1; } while(0) - -typedef enum EditStat {CONTINUE, CONFIRM, CANCEL} EditStat; -typedef enum Color {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK} Color; -typedef int (*PROCESS)(const char *path); - -static void -init_marks(Marks *marks) -{ - strcpy(marks->dirpath, ""); - marks->bulk = BULK_INIT; - marks->nentries = 0; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); -} - -/* Unmark all entries. */ -static void -mark_none(Marks *marks) -{ - int i; - - strcpy(marks->dirpath, ""); - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - free(marks->entries[i]); - marks->entries[i] = NULL; - marks->nentries--; - } - if (marks->bulk > BULK_THRESH) { - /* Reset bulk to free some memory. */ - free(marks->entries); - marks->bulk = BULK_INIT; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); - } -} - -static void -add_mark(Marks *marks, char *dirpath, char *entry) -{ - int i; - - if (!strcmp(marks->dirpath, dirpath)) { - /* Append mark to directory. */ - if (marks->nentries == marks->bulk) { - /* Expand bulk to accomodate new entry. */ - int extra = marks->bulk / 2; - marks->bulk += extra; /* bulk *= 1.5; */ - marks->entries = realloc(marks->entries, - marks->bulk * sizeof *marks->entries); - memset(&marks->entries[marks->nentries], 0, - extra * sizeof *marks->entries); - i = marks->nentries; - } else { - /* Search for empty slot (there must be one). */ - for (i = 0; i < marks->bulk; i++) - if (!marks->entries[i]) - break; - } - } else { - /* Directory changed. Discard old marks. */ - mark_none(marks); - strcpy(marks->dirpath, dirpath); - i = 0; - } - marks->entries[i] = malloc(strlen(entry) + 1); - strcpy(marks->entries[i], entry); - marks->nentries++; -} - -static void -del_mark(Marks *marks, char *entry) -{ - int i; - - if (marks->nentries > 1) { - for (i = 0; i < marks->bulk; i++) - if (marks->entries[i] && !strcmp(marks->entries[i], entry)) - break; - free(marks->entries[i]); - marks->entries[i] = NULL; - marks->nentries--; - } else - mark_none(marks); -} - -static void -free_marks(Marks *marks) -{ - int i; - - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - free(marks->entries[i]); - marks->nentries--; - } - free(marks->entries); -} - -static void -handle_usr1(int sig) -{ - rover.pending_usr1 = 1; -} - -static void -handle_winch(int sig) -{ - rover.pending_winch = 1; -} - -static void -enable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof (struct sigaction)); - sa.sa_handler = handle_usr1; - sigaction(SIGUSR1, &sa, NULL); - sa.sa_handler = handle_winch; - sigaction(SIGWINCH, &sa, NULL); -} - -static void -disable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof (struct sigaction)); - sa.sa_handler = SIG_DFL; - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); -} - -static void reload(); -static void update_view(); - -/* Handle any signals received since last call. */ -static void -sync_signals() -{ - if (rover.pending_usr1) { - /* SIGUSR1 received: refresh directory listing. */ - reload(); - rover.pending_usr1 = 0; - } - if (rover.pending_winch) { - /* SIGWINCH received: resize application accordingly. */ - delwin(rover.window); - endwin(); - refresh(); - clear(); - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles) - SCROLL = ESEL - HEIGHT; - update_view(); - rover.pending_winch = 0; - } -} - -/* This function must be used in place of getch(). - It handles signals while waiting for user input. */ -static int -rover_getch() -{ - int ch; - - while ((ch = getch()) == ERR) - sync_signals(); - return ch; -} - -/* This function must be used in place of get_wch(). - It handles signals while waiting for user input. */ -static int -rover_get_wch(wint_t *wch) -{ - wint_t ret; - - while ((ret = get_wch(wch)) == (wint_t) ERR) - sync_signals(); - return ret; -} - -/* Get user programs from the environment. */ - -#define ROVER_ENV(dst, src) if ((dst = getenv("ROVER_" #src)) == NULL) \ - dst = getenv(#src); - -static void -get_user_programs() -{ - ROVER_ENV(user_shell, SHELL) - ROVER_ENV(user_pager, PAGER) - ROVER_ENV(user_editor, VISUAL) - if (!user_editor) - ROVER_ENV(user_editor, EDITOR) - ROVER_ENV(user_open, OPEN) -} - -/* Do a fork-exec to external program (e.g. $EDITOR). */ -static void -spawn(char **args) -{ - pid_t pid; - int status; - - setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); - pid = fork(); - if (pid > 0) { - /* fork() succeeded. */ - disable_handlers(); - endwin(); - waitpid(pid, &status, 0); - enable_handlers(); - kill(getpid(), SIGWINCH); - } else if (pid == 0) { - /* Child process. */ - execvp(args[0], args); - } -} - -static void -shell_escaped_cat(char *buf, char *str, size_t n) -{ - char *p = buf + strlen(buf); - *p++ = '\''; - for (n--; n; n--, str++) { - switch (*str) { - case '\'': - if (n < 4) - goto done; - strcpy(p, "'\\''"); - n -= 4; - p += 4; - break; - case '\0': - goto done; - default: - *p = *str; - p++; - } - } -done: - strncat(p, "'", n); -} - -static int -open_with_env(char *program, char *path) -{ - if (program) { -#ifdef RV_SHELL - strncpy(BUF1, program, BUFLEN - 1); - strncat(BUF1, " ", BUFLEN - strlen(program) - 1); - shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2); - spawn((char *[]) {RV_SHELL, "-c", BUF1, NULL}); -#else - spawn((char *[]) {program, path, NULL}); -#endif - return 1; - } - return 0; -} - -/* Curses setup. */ -static void -init_term() -{ - setlocale(LC_ALL, ""); - initscr(); - cbreak(); /* Get one character at a time. */ - timeout(100); /* For getch(). */ - noecho(); - nonl(); /* No NL->CR/NL on output. */ - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - curs_set(FALSE); /* Hide blinking cursor. */ - if (has_colors()) { - short bg; - start_color(); -#ifdef NCURSES_EXT_FUNCS - use_default_colors(); - bg = -1; -#else - bg = COLOR_BLACK; -#endif - init_pair(RED, COLOR_RED, bg); - init_pair(GREEN, COLOR_GREEN, bg); - init_pair(YELLOW, COLOR_YELLOW, bg); - init_pair(BLUE, COLOR_BLUE, bg); - init_pair(CYAN, COLOR_CYAN, bg); - init_pair(MAGENTA, COLOR_MAGENTA, bg); - init_pair(WHITE, COLOR_WHITE, bg); - init_pair(BLACK, COLOR_BLACK, bg); - } - atexit((void (*)(void)) endwin); - enable_handlers(); -} - -/* Update the listing view. */ -static void -update_view() -{ - int i, j; - int numsize; - int ishidden; - int marking; - - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, BUF1); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(WBUF, CWD, PATH_MAX); - mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window - shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = ENAME(j)[0] == '.'; - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - if (S_ISDIR(EMODE(j))) { - mbstowcs(WBUF, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(WBUF, L"/"); - } else { - char *suffix, *suffixes = "BKMGTPEZY"; - off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); - int namecols = wcswidth(WBUF, length); - for (suffix = suffixes; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - if (*suffix == 'B') - swprintf(WBUF + length, PATH_MAX - length, L"%*d %c", - (int) (COLS - namecols - 6), - (int) human_size / 10, *suffix); - else - swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c", - (int) (COLS - namecols - 8), - (int) human_size / 10, (int) human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - if (rover.nfiles > HEIGHT) { - int center, height; - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT-1) * HEIGHT / rover.nfiles; - if (!height) height = 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height); - } - BUF1[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - BUF1[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - if (!rover.nfiles) - strcpy(BUF2, "0/0"); - else - snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, BUF1); - wrefresh(rover.window); -} - -/* Show a message on the status bar. */ -static void -message(Color color, char *fmt, ...) -{ - int len, pos; - va_list args; - - va_start(args, fmt); - vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args); - va_end(args); - len = strlen(BUF1); - pos = (STATUSPOS - len) / 2; - attr_on(A_BOLD, NULL); - color_set(color, NULL); - mvaddstr(LINES - 1, pos, BUF1); - color_set(DEFAULT, NULL); - attr_off(A_BOLD, NULL); -} - -/* Clear message area, leaving only status info. */ -static void -clear_message() -{ - mvhline(LINES - 1, 0, ' ', STATUSPOS); -} - -/* Comparison used to sort listing entries. */ -static int -rowcmp(const void *a, const void *b) -{ - int isdir1, isdir2, cmpdir; - const Row *r1 = a; - const Row *r2 = b; - isdir1 = S_ISDIR(r1->mode); - isdir2 = S_ISDIR(r2->mode); - cmpdir = isdir2 - isdir1; - return cmpdir ? cmpdir : strcoll(r1->name, r2->name); -} - -/* Get all entries in current working directory. */ -static int -ls(Row **rowsp, uint8_t flags) -{ - DIR *dp; - struct dirent *ep; - struct stat statbuf; - Row *rows; - int i, n; - - if(!(dp = opendir("."))) return -1; - n = -2; /* We don't want the entries "." and "..". */ - while (readdir(dp)) n++; - if (n == 0) { - closedir(dp); - return 0; - } - rewinddir(dp); - rows = malloc(n * sizeof *rows); - i = 0; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') - continue; - lstat(ep->d_name, &statbuf); - rows[i].islink = S_ISLNK(statbuf.st_mode); - stat(ep->d_name, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - if (flags & SHOW_DIRS) { - rows[i].name = malloc(strlen(ep->d_name) + 2); - strcpy(rows[i].name, ep->d_name); - if (!rows[i].islink) - strcat(rows[i].name, "/"); - rows[i].mode = statbuf.st_mode; - i++; - } - } else if (flags & SHOW_FILES) { - rows[i].name = malloc(strlen(ep->d_name) + 1); - strcpy(rows[i].name, ep->d_name); - rows[i].size = statbuf.st_size; - rows[i].mode = statbuf.st_mode; - i++; - } - } - n = i; /* Ignore unused space in array caused by filters. */ - qsort(rows, n, sizeof (*rows), rowcmp); - closedir(dp); - *rowsp = rows; - return n; -} - -static void -free_rows(Row **rowsp, int nfiles) -{ - int i; - - for (i = 0; i < nfiles; i++) - free((*rowsp)[i].name); - free(*rowsp); - *rowsp = NULL; -} - -/* Change working directory to the path in CWD. */ -static void -cd(int reset) -{ - int i, j; - - message(CYAN, "Loading \"%s\"...", CWD); - refresh(); - if (chdir(CWD) == -1) { - getcwd(CWD, PATH_MAX-1); - if (CWD[strlen(CWD)-1] != '/') - strcat(CWD, "/"); - goto done; - } - if (reset) ESEL = SCROLL = 0; - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - rover.nfiles = ls(&rover.rows, FLAGS); - if (!strcmp(CWD, rover.marks.dirpath)) { - for (i = 0; i < rover.nfiles; i++) { - for (j = 0; j < rover.marks.bulk; j++) - if ( - rover.marks.entries[j] && - !strcmp(rover.marks.entries[j], ENAME(i)) - ) - break; - MARKED(i) = j < rover.marks.bulk; - } - } else - for (i = 0; i < rover.nfiles; i++) - MARKED(i) = 0; -done: - clear_message(); - update_view(); -} - -/* Select a target entry, if it is present. */ -static void -try_to_sel(const char *target) -{ - ESEL = 0; - if (!ISDIR(target)) - while ((ESEL+1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) - ESEL++; - while ((ESEL+1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) - ESEL++; -} - -/* Reload CWD, but try to keep selection. */ -static void -reload() -{ - if (rover.nfiles) { - strcpy(INPUT, ENAME(ESEL)); - cd(0); - try_to_sel(INPUT); - update_view(); - } else - cd(1); -} - -static off_t -count_dir(const char *path) -{ - DIR *dp; - struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - off_t total; - - if(!(dp = opendir(path))) return 0; - total = 0; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); - total += count_dir(subpath); - } else - total += statbuf.st_size; - } - closedir(dp); - return total; -} - -static off_t -count_marked() -{ - int i; - char *entry; - off_t total; - struct stat statbuf; - - total = 0; - chdir(rover.marks.dirpath); - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - if (ISDIR(entry)) { - total += count_dir(entry); - } else { - lstat(entry, &statbuf); - total += statbuf.st_size; - } - } - } - chdir(CWD); - return total; -} - -/* Recursively process a source directory using CWD as destination root. - For each node (i.e. directory), do the following: - 1. call pre(destination); - 2. call proc() on every child leaf (i.e. files); - 3. recurse into every child node; - 4. call pos(source). - E.g. to move directory /src/ (and all its contents) inside /dst/: - strcpy(CWD, "/dst/"); - process_dir(adddir, movfile, deldir, "/src/"); */ -static int -process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) -{ - int ret; - DIR *dp; - struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - - ret = 0; - if (pre) { - char dstpath[PATH_MAX]; - strcpy(dstpath, CWD); - strcat(dstpath, path + strlen(rover.marks.dirpath)); - ret |= pre(dstpath); - } - if(!(dp = opendir(path))) return -1; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); - ret |= process_dir(pre, proc, pos, subpath); - } else - ret |= proc(subpath); - } - closedir(dp); - if (pos) ret |= pos(path); - return ret; -} - -/* Process all marked entries using CWD as destination root. - All marked entries that are directories will be recursively processed. - See process_dir() for details on the parameters. */ -static void -process_marked(PROCESS pre, PROCESS proc, PROCESS pos, - const char *msg_doing, const char *msg_done) -{ - int i, ret; - char *entry; - char path[PATH_MAX]; - - clear_message(); - message(CYAN, "%s...", msg_doing); - refresh(); - rover.prog = (Prog) {0, count_marked(), msg_doing}; - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - ret = 0; - snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); - if (ISDIR(entry)) { - if (!strncmp(path, CWD, strlen(path))) - ret = -1; - else - ret = process_dir(pre, proc, pos, path); - } else - ret = proc(path); - if (!ret) { - del_mark(&rover.marks, entry); - reload(); - } - } - } - rover.prog.total = 0; - reload(); - if (!rover.marks.nentries) - message(GREEN, "%s all marked entries.", msg_done); - else - message(RED, "Some errors occured while %s.", msg_doing); - RV_ALERT(); -} - -static void -update_progress(off_t delta) -{ - int percent; - - if (!rover.prog.total) return; - rover.prog.partial += delta; - percent = (int) (rover.prog.partial * 100 / rover.prog.total); - message(CYAN, "%s...%d%%", rover.prog.msg, percent); - refresh(); -} - -/* Wrappers for file operations. */ -static int delfile(const char *path) { - int ret; - struct stat st; - - ret = lstat(path, &st); - if (ret < 0) return ret; - update_progress(st.st_size); - return unlink(path); -} -static PROCESS deldir = rmdir; -static int addfile(const char *path) { - /* Using creat(2) because mknod(2) doesn't seem to be portable. */ - int ret; - - ret = creat(path, 0644); - if (ret < 0) return ret; - return close(ret); -} -static int cpyfile(const char *srcpath) { - int src, dst, ret; - size_t size; - struct stat st; - char buf[BUFSIZ]; - char dstpath[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = lstat(srcpath, &st); - if (ret < 0) return ret; - if (S_ISLNK(st.st_mode)) { - ret = readlink(srcpath, BUF1, BUFLEN-1); - if (ret < 0) return ret; - BUF1[ret] = '\0'; - ret = symlink(BUF1, dstpath); - } else { - ret = src = open(srcpath, O_RDONLY); - if (ret < 0) return ret; - ret = dst = creat(dstpath, st.st_mode); - if (ret < 0) return ret; - while ((size = read(src, buf, BUFSIZ)) > 0) { - write(dst, buf, size); - update_progress(size); - sync_signals(); - } - close(src); - close(dst); - ret = 0; - } - return ret; -} -static int adddir(const char *path) { - int ret; - struct stat st; - - ret = stat(CWD, &st); - if (ret < 0) return ret; - return mkdir(path, st.st_mode); -} -static int movfile(const char *srcpath) { - int ret; - struct stat st; - char dstpath[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = rename(srcpath, dstpath); - if (ret == 0) { - ret = lstat(dstpath, &st); - if (ret < 0) return ret; - update_progress(st.st_size); - } else if (errno == EXDEV) { - ret = cpyfile(srcpath); - if (ret < 0) return ret; - ret = unlink(srcpath); - } - return ret; -} - -static void -start_line_edit(const char *init_input) -{ - curs_set(TRUE); - strncpy(INPUT, init_input, BUFLEN); - rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN); - rover.edit.right = BUFLEN - 1; - rover.edit.buffer[BUFLEN] = L'\0'; - rover.edit_scroll = 0; -} - -/* Read input and change editing state accordingly. */ -static EditStat -get_line_edit() -{ - wchar_t eraser, killer, wch; - int ret, length; - - ret = rover_get_wch((wint_t *) &wch); - erasewchar(&eraser); - killwchar(&killer); - if (ret == KEY_CODE_YES) { - if (wch == KEY_ENTER) { - curs_set(FALSE); - return CONFIRM; - } else if (wch == KEY_LEFT) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit); - } else if (wch == KEY_RIGHT) { - if (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit); - } else if (wch == KEY_UP) { - while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit); - } else if (wch == KEY_DOWN) { - while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit); - } else if (wch == KEY_BACKSPACE) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit); - } else if (wch == KEY_DC) { - if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit); - } - } else { - if (wch == L'\r' || wch == L'\n') { - curs_set(FALSE); - return CONFIRM; - } else if (wch == L'\t') { - curs_set(FALSE); - return CANCEL; - } else if (wch == eraser) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit); - } else if (wch == killer) { - EDIT_CLEAR(rover.edit); - clear_message(); - } else if (iswprint(wch)) { - if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch); - } - } - /* Encode edit contents in INPUT. */ - rover.edit.buffer[rover.edit.left] = L'\0'; - length = wcstombs(INPUT, rover.edit.buffer, BUFLEN); - wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right+1], - BUFLEN-length); - return CONTINUE; -} - -/* Update line input on the screen. */ -static void -update_input(const char *prompt, Color color) -{ - int plen, ilen, maxlen; - - plen = strlen(prompt); - ilen = mbstowcs(NULL, INPUT, 0); - maxlen = STATUSPOS - plen - 2; - if (ilen - rover.edit_scroll < maxlen) - rover.edit_scroll = MAX(ilen - maxlen, 0); - else if (rover.edit.left > rover.edit_scroll + maxlen - 1) - rover.edit_scroll = rover.edit.left - maxlen; - else if (rover.edit.left < rover.edit_scroll) - rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); - color_set(RVC_PROMPT, NULL); - mvaddstr(LINES - 1, 0, prompt); - color_set(color, NULL); - mbstowcs(WBUF, INPUT, COLS); - mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen); - mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); - color_set(DEFAULT, NULL); - if (rover.edit_scroll) - mvaddch(LINES - 1, plen - 1, '<'); - if (ilen > rover.edit_scroll + maxlen) - mvaddch(LINES - 1, plen + maxlen, '>'); - move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); -} - -int -main(int argc, char *argv[]) -{ - int i, ch; - char *program; - char *entry; - const char *key; - const char *clip_path; - DIR *d; - EditStat edit_stat; - FILE *save_cwd_file = NULL; - FILE *save_marks_file = NULL; - FILE *clip_file; - - if (argc >= 2) { - if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { - printf("rover %s\n", RV_VERSION); - return 0; - } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { - printf( - "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" - " Browse current directory or the ones specified.\n\n" - " or: rover -h|--help\n" - " Print this help message and exit.\n\n" - " or: rover -v|--version\n" - " Print program version and exit.\n\n" - "See rover(1) for more information.\n" - "Rover homepage: .\n" - ); - return 0; - } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { - if (argc > 2) { - save_cwd_file = fopen(argv[2], "w"); - argc -= 2; argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { - if (argc > 2) { - save_marks_file = fopen(argv[2], "a"); - argc -= 2; argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } - } - get_user_programs(); - init_term(); - rover.nfiles = 0; - for (i = 0; i < 10; i++) { - rover.tabs[i].esel = rover.tabs[i].scroll = 0; - rover.tabs[i].flags = RV_FLAGS; - } - strcpy(rover.tabs[0].cwd, getenv("HOME")); - for (i = 1; i < argc && i < 10; i++) { - if ((d = opendir(argv[i]))) { - realpath(argv[i], rover.tabs[i].cwd); - closedir(d); - } else - strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); - } - getcwd(rover.tabs[i].cwd, PATH_MAX); - for (i++; i < 10; i++) - strcpy(rover.tabs[i].cwd, rover.tabs[i-1].cwd); - for (i = 0; i < 10; i++) - if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') - strcat(rover.tabs[i].cwd, "/"); - rover.tab = 1; - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - init_marks(&rover.marks); - cd(1); - strcpy(CLIPBOARD, CWD); - if (rover.nfiles > 0) - strcat(CLIPBOARD, ENAME(ESEL)); - while (1) { - ch = rover_getch(); - key = keyname(ch); - clear_message(); - if (!strcmp(key, RVK_QUIT)) break; - else if (ch >= '0' && ch <= '9') { - rover.tab = ch - '0'; - cd(0); - } else if (!strcmp(key, RVK_HELP)) { - spawn((char *[]) {"man", "rover", NULL}); - } else if (!strcmp(key, RVK_DOWN)) { - if (!rover.nfiles) continue; - ESEL = MIN(ESEL + 1, rover.nfiles - 1); - update_view(); - } else if (!strcmp(key, RVK_UP)) { - if (!rover.nfiles) continue; - ESEL = MAX(ESEL - 1, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_DOWN)) { - if (!rover.nfiles) continue; - ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); - if (rover.nfiles > HEIGHT) - SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); - update_view(); - } else if (!strcmp(key, RVK_JUMP_UP)) { - if (!rover.nfiles) continue; - ESEL = MAX(ESEL - RV_JUMP, 0); - SCROLL = MAX(SCROLL - RV_JUMP, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_TOP)) { - if (!rover.nfiles) continue; - ESEL = 0; - update_view(); - } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { - if (!rover.nfiles) continue; - ESEL = rover.nfiles - 1; - update_view(); - } else if (!strcmp(key, RVK_CD_DOWN)) { - if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue; - if (chdir(ENAME(ESEL)) == -1) { - message(RED, "Cannot access \"%s\".", ENAME(ESEL)); - continue; - } - strcat(CWD, ENAME(ESEL)); - cd(1); - } else if (!strcmp(key, RVK_CD_UP)) { - char *dirname, first; - if (!strcmp(CWD, "/")) continue; - CWD[strlen(CWD) - 1] = '\0'; - dirname = strrchr(CWD, '/') + 1; - first = dirname[0]; - dirname[0] = '\0'; - cd(1); - dirname[0] = first; - dirname[strlen(dirname)] = '/'; - try_to_sel(dirname); - dirname[0] = '\0'; - if (rover.nfiles > HEIGHT) - SCROLL = ESEL - HEIGHT / 2; - update_view(); - } else if (!strcmp(key, RVK_HOME)) { - strcpy(CWD, getenv("HOME")); - if (CWD[strlen(CWD) - 1] != '/') - strcat(CWD, "/"); - cd(1); - } else if (!strcmp(key, RVK_TARGET)) { - char *bname, first; - int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN-1); - if (len == -1) continue; - BUF1[len] = '\0'; - if (access(BUF1, F_OK) == -1) { - char *msg; - switch (errno) { - case EACCES: - msg = "Cannot access \"%s\"."; - break; - case ENOENT: - msg = "\"%s\" does not exist."; - break; - default: - msg = "Cannot navigate to \"%s\"."; - } - strcpy(BUF2, BUF1); /* message() uses BUF1. */ - message(RED, msg, BUF2); - continue; - } - realpath(BUF1, CWD); - len = strlen(CWD); - if (CWD[len - 1] == '/') - CWD[len - 1] = '\0'; - bname = strrchr(CWD, '/') + 1; - first = *bname; - *bname = '\0'; - cd(1); - *bname = first; - if (is_dir) - strcat(CWD, "/"); - try_to_sel(bname); - *bname = '\0'; - update_view(); - } else if (!strcmp(key, RVK_COPY_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) goto copy_path_fail; - clip_file = fopen(clip_path, "w"); - if (!clip_file) goto copy_path_fail; - fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); - fclose(clip_file); - goto copy_path_done; -copy_path_fail: - strcpy(CLIPBOARD, CWD); - strcat(CLIPBOARD, ENAME(ESEL)); -copy_path_done: - ; - } else if (!strcmp(key, RVK_PASTE_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) goto paste_path_fail; - clip_file = fopen(clip_path, "r"); - if (!clip_file) goto paste_path_fail; - fscanf(clip_file, "%s\n", CLIPBOARD); - fclose(clip_file); -paste_path_fail: - strcpy(BUF1, CLIPBOARD); - strcpy(CWD, dirname(BUF1)); - if (strcmp(CWD, "/")) - strcat(CWD, "/"); - cd(1); - strcpy(BUF1, CLIPBOARD); - try_to_sel(strstr(CLIPBOARD, basename(BUF1))); - update_view(); - } else if (!strcmp(key, RVK_REFRESH)) { - reload(); - } else if (!strcmp(key, RVK_SHELL)) { - program = user_shell; - if (program) { -#ifdef RV_SHELL - spawn((char *[]) {RV_SHELL, "-c", program, NULL}); -#else - spawn((char *[]) {program, NULL}); -#endif - reload(); - } - } else if (!strcmp(key, RVK_VIEW)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_pager, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_EDIT)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_editor, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_OPEN)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_open, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_SEARCH)) { - int oldsel, oldscroll, length; - if (!rover.nfiles) continue; - oldsel = ESEL; - oldscroll = SCROLL; - start_line_edit(""); - update_input(RVP_SEARCH, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int sel; - Color color = RED; - length = strlen(INPUT); - if (length) { - for (sel = 0; sel < rover.nfiles; sel++) - if (!strncmp(ENAME(sel), INPUT, length)) - break; - if (sel < rover.nfiles) { - color = GREEN; - ESEL = sel; - if (rover.nfiles > HEIGHT) { - if (sel < 3) - SCROLL = 0; - else if (sel - 3 > rover.nfiles - HEIGHT) - SCROLL = rover.nfiles - HEIGHT; - else - SCROLL = sel - 3; - } - } - } else { - ESEL = oldsel; - SCROLL = oldscroll; - } - update_view(); - update_input(RVP_SEARCH, color); - } - if (edit_stat == CANCEL) { - ESEL = oldsel; - SCROLL = oldscroll; - } - clear_message(); - update_view(); - } else if (!strcmp(key, RVK_TG_FILES)) { - FLAGS ^= SHOW_FILES; - reload(); - } else if (!strcmp(key, RVK_TG_DIRS)) { - FLAGS ^= SHOW_DIRS; - reload(); - } else if (!strcmp(key, RVK_TG_HIDDEN)) { - FLAGS ^= SHOW_HIDDEN; - reload(); - } else if (!strcmp(key, RVK_NEW_FILE)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_FILE, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - } - update_input(RVP_NEW_FILE, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (addfile(INPUT) == 0) { - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_NEW_DIR)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_DIR, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - } - update_input(RVP_NEW_DIR, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (adddir(INPUT) == 0) { - cd(1); - strcat(INPUT, "/"); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s/\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_RENAME)) { - int ok = 0; - char *last; - int isdir; - strcpy(INPUT, ENAME(ESEL)); - last = INPUT + strlen(INPUT) - 1; - if ((isdir = *last == '/')) - *last = '\0'; - start_line_edit(INPUT); - update_input(RVP_RENAME, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - update_input(RVP_RENAME, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (isdir) - strcat(INPUT, "/"); - if (ok) { - if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) { - del_mark(&rover.marks, ENAME(ESEL)); - add_mark(&rover.marks, CWD, INPUT); - } - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_TG_EXEC)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (S_IXUSR & EMODE(ESEL)) - EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); - else - EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH ; - if (chmod(ENAME(ESEL), EMODE(ESEL))) { - message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); - } else { - message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); - update_view(); - } - } else if (!strcmp(key, RVK_DELETE)) { - if (rover.nfiles) { - message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); - if (rover_getch() == 'Y') { - const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); - reload(); - if (ret) - message(RED, "Could not delete \"%s\".", ENAME(ESEL)); - } else - clear_message(); - } else - message(RED, "No entry selected for deletion."); - } else if (!strcmp(key, RVK_TG_MARK)) { - if (MARKED(ESEL)) - del_mark(&rover.marks, ENAME(ESEL)); - else - add_mark(&rover.marks, CWD, ENAME(ESEL)); - MARKED(ESEL) = !MARKED(ESEL); - ESEL = (ESEL + 1) % rover.nfiles; - update_view(); - } else if (!strcmp(key, RVK_INVMARK)) { - for (i = 0; i < rover.nfiles; i++) { - if (MARKED(i)) - del_mark(&rover.marks, ENAME(i)); - else - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = !MARKED(i); - } - update_view(); - } else if (!strcmp(key, RVK_MARKALL)) { - for (i = 0; i < rover.nfiles; i++) - if (!MARKED(i)) { - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = 1; - } - update_view(); - } else if (!strcmp(key, RVK_MARK_DELETE)) { - if (rover.marks.nentries) { - message(YELLOW, "Delete all marked entries? (Y/n)"); - if (rover_getch() == 'Y') - process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); - else - clear_message(); - } else - message(RED, "No entries marked for deletion."); - } else if (!strcmp(key, RVK_MARK_COPY)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); - else - message(RED, "Cannot copy to the same path."); - } else - message(RED, "No entries marked for copying."); - } else if (!strcmp(key, RVK_MARK_MOVE)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); - else - message(RED, "Cannot move to the same path."); - } else - message(RED, "No entries marked for moving."); - } - } - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - delwin(rover.window); - if (save_cwd_file != NULL) { - fputs(CWD, save_cwd_file); - fclose(save_cwd_file); - } - if (save_marks_file != NULL) { - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) - fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); - } - fclose(save_marks_file); - } - free_marks(&rover.marks); - return 0; -} diff --git a/config.h b/src/config.h similarity index 67% rename from config.h rename to src/config.h index 6f314dd..6426825 100644 --- a/config.h +++ b/src/config.h @@ -1,4 +1,4 @@ -#define RV_VERSION "1.0.1" +#define RV_VERSION "1.0.1" /* CTRL+X: "^X" ALT+X: "M-X" */ @@ -38,43 +38,43 @@ #define RVK_MARK_MOVE "V" /* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ -#define RVC_CWD GREEN -#define RVC_STATUS CYAN -#define RVC_BORDER BLUE -#define RVC_SCROLLBAR CYAN -#define RVC_LINK CYAN -#define RVC_HIDDEN YELLOW -#define RVC_EXEC GREEN -#define RVC_REG DEFAULT -#define RVC_DIR DEFAULT -#define RVC_CHR MAGENTA -#define RVC_BLK MAGENTA -#define RVC_FIFO BLUE -#define RVC_SOCK MAGENTA -#define RVC_PROMPT DEFAULT -#define RVC_TABNUM DEFAULT -#define RVC_MARKS YELLOW +#define RVC_CWD GREEN +#define RVC_STATUS CYAN +#define RVC_BORDER BLUE +#define RVC_SCROLLBAR CYAN +#define RVC_LINK CYAN +#define RVC_HIDDEN YELLOW +#define RVC_EXEC GREEN +#define RVC_REG DEFAULT +#define RVC_DIR DEFAULT +#define RVC_CHR MAGENTA +#define RVC_BLK MAGENTA +#define RVC_FIFO BLUE +#define RVC_SOCK MAGENTA +#define RVC_PROMPT DEFAULT +#define RVC_TABNUM DEFAULT +#define RVC_MARKS YELLOW /* Special symbols used by the TUI. See for available constants. */ -#define RVS_SCROLLBAR ACS_CKBOARD -#define RVS_MARK ACS_DIAMOND +#define RVS_SCROLLBAR ACS_CKBOARD +#define RVS_MARK ACS_DIAMOND /* Prompt strings for line input. */ -#define RV_PROMPT(S) S ": " -#define RVP_SEARCH RV_PROMPT("search") -#define RVP_NEW_FILE RV_PROMPT("new file") -#define RVP_NEW_DIR RV_PROMPT("new dir") -#define RVP_RENAME RV_PROMPT("rename") +#define RV_PROMPT(S) S ": " +#define RVP_SEARCH RV_PROMPT("search") +#define RVP_NEW_FILE RV_PROMPT("new file") +#define RVP_NEW_DIR RV_PROMPT("new dir") +#define RVP_RENAME RV_PROMPT("rename") /* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ -#define RV_JUMP 10 +#define RV_JUMP 10 /* Default listing view flags. May include SHOW_FILES, SHOW_DIRS and SHOW_HIDDEN. */ -#define RV_FLAGS SHOW_FILES | SHOW_DIRS +#define RV_FLAGS SHOW_FILES | SHOW_DIRS /* Optional macro to be executed when a batch operation finishes. */ -#define RV_ALERT() beep() +#define RV_ALERT() beep() /* Shell used to launch external programs. Defining this macro will force Rover to launch external @@ -85,4 +85,4 @@ process each time an external program is invoked. Leave this macro undefined if you prefer external programs to be launched with just `$EXTERNAL_PROGRAM [arg]`. */ -#define RV_SHELL "/bin/sh" +#define RV_SHELL "/bin/sh" diff --git a/src/rover.c b/src/rover.c new file mode 100644 index 0000000..3c3b2a8 --- /dev/null +++ b/src/rover.c @@ -0,0 +1,1562 @@ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#define _XOPEN_SOURCE_EXTENDED +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include /* pid_t, ... */ +#include +#include /* PATH_MAX */ +#include /* setlocale(), LC_ALL */ +#include /* chdir(), getcwd(), read(), close(), ... */ +#include /* DIR, struct dirent, opendir(), ... */ +#include +#include +#include /* open() */ +#include /* waitpid() */ +#include /* struct sigaction, sigaction() */ +#include +#include +#include + +#include "config.h" + +/* This signal is not defined by POSIX, but should be + present on all systems that have resizable terminals. */ +#ifndef SIGWINCH +#define SIGWINCH 28 +#endif + +/* String buffers. */ +#define BUFLEN PATH_MAX +static char BUF1[BUFLEN]; +static char BUF2[BUFLEN]; +static char INPUT[BUFLEN]; +static char CLIPBOARD[BUFLEN]; +static wchar_t WBUF[BUFLEN]; + +/* Paths to external programs. */ +static char *user_shell; +static char *user_pager; +static char *user_editor; +static char *user_open; + +/* Listing view parameters. */ +#define HEIGHT (LINES - 4) +#define STATUSPOS (COLS - 16) + +/* Listing view flags. */ +#define SHOW_FILES 0x01u +#define SHOW_DIRS 0x02u +#define SHOW_HIDDEN 0x04u + +/* Marks parameters. */ +#define BULK_INIT 5 +#define BULK_THRESH 256 + +/* Information associated to each entry in listing. */ +typedef struct Row { + char *name; + off_t size; + mode_t mode; + int islink; + int marked; +} Row; + +/* Dynamic array of marked entries. */ +typedef struct Marks { + char dirpath[PATH_MAX]; + int bulk; + int nentries; + char **entries; +} Marks; + +/* Line editing state. */ +typedef struct Edit { + wchar_t buffer[BUFLEN + 1]; + int left, right; +} Edit; + +/* Each tab only stores the following information. */ +typedef struct Tab { + int scroll; + int esel; + uint8_t flags; + char cwd[PATH_MAX]; +} Tab; + +typedef struct Prog { + off_t partial; + off_t total; + const char *msg; +} Prog; + +/* Global state. */ +static struct Rover { + int tab; + int nfiles; + Row *rows; + WINDOW *window; + Marks marks; + Edit edit; + int edit_scroll; + volatile sig_atomic_t pending_usr1; + volatile sig_atomic_t pending_winch; + Prog prog; + Tab tabs[10]; +} rover; + +/* Macros for accessing global state. */ +#define ENAME(I) rover.rows[I].name +#define ESIZE(I) rover.rows[I].size +#define EMODE(I) rover.rows[I].mode +#define ISLINK(I) rover.rows[I].islink +#define MARKED(I) rover.rows[I].marked +#define SCROLL rover.tabs[rover.tab].scroll +#define ESEL rover.tabs[rover.tab].esel +#define FLAGS rover.tabs[rover.tab].flags +#define CWD rover.tabs[rover.tab].cwd + +/* Helpers. */ +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define ISDIR(E) (strchr((E), '/') != NULL) + +/* Line Editing Macros. */ +#define EDIT_FULL(E) ((E).left == (E).right) +#define EDIT_CAN_LEFT(E) ((E).left) +#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN - 1) +#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] +#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] +#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) +#define EDIT_BACKSPACE(E) (E).left-- +#define EDIT_DELETE(E) (E).right++ +#define EDIT_CLEAR(E) \ + do { \ + (E).left = 0; \ + (E).right = BUFLEN - 1; \ + } while (0) + +typedef enum EditStat { CONTINUE, + CONFIRM, + CANCEL } EditStat; +typedef enum Color { DEFAULT, + RED, + GREEN, + YELLOW, + BLUE, + CYAN, + MAGENTA, + WHITE, + BLACK } Color; +typedef int (*PROCESS)(const char *path); + +static void +init_marks(Marks *marks) +{ + strcpy(marks->dirpath, ""); + marks->bulk = BULK_INIT; + marks->nentries = 0; + marks->entries = calloc(marks->bulk, sizeof *marks->entries); +} + +/* Unmark all entries. */ +static void +mark_none(Marks *marks) +{ + int i; + + strcpy(marks->dirpath, ""); + for (i = 0; i < marks->bulk && marks->nentries; i++) + if (marks->entries[i]) { + free(marks->entries[i]); + marks->entries[i] = NULL; + marks->nentries--; + } + if (marks->bulk > BULK_THRESH) { + /* Reset bulk to free some memory. */ + free(marks->entries); + marks->bulk = BULK_INIT; + marks->entries = calloc(marks->bulk, sizeof *marks->entries); + } +} + +static void +add_mark(Marks *marks, char *dirpath, char *entry) +{ + int i; + + if (!strcmp(marks->dirpath, dirpath)) { + /* Append mark to directory. */ + if (marks->nentries == marks->bulk) { + /* Expand bulk to accomodate new entry. */ + int extra = marks->bulk / 2; + marks->bulk += extra; /* bulk *= 1.5; */ + marks->entries = realloc(marks->entries, + marks->bulk * sizeof *marks->entries); + memset(&marks->entries[marks->nentries], 0, + extra * sizeof *marks->entries); + i = marks->nentries; + } else { + /* Search for empty slot (there must be one). */ + for (i = 0; i < marks->bulk; i++) + if (!marks->entries[i]) + break; + } + } else { + /* Directory changed. Discard old marks. */ + mark_none(marks); + strcpy(marks->dirpath, dirpath); + i = 0; + } + marks->entries[i] = malloc(strlen(entry) + 1); + strcpy(marks->entries[i], entry); + marks->nentries++; +} + +static void +del_mark(Marks *marks, char *entry) +{ + int i; + + if (marks->nentries > 1) { + for (i = 0; i < marks->bulk; i++) + if (marks->entries[i] && !strcmp(marks->entries[i], entry)) + break; + free(marks->entries[i]); + marks->entries[i] = NULL; + marks->nentries--; + } else + mark_none(marks); +} + +static void +free_marks(Marks *marks) +{ + int i; + + for (i = 0; i < marks->bulk && marks->nentries; i++) + if (marks->entries[i]) { + free(marks->entries[i]); + marks->nentries--; + } + free(marks->entries); +} + +static void +handle_usr1(int sig) +{ + rover.pending_usr1 = 1; +} + +static void +handle_winch(int sig) +{ + rover.pending_winch = 1; +} + +static void +enable_handlers() +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_usr1; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = handle_winch; + sigaction(SIGWINCH, &sa, NULL); +} + +static void +disable_handlers() +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGWINCH, &sa, NULL); +} + +static void reload(); +static void update_view(); + +/* Handle any signals received since last call. */ +static void +sync_signals() +{ + if (rover.pending_usr1) { + /* SIGUSR1 received: refresh directory listing. */ + reload(); + rover.pending_usr1 = 0; + } + if (rover.pending_winch) { + /* SIGWINCH received: resize application accordingly. */ + delwin(rover.window); + endwin(); + refresh(); + clear(); + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles) + SCROLL = ESEL - HEIGHT; + update_view(); + rover.pending_winch = 0; + } +} + +/* This function must be used in place of getch(). + It handles signals while waiting for user input. */ +static int +rover_getch() +{ + int ch; + + while ((ch = getch()) == ERR) + sync_signals(); + return ch; +} + +/* This function must be used in place of get_wch(). + It handles signals while waiting for user input. */ +static int +rover_get_wch(wint_t *wch) +{ + wint_t ret; + + while ((ret = get_wch(wch)) == (wint_t)ERR) + sync_signals(); + return ret; +} + +/* Get user programs from the environment. */ + +#define ROVER_ENV(dst, src) \ + if ((dst = getenv("ROVER_" #src)) == NULL) \ + dst = getenv(#src); + +static void +get_user_programs() +{ + ROVER_ENV(user_shell, SHELL) + ROVER_ENV(user_pager, PAGER) + ROVER_ENV(user_editor, VISUAL) + if (!user_editor) + ROVER_ENV(user_editor, EDITOR) + ROVER_ENV(user_open, OPEN) +} + +/* Do a fork-exec to external program (e.g. $EDITOR). */ +static void +spawn(char **args) +{ + pid_t pid; + int status; + + setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); + pid = fork(); + if (pid > 0) { + /* fork() succeeded. */ + disable_handlers(); + endwin(); + waitpid(pid, &status, 0); + enable_handlers(); + kill(getpid(), SIGWINCH); + } else if (pid == 0) { + /* Child process. */ + execvp(args[0], args); + } +} + +static void +shell_escaped_cat(char *buf, char *str, size_t n) +{ + char *p = buf + strlen(buf); + *p++ = '\''; + for (n--; n; n--, str++) { + switch (*str) { + case '\'': + if (n < 4) + goto done; + strcpy(p, "'\\''"); + n -= 4; + p += 4; + break; + case '\0': + goto done; + default: + *p = *str; + p++; + } + } +done: + strncat(p, "'", n); +} + +static int +open_with_env(char *program, char *path) +{ + if (program) { +#ifdef RV_SHELL + strncpy(BUF1, program, BUFLEN - 1); + strncat(BUF1, " ", BUFLEN - strlen(program) - 1); + shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2); + spawn((char *[]){ RV_SHELL, "-c", BUF1, NULL }); +#else + spawn((char *[]){ program, path, NULL }); +#endif + return 1; + } + return 0; +} + +/* Curses setup. */ +static void +init_term() +{ + setlocale(LC_ALL, ""); + initscr(); + cbreak(); /* Get one character at a time. */ + timeout(100); /* For getch(). */ + noecho(); + nonl(); /* No NL->CR/NL on output. */ + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); /* Hide blinking cursor. */ + if (has_colors()) { + short bg; + start_color(); +#ifdef NCURSES_EXT_FUNCS + use_default_colors(); + bg = -1; +#else + bg = COLOR_BLACK; +#endif + init_pair(RED, COLOR_RED, bg); + init_pair(GREEN, COLOR_GREEN, bg); + init_pair(YELLOW, COLOR_YELLOW, bg); + init_pair(BLUE, COLOR_BLUE, bg); + init_pair(CYAN, COLOR_CYAN, bg); + init_pair(MAGENTA, COLOR_MAGENTA, bg); + init_pair(WHITE, COLOR_WHITE, bg); + init_pair(BLACK, COLOR_BLACK, bg); + } + atexit((void (*)(void))endwin); + enable_handlers(); +} + +/* Update the listing view. */ +static void +update_view() +{ + int i, j; + int numsize; + int ishidden; + int marking; + + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, BUF1); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(WBUF, CWD, PATH_MAX); + mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window + shrinking. In that case, the scroll must be moved to make it visible. */ + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = ENAME(j)[0] == '.'; + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + if (S_ISDIR(EMODE(j))) { + mbstowcs(WBUF, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(WBUF, L"/"); + } else { + char *suffix, *suffixes = "BKMGTPEZY"; + off_t human_size = ESIZE(j) * 10; + int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); + int namecols = wcswidth(WBUF, length); + for (suffix = suffixes; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + if (*suffix == 'B') + swprintf(WBUF + length, PATH_MAX - length, L"%*d %c", + (int)(COLS - namecols - 6), + (int)human_size / 10, *suffix); + else + swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c", + (int)(COLS - namecols - 8), + (int)human_size / 10, (int)human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); + } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + if (rover.nfiles > HEIGHT) { + int center, height; + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT - 1) * HEIGHT / rover.nfiles; + if (!height) + height = 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); + } + BUF1[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + BUF1[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + if (!rover.nfiles) + strcpy(BUF2, "0/0"); + else + snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles); + //snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2); + snprintf((BUF1), BUFLEN, "%12s", BUF2); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, BUF1); + wrefresh(rover.window); +} + +/* Show a message on the status bar. */ +static void +message(Color color, char *fmt, ...) +{ + int len, pos; + va_list args; + + va_start(args, fmt); + vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args); + va_end(args); + len = strlen(BUF1); + pos = (STATUSPOS - len) / 2; + attr_on(A_BOLD, NULL); + color_set(color, NULL); + mvaddstr(LINES - 1, pos, BUF1); + color_set(DEFAULT, NULL); + attr_off(A_BOLD, NULL); +} + +/* Clear message area, leaving only status info. */ +static void +clear_message() +{ + mvhline(LINES - 1, 0, ' ', STATUSPOS); +} + +/* Comparison used to sort listing entries. */ +static int +rowcmp(const void *a, const void *b) +{ + int isdir1, isdir2, cmpdir; + const Row *r1 = a; + const Row *r2 = b; + isdir1 = S_ISDIR(r1->mode); + isdir2 = S_ISDIR(r2->mode); + cmpdir = isdir2 - isdir1; + return cmpdir ? cmpdir : strcoll(r1->name, r2->name); +} + +/* Get all entries in current working directory. */ +static int +ls(Row **rowsp, uint8_t flags) +{ + DIR *dp; + struct dirent *ep; + struct stat statbuf; + Row *rows; + int i, n; + + if (!(dp = opendir("."))) + return -1; + n = -2; /* We don't want the entries "." and "..". */ + while (readdir(dp)) + n++; + if (n == 0) { + closedir(dp); + return 0; + } + rewinddir(dp); + rows = malloc(n * sizeof *rows); + i = 0; + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') + continue; + lstat(ep->d_name, &statbuf); + rows[i].islink = S_ISLNK(statbuf.st_mode); + stat(ep->d_name, &statbuf); + if (S_ISDIR(statbuf.st_mode)) { + if (flags & SHOW_DIRS) { + rows[i].name = malloc(strlen(ep->d_name) + 2); + strcpy(rows[i].name, ep->d_name); + if (!rows[i].islink) + strcat(rows[i].name, "/"); + rows[i].mode = statbuf.st_mode; + i++; + } + } else if (flags & SHOW_FILES) { + rows[i].name = malloc(strlen(ep->d_name) + 1); + strcpy(rows[i].name, ep->d_name); + rows[i].size = statbuf.st_size; + rows[i].mode = statbuf.st_mode; + i++; + } + } + n = i; /* Ignore unused space in array caused by filters. */ + qsort(rows, n, sizeof(*rows), rowcmp); + closedir(dp); + *rowsp = rows; + return n; +} + +static void +free_rows(Row **rowsp, int nfiles) +{ + int i; + + for (i = 0; i < nfiles; i++) + free((*rowsp)[i].name); + free(*rowsp); + *rowsp = NULL; +} + +/* Change working directory to the path in CWD. */ +static void +cd(int reset) +{ + int i, j; + + message(CYAN, "Loading \"%s\"...", CWD); + refresh(); + if (chdir(CWD) == -1) { + getcwd(CWD, PATH_MAX - 1); + if (CWD[strlen(CWD) - 1] != '/') + strcat(CWD, "/"); + goto done; + } + if (reset) + ESEL = SCROLL = 0; + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + rover.nfiles = ls(&rover.rows, FLAGS); + if (!strcmp(CWD, rover.marks.dirpath)) { + for (i = 0; i < rover.nfiles; i++) { + for (j = 0; j < rover.marks.bulk; j++) + if ( + rover.marks.entries[j] && + !strcmp(rover.marks.entries[j], ENAME(i))) + break; + MARKED(i) = j < rover.marks.bulk; + } + } else + for (i = 0; i < rover.nfiles; i++) + MARKED(i) = 0; +done: + clear_message(); + update_view(); +} + +/* Select a target entry, if it is present. */ +static void +try_to_sel(const char *target) +{ + ESEL = 0; + if (!ISDIR(target)) + while ((ESEL + 1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) + ESEL++; + while ((ESEL + 1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) + ESEL++; +} + +/* Reload CWD, but try to keep selection. */ +static void +reload() +{ + if (rover.nfiles) { + strcpy(INPUT, ENAME(ESEL)); + cd(0); + try_to_sel(INPUT); + update_view(); + } else + cd(1); +} + +static off_t +count_dir(const char *path) +{ + DIR *dp; + struct dirent *ep; + struct stat statbuf; + char subpath[PATH_MAX]; + off_t total; + + if (!(dp = opendir(path))) + return 0; + total = 0; + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); + lstat(subpath, &statbuf); + if (S_ISDIR(statbuf.st_mode)) { + strcat(subpath, "/"); + total += count_dir(subpath); + } else + total += statbuf.st_size; + } + closedir(dp); + return total; +} + +static off_t +count_marked() +{ + int i; + char *entry; + off_t total; + struct stat statbuf; + + total = 0; + chdir(rover.marks.dirpath); + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) { + if (ISDIR(entry)) { + total += count_dir(entry); + } else { + lstat(entry, &statbuf); + total += statbuf.st_size; + } + } + } + chdir(CWD); + return total; +} + +/* Recursively process a source directory using CWD as destination root. + For each node (i.e. directory), do the following: + 1. call pre(destination); + 2. call proc() on every child leaf (i.e. files); + 3. recurse into every child node; + 4. call pos(source). + E.g. to move directory /src/ (and all its contents) inside /dst/: + strcpy(CWD, "/dst/"); + process_dir(adddir, movfile, deldir, "/src/"); */ +static int +process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) +{ + int ret; + DIR *dp; + struct dirent *ep; + struct stat statbuf; + char subpath[PATH_MAX]; + + ret = 0; + if (pre) { + char dstpath[PATH_MAX]; + strcpy(dstpath, CWD); + strcat(dstpath, path + strlen(rover.marks.dirpath)); + ret |= pre(dstpath); + } + if (!(dp = opendir(path))) + return -1; + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); + lstat(subpath, &statbuf); + if (S_ISDIR(statbuf.st_mode)) { + strcat(subpath, "/"); + ret |= process_dir(pre, proc, pos, subpath); + } else + ret |= proc(subpath); + } + closedir(dp); + if (pos) + ret |= pos(path); + return ret; +} + +/* Process all marked entries using CWD as destination root. + All marked entries that are directories will be recursively processed. + See process_dir() for details on the parameters. */ +static void +process_marked(PROCESS pre, PROCESS proc, PROCESS pos, + const char *msg_doing, const char *msg_done) +{ + int i, ret; + char *entry; + char path[PATH_MAX]; + + clear_message(); + message(CYAN, "%s...", msg_doing); + refresh(); + rover.prog = (Prog){ 0, count_marked(), msg_doing }; + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) { + ret = 0; + snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); + if (ISDIR(entry)) { + if (!strncmp(path, CWD, strlen(path))) + ret = -1; + else + ret = process_dir(pre, proc, pos, path); + } else + ret = proc(path); + if (!ret) { + del_mark(&rover.marks, entry); + reload(); + } + } + } + rover.prog.total = 0; + reload(); + if (!rover.marks.nentries) + message(GREEN, "%s all marked entries.", msg_done); + else + message(RED, "Some errors occured while %s.", msg_doing); + RV_ALERT(); +} + +static void +update_progress(off_t delta) +{ + int percent; + + if (!rover.prog.total) + return; + rover.prog.partial += delta; + percent = (int)(rover.prog.partial * 100 / rover.prog.total); + message(CYAN, "%s...%d%%", rover.prog.msg, percent); + refresh(); +} + +/* Wrappers for file operations. */ +static int delfile(const char *path) +{ + int ret; + struct stat st; + + ret = lstat(path, &st); + if (ret < 0) + return ret; + update_progress(st.st_size); + return unlink(path); +} +static PROCESS deldir = rmdir; +static int addfile(const char *path) +{ + /* Using creat(2) because mknod(2) doesn't seem to be portable. */ + int ret; + + ret = creat(path, 0644); + if (ret < 0) + return ret; + return close(ret); +} +static int cpyfile(const char *srcpath) +{ + int src, dst, ret; + size_t size; + struct stat st; + char buf[BUFSIZ]; + char dstpath[PATH_MAX]; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + ret = lstat(srcpath, &st); + if (ret < 0) + return ret; + if (S_ISLNK(st.st_mode)) { + ret = readlink(srcpath, BUF1, BUFLEN - 1); + if (ret < 0) + return ret; + BUF1[ret] = '\0'; + ret = symlink(BUF1, dstpath); + } else { + ret = src = open(srcpath, O_RDONLY); + if (ret < 0) + return ret; + ret = dst = creat(dstpath, st.st_mode); + if (ret < 0) + return ret; + while ((size = read(src, buf, BUFSIZ)) > 0) { + write(dst, buf, size); + update_progress(size); + sync_signals(); + } + close(src); + close(dst); + ret = 0; + } + return ret; +} +static int adddir(const char *path) +{ + int ret; + struct stat st; + + ret = stat(CWD, &st); + if (ret < 0) + return ret; + return mkdir(path, st.st_mode); +} +static int movfile(const char *srcpath) +{ + int ret; + struct stat st; + char dstpath[PATH_MAX]; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + ret = rename(srcpath, dstpath); + if (ret == 0) { + ret = lstat(dstpath, &st); + if (ret < 0) + return ret; + update_progress(st.st_size); + } else if (errno == EXDEV) { + ret = cpyfile(srcpath); + if (ret < 0) + return ret; + ret = unlink(srcpath); + } + return ret; +} + +static void +start_line_edit(const char *init_input) +{ + curs_set(TRUE); + strncpy(INPUT, init_input, BUFLEN); + rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN); + rover.edit.right = BUFLEN - 1; + rover.edit.buffer[BUFLEN] = L'\0'; + rover.edit_scroll = 0; +} + +/* Read input and change editing state accordingly. */ +static EditStat +get_line_edit() +{ + wchar_t eraser, killer, wch; + int ret, length; + + ret = rover_get_wch((wint_t *)&wch); + erasewchar(&eraser); + killwchar(&killer); + if (ret == KEY_CODE_YES) { + if (wch == KEY_ENTER) { + curs_set(FALSE); + return CONFIRM; + } else if (wch == KEY_LEFT) { + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + } else if (wch == KEY_RIGHT) { + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + } else if (wch == KEY_UP) { + while (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + } else if (wch == KEY_DOWN) { + while (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + } else if (wch == KEY_BACKSPACE) { + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_BACKSPACE(rover.edit); + } else if (wch == KEY_DC) { + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_DELETE(rover.edit); + } + } else { + if (wch == L'\r' || wch == L'\n') { + curs_set(FALSE); + return CONFIRM; + } else if (wch == L'\t') { + curs_set(FALSE); + return CANCEL; + } else if (wch == eraser) { + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_BACKSPACE(rover.edit); + } else if (wch == killer) { + EDIT_CLEAR(rover.edit); + clear_message(); + } else if (iswprint(wch)) { + if (!EDIT_FULL(rover.edit)) + EDIT_INSERT(rover.edit, wch); + } + } + /* Encode edit contents in INPUT. */ + rover.edit.buffer[rover.edit.left] = L'\0'; + length = wcstombs(INPUT, rover.edit.buffer, BUFLEN); + wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right + 1], + BUFLEN - length); + return CONTINUE; +} + +/* Update line input on the screen. */ +static void +update_input(const char *prompt, Color color) +{ + int plen, ilen, maxlen; + + plen = strlen(prompt); + ilen = mbstowcs(NULL, INPUT, 0); + maxlen = STATUSPOS - plen - 2; + if (ilen - rover.edit_scroll < maxlen) + rover.edit_scroll = MAX(ilen - maxlen, 0); + else if (rover.edit.left > rover.edit_scroll + maxlen - 1) + rover.edit_scroll = rover.edit.left - maxlen; + else if (rover.edit.left < rover.edit_scroll) + rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); + color_set(RVC_PROMPT, NULL); + mvaddstr(LINES - 1, 0, prompt); + color_set(color, NULL); + mbstowcs(WBUF, INPUT, COLS); + mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen); + mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); + color_set(DEFAULT, NULL); + if (rover.edit_scroll) + mvaddch(LINES - 1, plen - 1, '<'); + if (ilen > rover.edit_scroll + maxlen) + mvaddch(LINES - 1, plen + maxlen, '>'); + move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); +} + +int main(int argc, char *argv[]) +{ + int i, ch; + char *program; + char *entry; + const char *key; + const char *clip_path; + DIR *d; + EditStat edit_stat; + FILE *save_cwd_file = NULL; + FILE *save_marks_file = NULL; + FILE *clip_file; + + if (argc >= 2) { + if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { + printf("rover %s\n", RV_VERSION); + return 0; + } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + printf( + "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" + " Browse current directory or the ones specified.\n\n" + " or: rover -h|--help\n" + " Print this help message and exit.\n\n" + " or: rover -v|--version\n" + " Print program version and exit.\n\n" + "See rover(1) for more information.\n" + "Rover homepage: .\n"); + return 0; + } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { + if (argc > 2) { + save_cwd_file = fopen(argv[2], "w"); + argc -= 2; + argv += 2; + } else { + fprintf(stderr, "error: missing argument to %s\n", argv[1]); + return 1; + } + } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { + if (argc > 2) { + save_marks_file = fopen(argv[2], "a"); + argc -= 2; + argv += 2; + } else { + fprintf(stderr, "error: missing argument to %s\n", argv[1]); + return 1; + } + } + } + get_user_programs(); + init_term(); + rover.nfiles = 0; + for (i = 0; i < 10; i++) { + rover.tabs[i].esel = rover.tabs[i].scroll = 0; + rover.tabs[i].flags = RV_FLAGS; + } + strcpy(rover.tabs[0].cwd, getenv("HOME")); + for (i = 1; i < argc && i < 10; i++) { + if ((d = opendir(argv[i]))) { + realpath(argv[i], rover.tabs[i].cwd); + closedir(d); + } else + strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); + } + getcwd(rover.tabs[i].cwd, PATH_MAX); + for (i++; i < 10; i++) + strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); + for (i = 0; i < 10; i++) + if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') + strcat(rover.tabs[i].cwd, "/"); + rover.tab = 1; + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + init_marks(&rover.marks); + cd(1); + strcpy(CLIPBOARD, CWD); + if (rover.nfiles > 0) + strcat(CLIPBOARD, ENAME(ESEL)); + while (1) { + ch = rover_getch(); + key = keyname(ch); + clear_message(); + if (!strcmp(key, RVK_QUIT)) + break; + else if (ch >= '0' && ch <= '9') { + rover.tab = ch - '0'; + cd(0); + } else if (!strcmp(key, RVK_HELP)) { + spawn((char *[]){ "man", "rover", NULL }); + } else if (!strcmp(key, RVK_DOWN)) { + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + 1, rover.nfiles - 1); + update_view(); + } else if (!strcmp(key, RVK_UP)) { + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - 1, 0); + update_view(); + } else if (!strcmp(key, RVK_JUMP_DOWN)) { + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); + if (rover.nfiles > HEIGHT) + SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); + update_view(); + } else if (!strcmp(key, RVK_JUMP_UP)) { + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - RV_JUMP, 0); + SCROLL = MAX(SCROLL - RV_JUMP, 0); + update_view(); + } else if (!strcmp(key, RVK_JUMP_TOP)) { + if (!rover.nfiles) + continue; + ESEL = 0; + update_view(); + } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { + if (!rover.nfiles) + continue; + ESEL = rover.nfiles - 1; + update_view(); + } else if (!strcmp(key, RVK_CD_DOWN)) { + if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) + continue; + if (chdir(ENAME(ESEL)) == -1) { + message(RED, "Cannot access \"%s\".", ENAME(ESEL)); + continue; + } + strcat(CWD, ENAME(ESEL)); + cd(1); + } else if (!strcmp(key, RVK_CD_UP)) { + char *dirname, first; + if (!strcmp(CWD, "/")) + continue; + CWD[strlen(CWD) - 1] = '\0'; + dirname = strrchr(CWD, '/') + 1; + first = dirname[0]; + dirname[0] = '\0'; + cd(1); + dirname[0] = first; + dirname[strlen(dirname)] = '/'; + try_to_sel(dirname); + dirname[0] = '\0'; + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; + update_view(); + } else if (!strcmp(key, RVK_HOME)) { + strcpy(CWD, getenv("HOME")); + if (CWD[strlen(CWD) - 1] != '/') + strcat(CWD, "/"); + cd(1); + } else if (!strcmp(key, RVK_TARGET)) { + char *bname, first; + int is_dir = S_ISDIR(EMODE(ESEL)); + ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN - 1); + if (len == -1) + continue; + BUF1[len] = '\0'; + if (access(BUF1, F_OK) == -1) { + char *msg; + switch (errno) { + case EACCES: + msg = "Cannot access \"%s\"."; + break; + case ENOENT: + msg = "\"%s\" does not exist."; + break; + default: + msg = "Cannot navigate to \"%s\"."; + } + strcpy(BUF2, BUF1); /* message() uses BUF1. */ + message(RED, msg, BUF2); + continue; + } + realpath(BUF1, CWD); + len = strlen(CWD); + if (CWD[len - 1] == '/') + CWD[len - 1] = '\0'; + bname = strrchr(CWD, '/') + 1; + first = *bname; + *bname = '\0'; + cd(1); + *bname = first; + if (is_dir) + strcat(CWD, "/"); + try_to_sel(bname); + *bname = '\0'; + update_view(); + } else if (!strcmp(key, RVK_COPY_PATH)) { + clip_path = getenv("CLIP"); + if (!clip_path) + goto copy_path_fail; + clip_file = fopen(clip_path, "w"); + if (!clip_file) + goto copy_path_fail; + fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); + fclose(clip_file); + goto copy_path_done; +copy_path_fail: + strcpy(CLIPBOARD, CWD); + strcat(CLIPBOARD, ENAME(ESEL)); +copy_path_done:; + } else if (!strcmp(key, RVK_PASTE_PATH)) { + clip_path = getenv("CLIP"); + if (!clip_path) + goto paste_path_fail; + clip_file = fopen(clip_path, "r"); + if (!clip_file) + goto paste_path_fail; + fscanf(clip_file, "%s\n", CLIPBOARD); + fclose(clip_file); +paste_path_fail: + strcpy(BUF1, CLIPBOARD); + strcpy(CWD, dirname(BUF1)); + if (strcmp(CWD, "/")) + strcat(CWD, "/"); + cd(1); + strcpy(BUF1, CLIPBOARD); + try_to_sel(strstr(CLIPBOARD, basename(BUF1))); + update_view(); + } else if (!strcmp(key, RVK_REFRESH)) { + reload(); + } else if (!strcmp(key, RVK_SHELL)) { + program = user_shell; + if (program) { +#ifdef RV_SHELL + spawn((char *[]){ RV_SHELL, "-c", program, NULL }); +#else + spawn((char *[]){ program, NULL }); +#endif + reload(); + } + } else if (!strcmp(key, RVK_VIEW)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_pager, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_EDIT)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_editor, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_OPEN)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_open, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_SEARCH)) { + int oldsel, oldscroll, length; + if (!rover.nfiles) + continue; + oldsel = ESEL; + oldscroll = SCROLL; + start_line_edit(""); + update_input(RVP_SEARCH, RED); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int sel; + Color color = RED; + length = strlen(INPUT); + if (length) { + for (sel = 0; sel < rover.nfiles; sel++) + if (!strncmp(ENAME(sel), INPUT, length)) + break; + if (sel < rover.nfiles) { + color = GREEN; + ESEL = sel; + if (rover.nfiles > HEIGHT) { + if (sel < 3) + SCROLL = 0; + else if (sel - 3 > rover.nfiles - HEIGHT) + SCROLL = rover.nfiles - HEIGHT; + else + SCROLL = sel - 3; + } + } + } else { + ESEL = oldsel; + SCROLL = oldscroll; + } + update_view(); + update_input(RVP_SEARCH, color); + } + if (edit_stat == CANCEL) { + ESEL = oldsel; + SCROLL = oldscroll; + } + clear_message(); + update_view(); + } else if (!strcmp(key, RVK_TG_FILES)) { + FLAGS ^= SHOW_FILES; + reload(); + } else if (!strcmp(key, RVK_TG_DIRS)) { + FLAGS ^= SHOW_DIRS; + reload(); + } else if (!strcmp(key, RVK_TG_HIDDEN)) { + FLAGS ^= SHOW_HIDDEN; + reload(); + } else if (!strcmp(key, RVK_NEW_FILE)) { + int ok = 0; + start_line_edit(""); + update_input(RVP_NEW_FILE, RED); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(INPUT); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), INPUT, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_FILE, ok ? GREEN : RED); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (ok) { + if (addfile(INPUT) == 0) { + cd(1); + try_to_sel(INPUT); + update_view(); + } else + message(RED, "Could not create \"%s\".", INPUT); + } else + message(RED, "\"%s\" already exists.", INPUT); + } + } else if (!strcmp(key, RVK_NEW_DIR)) { + int ok = 0; + start_line_edit(""); + update_input(RVP_NEW_DIR, RED); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(INPUT); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), INPUT, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_DIR, ok ? GREEN : RED); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (ok) { + if (adddir(INPUT) == 0) { + cd(1); + strcat(INPUT, "/"); + try_to_sel(INPUT); + update_view(); + } else + message(RED, "Could not create \"%s/\".", INPUT); + } else + message(RED, "\"%s\" already exists.", INPUT); + } + } else if (!strcmp(key, RVK_RENAME)) { + int ok = 0; + char *last; + int isdir; + strcpy(INPUT, ENAME(ESEL)); + last = INPUT + strlen(INPUT) - 1; + if ((isdir = *last == '/')) + *last = '\0'; + start_line_edit(INPUT); + update_input(RVP_RENAME, RED); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(INPUT); + ok = length; + for (i = 0; i < rover.nfiles; i++) + if ( + !strncmp(ENAME(i), INPUT, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + update_input(RVP_RENAME, ok ? GREEN : RED); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (isdir) + strcat(INPUT, "/"); + if (ok) { + if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) { + del_mark(&rover.marks, ENAME(ESEL)); + add_mark(&rover.marks, CWD, INPUT); + } + cd(1); + try_to_sel(INPUT); + update_view(); + } else + message(RED, "\"%s\" already exists.", INPUT); + } + } else if (!strcmp(key, RVK_TG_EXEC)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (S_IXUSR & EMODE(ESEL)) + EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); + else + EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; + if (chmod(ENAME(ESEL), EMODE(ESEL))) { + message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); + } else { + message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); + update_view(); + } + } else if (!strcmp(key, RVK_DELETE)) { + if (rover.nfiles) { + message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); + if (rover_getch() == 'Y') { + const char *name = ENAME(ESEL); + int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); + reload(); + if (ret) + message(RED, "Could not delete \"%s\".", ENAME(ESEL)); + } else + clear_message(); + } else + message(RED, "No entry selected for deletion."); + } else if (!strcmp(key, RVK_TG_MARK)) { + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + else + add_mark(&rover.marks, CWD, ENAME(ESEL)); + MARKED(ESEL) = !MARKED(ESEL); + ESEL = (ESEL + 1) % rover.nfiles; + update_view(); + } else if (!strcmp(key, RVK_INVMARK)) { + for (i = 0; i < rover.nfiles; i++) { + if (MARKED(i)) + del_mark(&rover.marks, ENAME(i)); + else + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = !MARKED(i); + } + update_view(); + } else if (!strcmp(key, RVK_MARKALL)) { + for (i = 0; i < rover.nfiles; i++) + if (!MARKED(i)) { + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = 1; + } + update_view(); + } else if (!strcmp(key, RVK_MARK_DELETE)) { + if (rover.marks.nentries) { + message(YELLOW, "Delete all marked entries? (Y/n)"); + if (rover_getch() == 'Y') + process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); + else + clear_message(); + } else + message(RED, "No entries marked for deletion."); + } else if (!strcmp(key, RVK_MARK_COPY)) { + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); + else + message(RED, "Cannot copy to the same path."); + } else + message(RED, "No entries marked for copying."); + } else if (!strcmp(key, RVK_MARK_MOVE)) { + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, movfile, deldir, "Moving", "Moved"); + else + message(RED, "Cannot move to the same path."); + } else + message(RED, "No entries marked for moving."); + } + } + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + delwin(rover.window); + if (save_cwd_file != NULL) { + fputs(CWD, save_cwd_file); + fclose(save_cwd_file); + } + if (save_marks_file != NULL) { + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) + fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); + } + fclose(save_marks_file); + } + free_marks(&rover.marks); + return 0; +} From 38156d19a62a395eeafc24d28bd0d7ca8ec42445 Mon Sep 17 00:00:00 2001 From: Sandroid75 Date: Tue, 27 Dec 2022 22:43:53 +0100 Subject: [PATCH 02/18] improved .clang-format --- .clang-format | 7 +-- .gitignore | 1 + src/rover.c | 146 +++++++++++++++++++++++++------------------------- 3 files changed, 75 insertions(+), 79 deletions(-) diff --git a/.clang-format b/.clang-format index 000e206..e36290f 100644 --- a/.clang-format +++ b/.clang-format @@ -18,12 +18,7 @@ AlignConsecutiveAssignments: AcrossEmptyLines: false AcrossComments: false AcrossEmptyLinesAndComments: false -AlignConsecutiveDeclarations: - Enabled: true - Consecutive: true - AcrossEmptyLines: false - AcrossComments: false - AcrossEmptyLinesAndComments: false +AlignConsecutiveDeclarations: false AlignConsecutiveMacros: Enabled: true Consecutive: true diff --git a/.gitignore b/.gitignore index d85ed88..087824b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ rover + diff --git a/src/rover.c b/src/rover.c index 3c3b2a8..9b3f6a3 100644 --- a/src/rover.c +++ b/src/rover.c @@ -35,10 +35,10 @@ /* String buffers. */ #define BUFLEN PATH_MAX -static char BUF1[BUFLEN]; -static char BUF2[BUFLEN]; -static char INPUT[BUFLEN]; -static char CLIPBOARD[BUFLEN]; +static char BUF1[BUFLEN]; +static char BUF2[BUFLEN]; +static char INPUT[BUFLEN]; +static char CLIPBOARD[BUFLEN]; static wchar_t WBUF[BUFLEN]; /* Paths to external programs. */ @@ -62,54 +62,54 @@ static char *user_open; /* Information associated to each entry in listing. */ typedef struct Row { - char *name; - off_t size; + char *name; + off_t size; mode_t mode; - int islink; - int marked; + int islink; + int marked; } Row; /* Dynamic array of marked entries. */ typedef struct Marks { - char dirpath[PATH_MAX]; - int bulk; - int nentries; + char dirpath[PATH_MAX]; + int bulk; + int nentries; char **entries; } Marks; /* Line editing state. */ typedef struct Edit { wchar_t buffer[BUFLEN + 1]; - int left, right; + int left, right; } Edit; /* Each tab only stores the following information. */ typedef struct Tab { - int scroll; - int esel; + int scroll; + int esel; uint8_t flags; - char cwd[PATH_MAX]; + char cwd[PATH_MAX]; } Tab; typedef struct Prog { - off_t partial; - off_t total; + off_t partial; + off_t total; const char *msg; } Prog; /* Global state. */ static struct Rover { - int tab; - int nfiles; - Row *rows; - WINDOW *window; - Marks marks; - Edit edit; - int edit_scroll; + int tab; + int nfiles; + Row *rows; + WINDOW *window; + Marks marks; + Edit edit; + int edit_scroll; volatile sig_atomic_t pending_usr1; volatile sig_atomic_t pending_winch; - Prog prog; - Tab tabs[10]; + Prog prog; + Tab tabs[10]; } rover; /* Macros for accessing global state. */ @@ -356,7 +356,7 @@ static void spawn(char **args) { pid_t pid; - int status; + int status; setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); pid = fork(); @@ -514,8 +514,8 @@ update_view() } else { char *suffix, *suffixes = "BKMGTPEZY"; off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); - int namecols = wcswidth(WBUF, length); + int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); + int namecols = wcswidth(WBUF, length); for (suffix = suffixes; human_size >= 10240; suffix++) human_size = (human_size + 512) / 1024; if (*suffix == 'B') @@ -566,7 +566,7 @@ update_view() static void message(Color color, char *fmt, ...) { - int len, pos; + int len, pos; va_list args; va_start(args, fmt); @@ -592,7 +592,7 @@ clear_message() static int rowcmp(const void *a, const void *b) { - int isdir1, isdir2, cmpdir; + int isdir1, isdir2, cmpdir; const Row *r1 = a; const Row *r2 = b; isdir1 = S_ISDIR(r1->mode); @@ -605,11 +605,11 @@ rowcmp(const void *a, const void *b) static int ls(Row **rowsp, uint8_t flags) { - DIR *dp; + DIR *dp; struct dirent *ep; - struct stat statbuf; - Row *rows; - int i, n; + struct stat statbuf; + Row *rows; + int i, n; if (!(dp = opendir("."))) return -1; @@ -730,11 +730,11 @@ reload() static off_t count_dir(const char *path) { - DIR *dp; + DIR *dp; struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - off_t total; + struct stat statbuf; + char subpath[PATH_MAX]; + off_t total; if (!(dp = opendir(path))) return 0; @@ -757,9 +757,9 @@ count_dir(const char *path) static off_t count_marked() { - int i; - char *entry; - off_t total; + int i; + char *entry; + off_t total; struct stat statbuf; total = 0; @@ -791,11 +791,11 @@ count_marked() static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) { - int ret; - DIR *dp; + int ret; + DIR *dp; struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; + struct stat statbuf; + char subpath[PATH_MAX]; ret = 0; if (pre) { @@ -830,9 +830,9 @@ static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) { - int i, ret; + int i, ret; char *entry; - char path[PATH_MAX]; + char path[PATH_MAX]; clear_message(); message(CYAN, "%s...", msg_doing); @@ -881,7 +881,7 @@ update_progress(off_t delta) /* Wrappers for file operations. */ static int delfile(const char *path) { - int ret; + int ret; struct stat st; ret = lstat(path, &st); @@ -891,7 +891,7 @@ static int delfile(const char *path) return unlink(path); } static PROCESS deldir = rmdir; -static int addfile(const char *path) +static int addfile(const char *path) { /* Using creat(2) because mknod(2) doesn't seem to be portable. */ int ret; @@ -903,11 +903,11 @@ static int addfile(const char *path) } static int cpyfile(const char *srcpath) { - int src, dst, ret; - size_t size; + int src, dst, ret; + size_t size; struct stat st; - char buf[BUFSIZ]; - char dstpath[PATH_MAX]; + char buf[BUFSIZ]; + char dstpath[PATH_MAX]; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); @@ -940,7 +940,7 @@ static int cpyfile(const char *srcpath) } static int adddir(const char *path) { - int ret; + int ret; struct stat st; ret = stat(CWD, &st); @@ -950,9 +950,9 @@ static int adddir(const char *path) } static int movfile(const char *srcpath) { - int ret; + int ret; struct stat st; - char dstpath[PATH_MAX]; + char dstpath[PATH_MAX]; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); @@ -987,7 +987,7 @@ static EditStat get_line_edit() { wchar_t eraser, killer, wch; - int ret, length; + int ret, length; ret = rover_get_wch((wint_t *)&wch); erasewchar(&eraser); @@ -1072,16 +1072,16 @@ update_input(const char *prompt, Color color) int main(int argc, char *argv[]) { - int i, ch; - char *program; - char *entry; + int i, ch; + char *program; + char *entry; const char *key; const char *clip_path; - DIR *d; - EditStat edit_stat; - FILE *save_cwd_file = NULL; - FILE *save_marks_file = NULL; - FILE *clip_file; + DIR *d; + EditStat edit_stat; + FILE *save_cwd_file = NULL; + FILE *save_marks_file = NULL; + FILE *clip_file; if (argc >= 2) { if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { @@ -1221,9 +1221,9 @@ int main(int argc, char *argv[]) strcat(CWD, "/"); cd(1); } else if (!strcmp(key, RVK_TARGET)) { - char *bname, first; - int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN - 1); + char *bname, first; + int is_dir = S_ISDIR(EMODE(ESEL)); + ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN - 1); if (len == -1) continue; BUF1[len] = '\0'; @@ -1325,7 +1325,7 @@ copy_path_done:; start_line_edit(""); update_input(RVP_SEARCH, RED); while ((edit_stat = get_line_edit()) == CONTINUE) { - int sel; + int sel; Color color = RED; length = strlen(INPUT); if (length) { @@ -1428,9 +1428,9 @@ copy_path_done:; message(RED, "\"%s\" already exists.", INPUT); } } else if (!strcmp(key, RVK_RENAME)) { - int ok = 0; + int ok = 0; char *last; - int isdir; + int isdir; strcpy(INPUT, ENAME(ESEL)); last = INPUT + strlen(INPUT) - 1; if ((isdir = *last == '/')) @@ -1483,7 +1483,7 @@ copy_path_done:; message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); if (rover_getch() == 'Y') { const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); + int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); reload(); if (ret) message(RED, "Could not delete \"%s\".", ENAME(ESEL)); From 97574f5339ed13e171f4295e0be960529b39c93d Mon Sep 17 00:00:00 2001 From: Sandro Date: Wed, 28 Dec 2022 17:59:01 +0100 Subject: [PATCH 03/18] clang-format and remove static To Do: organize better files, move functions in specific files like main() -> main.c --- .clang-format | 15 ++- src/config.h | 164 +++++++++++++++++++++++++++++ src/rover.c | 282 +++++++++----------------------------------------- 3 files changed, 224 insertions(+), 237 deletions(-) diff --git a/.clang-format b/.clang-format index e36290f..19b69a2 100644 --- a/.clang-format +++ b/.clang-format @@ -29,7 +29,7 @@ AlignConsecutiveStyle: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: false -AllowAllParametersOfDeclarationOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None @@ -686,8 +686,17 @@ SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: false + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: false + AfterOverloadedOperator: true + BeforeNonEmptyParentheses: false SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false @@ -695,7 +704,7 @@ SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp03 +Standard: c++20 TabWidth: 4 UseTab: AlignWithSpaces ... \ No newline at end of file diff --git a/src/config.h b/src/config.h index 6426825..b9dd6c1 100644 --- a/src/config.h +++ b/src/config.h @@ -1,3 +1,33 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#define _XOPEN_SOURCE_EXTENDED +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include /* pid_t, ... */ +#include +#include /* PATH_MAX */ +#include /* setlocale(), LC_ALL */ +#include /* chdir(), getcwd(), read(), close(), ... */ +#include /* DIR, struct dirent, opendir(), ... */ +#include +#include +#include /* open() */ +#include /* waitpid() */ +#include /* struct sigaction, sigaction() */ +#include +#include +#include + #define RV_VERSION "1.0.1" /* CTRL+X: "^X" @@ -86,3 +116,137 @@ undefined if you prefer external programs to be launched with just `$EXTERNAL_PROGRAM [arg]`. */ #define RV_SHELL "/bin/sh" + +/* This signal is not defined by POSIX, but should be + present on all systems that have resizable terminals. */ +#ifndef SIGWINCH +#define SIGWINCH 28 +#endif + +/* String buffers. */ +#define BUFLEN PATH_MAX +static char BUF1[BUFLEN]; +static char BUF2[BUFLEN]; +static char INPUT[BUFLEN]; +static char CLIPBOARD[BUFLEN]; +static wchar_t WBUF[BUFLEN]; + +/* Paths to external programs. */ +static char *user_shell; +static char *user_pager; +static char *user_editor; +static char *user_open; + +/* Listing view parameters. */ +#define HEIGHT (LINES - 4) +#define STATUSPOS (COLS - 16) + +/* Listing view flags. */ +#define SHOW_FILES 0x01u +#define SHOW_DIRS 0x02u +#define SHOW_HIDDEN 0x04u + +/* Marks parameters. */ +#define BULK_INIT 5 +#define BULK_THRESH 256 + +/* Information associated to each entry in listing. */ +typedef struct Row { + char *name; + off_t size; + mode_t mode; + int islink; + int marked; +} Row; + +/* Dynamic array of marked entries. */ +typedef struct Marks { + char dirpath[PATH_MAX]; + int bulk; + int nentries; + char **entries; +} Marks; + +/* Line editing state. */ +typedef struct Edit { + wchar_t buffer[BUFLEN + 1]; + int left, right; +} Edit; + +/* Each tab only stores the following information. */ +typedef struct Tab { + int scroll; + int esel; + uint8_t flags; + char cwd[PATH_MAX]; +} Tab; + +typedef struct Prog { + off_t partial; + off_t total; + const char *msg; +} Prog; + +/* Global state. */ +static struct Rover { + int tab; + int nfiles; + Row *rows; + WINDOW *window; + Marks marks; + Edit edit; + int edit_scroll; + volatile sig_atomic_t pending_usr1; + volatile sig_atomic_t pending_winch; + Prog prog; + Tab tabs[10]; +} rover; + +/* Macros for accessing global state. */ +#define ENAME(I) rover.rows[I].name +#define ESIZE(I) rover.rows[I].size +#define EMODE(I) rover.rows[I].mode +#define ISLINK(I) rover.rows[I].islink +#define MARKED(I) rover.rows[I].marked +#define SCROLL rover.tabs[rover.tab].scroll +#define ESEL rover.tabs[rover.tab].esel +#define FLAGS rover.tabs[rover.tab].flags +#define CWD rover.tabs[rover.tab].cwd + +/* Helpers. */ +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define ISDIR(E) (strchr((E), '/') != NULL) + +/* Line Editing Macros. */ +#define EDIT_FULL(E) ((E).left == (E).right) +#define EDIT_CAN_LEFT(E) ((E).left) +#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN - 1) +#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] +#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] +#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) +#define EDIT_BACKSPACE(E) (E).left-- +#define EDIT_DELETE(E) (E).right++ +#define EDIT_CLEAR(E) \ + do { \ + (E).left = 0; \ + (E).right = BUFLEN - 1; \ + } while (0) + +typedef enum EditStat { CONTINUE, + CONFIRM, + CANCEL } EditStat; +typedef enum Color { DEFAULT, + RED, + GREEN, + YELLOW, + BLUE, + CYAN, + MAGENTA, + WHITE, + BLACK } Color; +typedef int (*PROCESS)(const char *path); + + + +#endif // _CONFIG_H \ No newline at end of file diff --git a/src/rover.c b/src/rover.c index 9b3f6a3..cbeb37b 100644 --- a/src/rover.c +++ b/src/rover.c @@ -1,164 +1,7 @@ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif -#define _XOPEN_SOURCE_EXTENDED -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include /* pid_t, ... */ -#include -#include /* PATH_MAX */ -#include /* setlocale(), LC_ALL */ -#include /* chdir(), getcwd(), read(), close(), ... */ -#include /* DIR, struct dirent, opendir(), ... */ -#include -#include -#include /* open() */ -#include /* waitpid() */ -#include /* struct sigaction, sigaction() */ -#include -#include -#include #include "config.h" -/* This signal is not defined by POSIX, but should be - present on all systems that have resizable terminals. */ -#ifndef SIGWINCH -#define SIGWINCH 28 -#endif - -/* String buffers. */ -#define BUFLEN PATH_MAX -static char BUF1[BUFLEN]; -static char BUF2[BUFLEN]; -static char INPUT[BUFLEN]; -static char CLIPBOARD[BUFLEN]; -static wchar_t WBUF[BUFLEN]; - -/* Paths to external programs. */ -static char *user_shell; -static char *user_pager; -static char *user_editor; -static char *user_open; - -/* Listing view parameters. */ -#define HEIGHT (LINES - 4) -#define STATUSPOS (COLS - 16) - -/* Listing view flags. */ -#define SHOW_FILES 0x01u -#define SHOW_DIRS 0x02u -#define SHOW_HIDDEN 0x04u - -/* Marks parameters. */ -#define BULK_INIT 5 -#define BULK_THRESH 256 - -/* Information associated to each entry in listing. */ -typedef struct Row { - char *name; - off_t size; - mode_t mode; - int islink; - int marked; -} Row; - -/* Dynamic array of marked entries. */ -typedef struct Marks { - char dirpath[PATH_MAX]; - int bulk; - int nentries; - char **entries; -} Marks; - -/* Line editing state. */ -typedef struct Edit { - wchar_t buffer[BUFLEN + 1]; - int left, right; -} Edit; - -/* Each tab only stores the following information. */ -typedef struct Tab { - int scroll; - int esel; - uint8_t flags; - char cwd[PATH_MAX]; -} Tab; - -typedef struct Prog { - off_t partial; - off_t total; - const char *msg; -} Prog; - -/* Global state. */ -static struct Rover { - int tab; - int nfiles; - Row *rows; - WINDOW *window; - Marks marks; - Edit edit; - int edit_scroll; - volatile sig_atomic_t pending_usr1; - volatile sig_atomic_t pending_winch; - Prog prog; - Tab tabs[10]; -} rover; - -/* Macros for accessing global state. */ -#define ENAME(I) rover.rows[I].name -#define ESIZE(I) rover.rows[I].size -#define EMODE(I) rover.rows[I].mode -#define ISLINK(I) rover.rows[I].islink -#define MARKED(I) rover.rows[I].marked -#define SCROLL rover.tabs[rover.tab].scroll -#define ESEL rover.tabs[rover.tab].esel -#define FLAGS rover.tabs[rover.tab].flags -#define CWD rover.tabs[rover.tab].cwd - -/* Helpers. */ -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define ISDIR(E) (strchr((E), '/') != NULL) - -/* Line Editing Macros. */ -#define EDIT_FULL(E) ((E).left == (E).right) -#define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN - 1) -#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] -#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] -#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) -#define EDIT_BACKSPACE(E) (E).left-- -#define EDIT_DELETE(E) (E).right++ -#define EDIT_CLEAR(E) \ - do { \ - (E).left = 0; \ - (E).right = BUFLEN - 1; \ - } while (0) - -typedef enum EditStat { CONTINUE, - CONFIRM, - CANCEL } EditStat; -typedef enum Color { DEFAULT, - RED, - GREEN, - YELLOW, - BLUE, - CYAN, - MAGENTA, - WHITE, - BLACK } Color; -typedef int (*PROCESS)(const char *path); - -static void -init_marks(Marks *marks) +void init_marks(Marks *marks) { strcpy(marks->dirpath, ""); marks->bulk = BULK_INIT; @@ -167,8 +10,7 @@ init_marks(Marks *marks) } /* Unmark all entries. */ -static void -mark_none(Marks *marks) +void mark_none(Marks *marks) { int i; @@ -187,8 +29,7 @@ mark_none(Marks *marks) } } -static void -add_mark(Marks *marks, char *dirpath, char *entry) +void add_mark(Marks *marks, char *dirpath, char *entry) { int i; @@ -220,8 +61,7 @@ add_mark(Marks *marks, char *dirpath, char *entry) marks->nentries++; } -static void -del_mark(Marks *marks, char *entry) +void del_mark(Marks *marks, char *entry) { int i; @@ -236,8 +76,7 @@ del_mark(Marks *marks, char *entry) mark_none(marks); } -static void -free_marks(Marks *marks) +void free_marks(Marks *marks) { int i; @@ -249,20 +88,17 @@ free_marks(Marks *marks) free(marks->entries); } -static void -handle_usr1(int sig) +void handle_usr1(int sig) { rover.pending_usr1 = 1; } -static void -handle_winch(int sig) +void handle_winch(int sig) { rover.pending_winch = 1; } -static void -enable_handlers() +void enable_handlers() { struct sigaction sa; @@ -273,8 +109,7 @@ enable_handlers() sigaction(SIGWINCH, &sa, NULL); } -static void -disable_handlers() +void disable_handlers() { struct sigaction sa; @@ -284,12 +119,11 @@ disable_handlers() sigaction(SIGWINCH, &sa, NULL); } -static void reload(); -static void update_view(); +void reload(); +void update_view(); /* Handle any signals received since last call. */ -static void -sync_signals() +void sync_signals() { if (rover.pending_usr1) { /* SIGUSR1 received: refresh directory listing. */ @@ -312,8 +146,7 @@ sync_signals() /* This function must be used in place of getch(). It handles signals while waiting for user input. */ -static int -rover_getch() +int rover_getch() { int ch; @@ -324,8 +157,7 @@ rover_getch() /* This function must be used in place of get_wch(). It handles signals while waiting for user input. */ -static int -rover_get_wch(wint_t *wch) +int rover_get_wch(wint_t *wch) { wint_t ret; @@ -340,8 +172,7 @@ rover_get_wch(wint_t *wch) if ((dst = getenv("ROVER_" #src)) == NULL) \ dst = getenv(#src); -static void -get_user_programs() +void get_user_programs() { ROVER_ENV(user_shell, SHELL) ROVER_ENV(user_pager, PAGER) @@ -352,8 +183,7 @@ get_user_programs() } /* Do a fork-exec to external program (e.g. $EDITOR). */ -static void -spawn(char **args) +void spawn(char **args) { pid_t pid; int status; @@ -373,8 +203,7 @@ spawn(char **args) } } -static void -shell_escaped_cat(char *buf, char *str, size_t n) +void shell_escaped_cat(char *buf, char *str, size_t n) { char *p = buf + strlen(buf); *p++ = '\''; @@ -398,8 +227,7 @@ shell_escaped_cat(char *buf, char *str, size_t n) strncat(p, "'", n); } -static int -open_with_env(char *program, char *path) +int open_with_env(char *program, char *path) { if (program) { #ifdef RV_SHELL @@ -416,8 +244,7 @@ open_with_env(char *program, char *path) } /* Curses setup. */ -static void -init_term() +void init_term() { setlocale(LC_ALL, ""); initscr(); @@ -451,8 +278,7 @@ init_term() } /* Update the listing view. */ -static void -update_view() +void update_view() { int i, j; int numsize; @@ -555,16 +381,14 @@ update_view() strcpy(BUF2, "0/0"); else snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles); - //snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2); - snprintf((BUF1), BUFLEN, "%12s", BUF2); + snprintf(BUF1 + 3, BUFLEN, "%12s", BUF2); color_set(RVC_STATUS, NULL); mvaddstr(LINES - 1, STATUSPOS, BUF1); wrefresh(rover.window); } /* Show a message on the status bar. */ -static void -message(Color color, char *fmt, ...) +void message(Color color, char *fmt, ...) { int len, pos; va_list args; @@ -582,15 +406,13 @@ message(Color color, char *fmt, ...) } /* Clear message area, leaving only status info. */ -static void -clear_message() +void clear_message() { mvhline(LINES - 1, 0, ' ', STATUSPOS); } /* Comparison used to sort listing entries. */ -static int -rowcmp(const void *a, const void *b) +int rowcmp(const void *a, const void *b) { int isdir1, isdir2, cmpdir; const Row *r1 = a; @@ -602,8 +424,7 @@ rowcmp(const void *a, const void *b) } /* Get all entries in current working directory. */ -static int -ls(Row **rowsp, uint8_t flags) +int ls(Row **rowsp, uint8_t flags) { DIR *dp; struct dirent *ep; @@ -655,8 +476,7 @@ ls(Row **rowsp, uint8_t flags) return n; } -static void -free_rows(Row **rowsp, int nfiles) +void free_rows(Row **rowsp, int nfiles) { int i; @@ -667,8 +487,7 @@ free_rows(Row **rowsp, int nfiles) } /* Change working directory to the path in CWD. */ -static void -cd(int reset) +void cd(int reset) { int i, j; @@ -703,8 +522,7 @@ cd(int reset) } /* Select a target entry, if it is present. */ -static void -try_to_sel(const char *target) +void try_to_sel(const char *target) { ESEL = 0; if (!ISDIR(target)) @@ -715,8 +533,7 @@ try_to_sel(const char *target) } /* Reload CWD, but try to keep selection. */ -static void -reload() +void reload() { if (rover.nfiles) { strcpy(INPUT, ENAME(ESEL)); @@ -727,8 +544,7 @@ reload() cd(1); } -static off_t -count_dir(const char *path) +off_t count_dir(const char *path) { DIR *dp; struct dirent *ep; @@ -754,8 +570,7 @@ count_dir(const char *path) return total; } -static off_t -count_marked() +off_t count_marked() { int i; char *entry; @@ -788,8 +603,7 @@ count_marked() E.g. to move directory /src/ (and all its contents) inside /dst/: strcpy(CWD, "/dst/"); process_dir(adddir, movfile, deldir, "/src/"); */ -static int -process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) +int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) { int ret; DIR *dp; @@ -826,8 +640,7 @@ process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) /* Process all marked entries using CWD as destination root. All marked entries that are directories will be recursively processed. See process_dir() for details on the parameters. */ -static void -process_marked(PROCESS pre, PROCESS proc, PROCESS pos, +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) { int i, ret; @@ -865,8 +678,7 @@ process_marked(PROCESS pre, PROCESS proc, PROCESS pos, RV_ALERT(); } -static void -update_progress(off_t delta) +void update_progress(off_t delta) { int percent; @@ -879,7 +691,7 @@ update_progress(off_t delta) } /* Wrappers for file operations. */ -static int delfile(const char *path) +int delfile(const char *path) { int ret; struct stat st; @@ -890,8 +702,10 @@ static int delfile(const char *path) update_progress(st.st_size); return unlink(path); } -static PROCESS deldir = rmdir; -static int addfile(const char *path) + +PROCESS deldir = rmdir; + +int addfile(const char *path) { /* Using creat(2) because mknod(2) doesn't seem to be portable. */ int ret; @@ -901,7 +715,8 @@ static int addfile(const char *path) return ret; return close(ret); } -static int cpyfile(const char *srcpath) + +int cpyfile(const char *srcpath) { int src, dst, ret; size_t size; @@ -938,7 +753,8 @@ static int cpyfile(const char *srcpath) } return ret; } -static int adddir(const char *path) + +int adddir(const char *path) { int ret; struct stat st; @@ -948,7 +764,8 @@ static int adddir(const char *path) return ret; return mkdir(path, st.st_mode); } -static int movfile(const char *srcpath) + +int movfile(const char *srcpath) { int ret; struct stat st; @@ -971,8 +788,7 @@ static int movfile(const char *srcpath) return ret; } -static void -start_line_edit(const char *init_input) +void start_line_edit(const char *init_input) { curs_set(TRUE); strncpy(INPUT, init_input, BUFLEN); @@ -983,8 +799,7 @@ start_line_edit(const char *init_input) } /* Read input and change editing state accordingly. */ -static EditStat -get_line_edit() +EditStat get_line_edit() { wchar_t eraser, killer, wch; int ret, length; @@ -1042,8 +857,7 @@ get_line_edit() } /* Update line input on the screen. */ -static void -update_input(const char *prompt, Color color) +void update_input(const char *prompt, Color color) { int plen, ilen, maxlen; From 0e363eb00b548cf64a69abbe06325c703e60a912 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 29 Dec 2022 16:51:36 +0100 Subject: [PATCH 04/18] start to organize different files --- src/config.h | 77 +++--- src/main.c | 510 ++++++++++++++++++++++++++++++++++++ src/rover.c | 681 ++++--------------------------------------------- src/ui_funcs.c | 84 ++++++ src/ui_funcs.h | 24 ++ 5 files changed, 714 insertions(+), 662 deletions(-) create mode 100644 src/main.c create mode 100644 src/ui_funcs.c create mode 100644 src/ui_funcs.h diff --git a/src/config.h b/src/config.h index b9dd6c1..27d73fe 100644 --- a/src/config.h +++ b/src/config.h @@ -123,19 +123,6 @@ #define SIGWINCH 28 #endif -/* String buffers. */ -#define BUFLEN PATH_MAX -static char BUF1[BUFLEN]; -static char BUF2[BUFLEN]; -static char INPUT[BUFLEN]; -static char CLIPBOARD[BUFLEN]; -static wchar_t WBUF[BUFLEN]; - -/* Paths to external programs. */ -static char *user_shell; -static char *user_pager; -static char *user_editor; -static char *user_open; /* Listing view parameters. */ #define HEIGHT (LINES - 4) @@ -169,7 +156,7 @@ typedef struct Marks { /* Line editing state. */ typedef struct Edit { - wchar_t buffer[BUFLEN + 1]; + wchar_t buffer[PATH_MAX +1]; int left, right; } Edit; @@ -221,7 +208,7 @@ static struct Rover { /* Line Editing Macros. */ #define EDIT_FULL(E) ((E).left == (E).right) #define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN - 1) +#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX -1) #define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] #define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] #define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) @@ -230,23 +217,55 @@ static struct Rover { #define EDIT_CLEAR(E) \ do { \ (E).left = 0; \ - (E).right = BUFLEN - 1; \ + (E).right = PATH_MAX -1; \ } while (0) -typedef enum EditStat { CONTINUE, - CONFIRM, - CANCEL } EditStat; -typedef enum Color { DEFAULT, - RED, - GREEN, - YELLOW, - BLUE, - CYAN, - MAGENTA, - WHITE, - BLACK } Color; -typedef int (*PROCESS)(const char *path); +typedef enum EditStat { + CONTINUE, + CONFIRM, + CANCEL +} EditStat; + +typedef int (*PROCESS)(const char *path); + +void init_marks(Marks *marks); +void mark_none(Marks *marks); +void add_mark(Marks *marks, char *dirpath, char *entry); +void del_mark(Marks *marks, char *entry); +void free_marks(Marks *marks); +void handle_usr1(int sig); +void handle_winch(int sig); +void enable_handlers(); +void disable_handlers(); +void reload(); +void update_view(); +void sync_signals(); +int rover_getch(); +int rover_get_wch(wint_t *wch); +void spawn(char **args); +void shell_escaped_cat(char *buf, char *str, size_t n); +int open_with_env(char *program, char *path); +void update_view(); +void clear_message(); +int rowcmp(const void *a, const void *b); +int ls(Row **rowsp, uint8_t flags); +void free_rows(Row **rowsp, int nfiles); +void cd(int reset); +void try_to_sel(const char *target); +void reload(); +off_t count_dir(const char *path); +off_t count_marked(); +int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path); +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done); +void update_progress(off_t delta); +int delfile(const char *path); +int addfile(const char *path); +int cpyfile(const char *srcpath); +int adddir(const char *path); +int movfile(const char *srcpath); +void start_line_edit(const char *init_input); +EditStat get_line_edit(); #endif // _CONFIG_H \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..7ca9513 --- /dev/null +++ b/src/main.c @@ -0,0 +1,510 @@ +#include "config.h" +#include "ui_funcs.h" + +/* Get user programs from the environment vars */ +#define ROVER_ENV(dst, src) \ + { \ + if ((dst = getenv("ROVER_" #src)) == NULL) \ + dst = getenv(#src); \ + } + +static PROCESS deldir = rmdir; + +int main(int argc, char *argv[]) +{ + int i, ch; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; + char *program, *entry, *user_shell, *user_pager, *user_editor, *user_open; + const char *key, *clip_path; + DIR *d; + EditStat edit_stat; + FILE *save_cwd_file = NULL, *save_marks_file = NULL, *clip_file; + + if (argc >= 2) { + if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { + printf("rover %s\n", RV_VERSION); + + return 0; + } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + printf( + "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" + " Browse current directory or the ones specified.\n\n" + " or: rover -h|--help\n" + " Print this help message and exit.\n\n" + " or: rover -v|--version\n" + " Print program version and exit.\n\n" + "See rover(1) for more information.\n" + "Rover homepage: .\n"); + + return 0; + } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { + if (argc > 2) { + save_cwd_file = fopen(argv[2], "w"); + argc -= 2; + argv += 2; + } else { + fprintf(stderr, "error: missing argument to %s\n", argv[1]); + + return 1; + } + } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { + if (argc > 2) { + save_marks_file = fopen(argv[2], "a"); + argc -= 2; + argv += 2; + } else { + fprintf(stderr, "error: missing argument to %s\n", argv[1]); + + return 1; + } + } + } + + ROVER_ENV(user_shell, SHELL); + ROVER_ENV(user_pager, PAGER); + ROVER_ENV(user_editor, VISUAL); + if (!user_editor) + ROVER_ENV(user_editor, EDITOR); + ROVER_ENV(user_open, OPEN); + + init_term(); + rover.nfiles = 0; + for (i = 0; i < 10; i++) { + rover.tabs[i].esel = rover.tabs[i].scroll = 0; + rover.tabs[i].flags = RV_FLAGS; + } + strcpy(rover.tabs[0].cwd, getenv("HOME")); + for (i = 1; i < argc && i < 10; i++) { + if ((d = opendir(argv[i]))) { + realpath(argv[i], rover.tabs[i].cwd); + closedir(d); + } else + strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); + } + getcwd(rover.tabs[i].cwd, PATH_MAX); + for (i++; i < 10; i++) + strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); + for (i = 0; i < 10; i++) + if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') + strcat(rover.tabs[i].cwd, "/"); + rover.tab = 1; + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + init_marks(&rover.marks); + cd(1); + strcpy(clipboard, CWD); + if (rover.nfiles > 0) + strcat(clipboard, ENAME(ESEL)); + while (1) { + ch = rover_getch(); + key = keyname(ch); + clear_message(); + if (!strcmp(key, RVK_QUIT)) + break; + else if (ch >= '0' && ch <= '9') { + rover.tab = ch - '0'; + cd(0); + } else if (!strcmp(key, RVK_HELP)) { + spawn((char *[]){ "man", "rover", NULL }); + } else if (!strcmp(key, RVK_DOWN)) { + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + 1, rover.nfiles - 1); + update_view(); + } else if (!strcmp(key, RVK_UP)) { + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - 1, 0); + update_view(); + } else if (!strcmp(key, RVK_JUMP_DOWN)) { + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); + if (rover.nfiles > HEIGHT) + SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); + update_view(); + } else if (!strcmp(key, RVK_JUMP_UP)) { + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - RV_JUMP, 0); + SCROLL = MAX(SCROLL - RV_JUMP, 0); + update_view(); + } else if (!strcmp(key, RVK_JUMP_TOP)) { + if (!rover.nfiles) + continue; + ESEL = 0; + update_view(); + } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { + if (!rover.nfiles) + continue; + ESEL = rover.nfiles - 1; + update_view(); + } else if (!strcmp(key, RVK_CD_DOWN)) { + if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) + continue; + if (chdir(ENAME(ESEL)) == -1) { + message(RED, "Cannot access \"%s\".", ENAME(ESEL)); + continue; + } + strcat(CWD, ENAME(ESEL)); + cd(1); + } else if (!strcmp(key, RVK_CD_UP)) { + char *dirname, first; + if (!strcmp(CWD, "/")) + continue; + CWD[strlen(CWD) - 1] = '\0'; + dirname = strrchr(CWD, '/') + 1; + first = dirname[0]; + dirname[0] = '\0'; + cd(1); + dirname[0] = first; + dirname[strlen(dirname)] = '/'; + try_to_sel(dirname); + dirname[0] = '\0'; + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; + update_view(); + } else if (!strcmp(key, RVK_HOME)) { + strcpy(CWD, getenv("HOME")); + if (CWD[strlen(CWD) - 1] != '/') + strcat(CWD, "/"); + cd(1); + } else if (!strcmp(key, RVK_TARGET)) { + char *bname, first; + int is_dir = S_ISDIR(EMODE(ESEL)); + ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX -1); + if (len == -1) + continue; + buffer_one[len] = '\0'; + if (access(buffer_one, F_OK) == -1) { + char *msg; + switch (errno) { + case EACCES: + msg = "Cannot access \"%s\"."; + break; + case ENOENT: + msg = "\"%s\" does not exist."; + break; + default: + msg = "Cannot navigate to \"%s\"."; + } + strcpy(buffer_two, buffer_one); /* message() uses buffer_one. */ + message(RED, msg, buffer_two); + continue; + } + realpath(buffer_one, CWD); + len = strlen(CWD); + if (CWD[len - 1] == '/') + CWD[len - 1] = '\0'; + bname = strrchr(CWD, '/') + 1; + first = *bname; + *bname = '\0'; + cd(1); + *bname = first; + if (is_dir) + strcat(CWD, "/"); + try_to_sel(bname); + *bname = '\0'; + update_view(); + } else if (!strcmp(key, RVK_COPY_PATH)) { + clip_path = getenv("CLIP"); + if (!clip_path) + goto copy_path_fail; + clip_file = fopen(clip_path, "w"); + if (!clip_file) + goto copy_path_fail; + fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); + fclose(clip_file); + goto copy_path_done; +copy_path_fail: + strcpy(clipboard, CWD); + strcat(clipboard, ENAME(ESEL)); +copy_path_done:; + } else if (!strcmp(key, RVK_PASTE_PATH)) { + clip_path = getenv("CLIP"); + if (!clip_path) + goto paste_path_fail; + clip_file = fopen(clip_path, "r"); + if (!clip_file) + goto paste_path_fail; + fscanf(clip_file, "%s\n", clipboard); + fclose(clip_file); +paste_path_fail: + strcpy(buffer_one, clipboard); + strcpy(CWD, dirname(buffer_one)); + if (strcmp(CWD, "/")) + strcat(CWD, "/"); + cd(1); + strcpy(buffer_one, clipboard); + try_to_sel(strstr(clipboard, basename(buffer_one))); + update_view(); + } else if (!strcmp(key, RVK_REFRESH)) { + reload(); + } else if (!strcmp(key, RVK_SHELL)) { + program = user_shell; + if (program) { +#ifdef RV_SHELL + spawn((char *[]){ RV_SHELL, "-c", program, NULL }); +#else + spawn((char *[]){ program, NULL }); +#endif + reload(); + } + } else if (!strcmp(key, RVK_VIEW)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_pager, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_EDIT)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_editor, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_OPEN)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(user_open, ENAME(ESEL))) + cd(0); + } else if (!strcmp(key, RVK_SEARCH)) { + int oldsel, oldscroll, length; + if (!rover.nfiles) + continue; + oldsel = ESEL; + oldscroll = SCROLL; + start_line_edit(""); + update_input(RVP_SEARCH, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int sel; + Color color = RED; + length = strlen(input); + if (length) { + for (sel = 0; sel < rover.nfiles; sel++) + if (!strncmp(ENAME(sel), input, length)) + break; + if (sel < rover.nfiles) { + color = GREEN; + ESEL = sel; + if (rover.nfiles > HEIGHT) { + if (sel < 3) + SCROLL = 0; + else if (sel - 3 > rover.nfiles - HEIGHT) + SCROLL = rover.nfiles - HEIGHT; + else + SCROLL = sel - 3; + } + } + } else { + ESEL = oldsel; + SCROLL = oldscroll; + } + update_view(); + update_input(RVP_SEARCH, color, input); + } + if (edit_stat == CANCEL) { + ESEL = oldsel; + SCROLL = oldscroll; + } + clear_message(); + update_view(); + } else if (!strcmp(key, RVK_TG_FILES)) { + FLAGS ^= SHOW_FILES; + reload(); + } else if (!strcmp(key, RVK_TG_DIRS)) { + FLAGS ^= SHOW_DIRS; + reload(); + } else if (!strcmp(key, RVK_TG_HIDDEN)) { + FLAGS ^= SHOW_HIDDEN; + reload(); + } else if (!strcmp(key, RVK_NEW_FILE)) { + int ok = 0; + start_line_edit(""); + update_input(RVP_NEW_FILE, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (ok) { + if (addfile(input) == 0) { + cd(1); + try_to_sel(input); + update_view(); + } else + message(RED, "Could not create \"%s\".", input); + } else + message(RED, "\"%s\" already exists.", input); + } + } else if (!strcmp(key, RVK_NEW_DIR)) { + int ok = 0; + start_line_edit(""); + update_input(RVP_NEW_DIR, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (ok) { + if (adddir(input) == 0) { + cd(1); + strcat(input, "/"); + try_to_sel(input); + update_view(); + } else + message(RED, "Could not create \"%s/\".", input); + } else + message(RED, "\"%s\" already exists.", input); + } + } else if (!strcmp(key, RVK_RENAME)) { + int ok = 0; + char *last; + int isdir; + strcpy(input, ENAME(ESEL)); + last = input + strlen(input) - 1; + if ((isdir = *last == '/')) + *last = '\0'; + start_line_edit(input); + update_input(RVP_RENAME, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + update_input(RVP_RENAME, (ok ? GREEN : RED), input); + } + clear_message(); + if (edit_stat == CONFIRM) { + if (isdir) + strcat(input, "/"); + if (ok) { + if (!rename(ENAME(ESEL), input) && MARKED(ESEL)) { + del_mark(&rover.marks, ENAME(ESEL)); + add_mark(&rover.marks, CWD, input); + } + cd(1); + try_to_sel(input); + update_view(); + } else + message(RED, "\"%s\" already exists.", input); + } + } else if (!strcmp(key, RVK_TG_EXEC)) { + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (S_IXUSR & EMODE(ESEL)) + EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); + else + EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; + if (chmod(ENAME(ESEL), EMODE(ESEL))) { + message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); + } else { + message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); + update_view(); + } + } else if (!strcmp(key, RVK_DELETE)) { + if (rover.nfiles) { + message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); + if (rover_getch() == 'Y') { + const char *name = ENAME(ESEL); + int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); + reload(); + if (ret) + message(RED, "Could not delete \"%s\".", ENAME(ESEL)); + } else + clear_message(); + } else + message(RED, "No entry selected for deletion."); + } else if (!strcmp(key, RVK_TG_MARK)) { + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + else + add_mark(&rover.marks, CWD, ENAME(ESEL)); + MARKED(ESEL) = !MARKED(ESEL); + ESEL = (ESEL + 1) % rover.nfiles; + update_view(); + } else if (!strcmp(key, RVK_INVMARK)) { + for (i = 0; i < rover.nfiles; i++) { + if (MARKED(i)) + del_mark(&rover.marks, ENAME(i)); + else + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = !MARKED(i); + } + update_view(); + } else if (!strcmp(key, RVK_MARKALL)) { + for (i = 0; i < rover.nfiles; i++) + if (!MARKED(i)) { + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = 1; + } + update_view(); + } else if (!strcmp(key, RVK_MARK_DELETE)) { + if (rover.marks.nentries) { + message(YELLOW, "Delete all marked entries? (Y/n)"); + if (rover_getch() == 'Y') + process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); + else + clear_message(); + } else + message(RED, "No entries marked for deletion."); + } else if (!strcmp(key, RVK_MARK_COPY)) { + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); + else + message(RED, "Cannot copy to the same path."); + } else + message(RED, "No entries marked for copying."); + } else if (!strcmp(key, RVK_MARK_MOVE)) { + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, movfile, deldir, "Moving", "Moved"); + else + message(RED, "Cannot move to the same path."); + } else + message(RED, "No entries marked for moving."); + } + } + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + delwin(rover.window); + if (save_cwd_file != NULL) { + fputs(CWD, save_cwd_file); + fclose(save_cwd_file); + } + if (save_marks_file != NULL) { + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) + fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); + } + fclose(save_marks_file); + } + free_marks(&rover.marks); + return 0; +} diff --git a/src/rover.c b/src/rover.c index cbeb37b..7cce955 100644 --- a/src/rover.c +++ b/src/rover.c @@ -1,5 +1,5 @@ - #include "config.h" +#include "ui_funcs.h" void init_marks(Marks *marks) { @@ -119,9 +119,6 @@ void disable_handlers() sigaction(SIGWINCH, &sa, NULL); } -void reload(); -void update_view(); - /* Handle any signals received since last call. */ void sync_signals() { @@ -166,22 +163,6 @@ int rover_get_wch(wint_t *wch) return ret; } -/* Get user programs from the environment. */ - -#define ROVER_ENV(dst, src) \ - if ((dst = getenv("ROVER_" #src)) == NULL) \ - dst = getenv(#src); - -void get_user_programs() -{ - ROVER_ENV(user_shell, SHELL) - ROVER_ENV(user_pager, PAGER) - ROVER_ENV(user_editor, VISUAL) - if (!user_editor) - ROVER_ENV(user_editor, EDITOR) - ROVER_ENV(user_open, OPEN) -} - /* Do a fork-exec to external program (e.g. $EDITOR). */ void spawn(char **args) { @@ -229,12 +210,14 @@ void shell_escaped_cat(char *buf, char *str, size_t n) int open_with_env(char *program, char *path) { + char buffer[PATH_MAX]; + if (program) { #ifdef RV_SHELL - strncpy(BUF1, program, BUFLEN - 1); - strncat(BUF1, " ", BUFLEN - strlen(program) - 1); - shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2); - spawn((char *[]){ RV_SHELL, "-c", BUF1, NULL }); + strncpy(buffer, program, PATH_MAX -1); + strncat(buffer, " ", PATH_MAX - strlen(program) - 1); + shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) -2); + spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); #else spawn((char *[]){ program, path, NULL }); #endif @@ -243,47 +226,13 @@ int open_with_env(char *program, char *path) return 0; } -/* Curses setup. */ -void init_term() -{ - setlocale(LC_ALL, ""); - initscr(); - cbreak(); /* Get one character at a time. */ - timeout(100); /* For getch(). */ - noecho(); - nonl(); /* No NL->CR/NL on output. */ - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - curs_set(FALSE); /* Hide blinking cursor. */ - if (has_colors()) { - short bg; - start_color(); -#ifdef NCURSES_EXT_FUNCS - use_default_colors(); - bg = -1; -#else - bg = COLOR_BLACK; -#endif - init_pair(RED, COLOR_RED, bg); - init_pair(GREEN, COLOR_GREEN, bg); - init_pair(YELLOW, COLOR_YELLOW, bg); - init_pair(BLUE, COLOR_BLUE, bg); - init_pair(CYAN, COLOR_CYAN, bg); - init_pair(MAGENTA, COLOR_MAGENTA, bg); - init_pair(WHITE, COLOR_WHITE, bg); - init_pair(BLACK, COLOR_BLACK, bg); - } - atexit((void (*)(void))endwin); - enable_handlers(); -} /* Update the listing view. */ void update_view() { - int i, j; - int numsize; - int ishidden; - int marking; + int i, j, numsize, ishidden, marking; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX]; + wchar_t wbuffer[PATH_MAX]; mvhline(0, 0, ' ', COLS); attr_on(A_BOLD, NULL); @@ -291,14 +240,14 @@ void update_view() mvaddch(0, COLS - 2, rover.tab + '0'); attr_off(A_BOLD, NULL); if (rover.marks.nentries) { - numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries); + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, BUF1); + mvaddstr(0, COLS - 3 - numsize, buffer_one); } else numsize = -1; color_set(RVC_CWD, NULL); - mbstowcs(WBUF, CWD, PATH_MAX); - mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); wcolor_set(rover.window, RVC_BORDER, NULL); wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); @@ -334,27 +283,27 @@ void update_view() else if (S_ISSOCK(EMODE(j))) wcolor_set(rover.window, RVC_SOCK, NULL); if (S_ISDIR(EMODE(j))) { - mbstowcs(WBUF, ENAME(j), PATH_MAX); + mbstowcs(wbuffer, ENAME(j), PATH_MAX); if (ISLINK(j)) - wcscat(WBUF, L"/"); + wcscat(wbuffer, L"/"); } else { char *suffix, *suffixes = "BKMGTPEZY"; off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); - int namecols = wcswidth(WBUF, length); + int length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + int namecols = wcswidth(wbuffer, length); for (suffix = suffixes; human_size >= 10240; suffix++) human_size = (human_size + 512) / 1024; if (*suffix == 'B') - swprintf(WBUF + length, PATH_MAX - length, L"%*d %c", + swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", (int)(COLS - namecols - 6), (int)human_size / 10, *suffix); else - swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c", + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", (int)(COLS - namecols - 8), (int)human_size / 10, (int)human_size % 10, *suffix); } mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); if (marking && MARKED(j)) { wcolor_set(rover.window, RVC_MARKS, NULL); mvwaddch(rover.window, i + 1, 1, RVS_MARK); @@ -374,36 +323,19 @@ void update_view() wcolor_set(rover.window, RVC_SCROLLBAR, NULL); mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); } - BUF1[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - BUF1[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; if (!rover.nfiles) - strcpy(BUF2, "0/0"); + strcpy(buffer_two, "0/0"); else - snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(BUF1 + 3, BUFLEN, "%12s", BUF2); + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, BUF1); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); wrefresh(rover.window); } -/* Show a message on the status bar. */ -void message(Color color, char *fmt, ...) -{ - int len, pos; - va_list args; - - va_start(args, fmt); - vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args); - va_end(args); - len = strlen(BUF1); - pos = (STATUSPOS - len) / 2; - attr_on(A_BOLD, NULL); - color_set(color, NULL); - mvaddstr(LINES - 1, pos, BUF1); - color_set(DEFAULT, NULL); - attr_off(A_BOLD, NULL); -} /* Clear message area, leaving only status info. */ void clear_message() @@ -535,10 +467,12 @@ void try_to_sel(const char *target) /* Reload CWD, but try to keep selection. */ void reload() { + char input[PATH_MAX]; + if (rover.nfiles) { - strcpy(INPUT, ENAME(ESEL)); + strcpy(input, ENAME(ESEL)); cd(0); - try_to_sel(INPUT); + try_to_sel(input); update_view(); } else cd(1); @@ -640,8 +574,7 @@ int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) /* Process all marked entries using CWD as destination root. All marked entries that are directories will be recursively processed. See process_dir() for details on the parameters. */ -void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, - const char *msg_doing, const char *msg_done) +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) { int i, ret; char *entry; @@ -703,8 +636,6 @@ int delfile(const char *path) return unlink(path); } -PROCESS deldir = rmdir; - int addfile(const char *path) { /* Using creat(2) because mknod(2) doesn't seem to be portable. */ @@ -721,8 +652,7 @@ int cpyfile(const char *srcpath) int src, dst, ret; size_t size; struct stat st; - char buf[BUFSIZ]; - char dstpath[PATH_MAX]; + char buf[BUFSIZ], dstpath[PATH_MAX], buffer[PATH_MAX]; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); @@ -730,11 +660,11 @@ int cpyfile(const char *srcpath) if (ret < 0) return ret; if (S_ISLNK(st.st_mode)) { - ret = readlink(srcpath, BUF1, BUFLEN - 1); + ret = readlink(srcpath, buffer, PATH_MAX - 1); if (ret < 0) return ret; - BUF1[ret] = '\0'; - ret = symlink(BUF1, dstpath); + buffer[ret] = '\0'; + ret = symlink(buffer, dstpath); } else { ret = src = open(srcpath, O_RDONLY); if (ret < 0) @@ -790,11 +720,13 @@ int movfile(const char *srcpath) void start_line_edit(const char *init_input) { + char input[PATH_MAX]; + curs_set(TRUE); - strncpy(INPUT, init_input, BUFLEN); - rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN); - rover.edit.right = BUFLEN - 1; - rover.edit.buffer[BUFLEN] = L'\0'; + strncpy(input, init_input, PATH_MAX); + rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); + rover.edit.right = PATH_MAX - 1; + rover.edit.buffer[PATH_MAX] = L'\0'; rover.edit_scroll = 0; } @@ -803,6 +735,7 @@ EditStat get_line_edit() { wchar_t eraser, killer, wch; int ret, length; + char input[PATH_MAX]; ret = rover_get_wch((wint_t *)&wch); erasewchar(&eraser); @@ -848,529 +781,11 @@ EditStat get_line_edit() EDIT_INSERT(rover.edit, wch); } } - /* Encode edit contents in INPUT. */ + /* Encode edit contents in input. */ rover.edit.buffer[rover.edit.left] = L'\0'; - length = wcstombs(INPUT, rover.edit.buffer, BUFLEN); - wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right + 1], - BUFLEN - length); + length = wcstombs(input, rover.edit.buffer, PATH_MAX); + wcstombs(&input[length], &rover.edit.buffer[rover.edit.right + 1], + PATH_MAX - length); return CONTINUE; } -/* Update line input on the screen. */ -void update_input(const char *prompt, Color color) -{ - int plen, ilen, maxlen; - - plen = strlen(prompt); - ilen = mbstowcs(NULL, INPUT, 0); - maxlen = STATUSPOS - plen - 2; - if (ilen - rover.edit_scroll < maxlen) - rover.edit_scroll = MAX(ilen - maxlen, 0); - else if (rover.edit.left > rover.edit_scroll + maxlen - 1) - rover.edit_scroll = rover.edit.left - maxlen; - else if (rover.edit.left < rover.edit_scroll) - rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); - color_set(RVC_PROMPT, NULL); - mvaddstr(LINES - 1, 0, prompt); - color_set(color, NULL); - mbstowcs(WBUF, INPUT, COLS); - mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen); - mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); - color_set(DEFAULT, NULL); - if (rover.edit_scroll) - mvaddch(LINES - 1, plen - 1, '<'); - if (ilen > rover.edit_scroll + maxlen) - mvaddch(LINES - 1, plen + maxlen, '>'); - move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); -} - -int main(int argc, char *argv[]) -{ - int i, ch; - char *program; - char *entry; - const char *key; - const char *clip_path; - DIR *d; - EditStat edit_stat; - FILE *save_cwd_file = NULL; - FILE *save_marks_file = NULL; - FILE *clip_file; - - if (argc >= 2) { - if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { - printf("rover %s\n", RV_VERSION); - return 0; - } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { - printf( - "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" - " Browse current directory or the ones specified.\n\n" - " or: rover -h|--help\n" - " Print this help message and exit.\n\n" - " or: rover -v|--version\n" - " Print program version and exit.\n\n" - "See rover(1) for more information.\n" - "Rover homepage: .\n"); - return 0; - } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { - if (argc > 2) { - save_cwd_file = fopen(argv[2], "w"); - argc -= 2; - argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { - if (argc > 2) { - save_marks_file = fopen(argv[2], "a"); - argc -= 2; - argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } - } - get_user_programs(); - init_term(); - rover.nfiles = 0; - for (i = 0; i < 10; i++) { - rover.tabs[i].esel = rover.tabs[i].scroll = 0; - rover.tabs[i].flags = RV_FLAGS; - } - strcpy(rover.tabs[0].cwd, getenv("HOME")); - for (i = 1; i < argc && i < 10; i++) { - if ((d = opendir(argv[i]))) { - realpath(argv[i], rover.tabs[i].cwd); - closedir(d); - } else - strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); - } - getcwd(rover.tabs[i].cwd, PATH_MAX); - for (i++; i < 10; i++) - strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); - for (i = 0; i < 10; i++) - if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') - strcat(rover.tabs[i].cwd, "/"); - rover.tab = 1; - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - init_marks(&rover.marks); - cd(1); - strcpy(CLIPBOARD, CWD); - if (rover.nfiles > 0) - strcat(CLIPBOARD, ENAME(ESEL)); - while (1) { - ch = rover_getch(); - key = keyname(ch); - clear_message(); - if (!strcmp(key, RVK_QUIT)) - break; - else if (ch >= '0' && ch <= '9') { - rover.tab = ch - '0'; - cd(0); - } else if (!strcmp(key, RVK_HELP)) { - spawn((char *[]){ "man", "rover", NULL }); - } else if (!strcmp(key, RVK_DOWN)) { - if (!rover.nfiles) - continue; - ESEL = MIN(ESEL + 1, rover.nfiles - 1); - update_view(); - } else if (!strcmp(key, RVK_UP)) { - if (!rover.nfiles) - continue; - ESEL = MAX(ESEL - 1, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_DOWN)) { - if (!rover.nfiles) - continue; - ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); - if (rover.nfiles > HEIGHT) - SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); - update_view(); - } else if (!strcmp(key, RVK_JUMP_UP)) { - if (!rover.nfiles) - continue; - ESEL = MAX(ESEL - RV_JUMP, 0); - SCROLL = MAX(SCROLL - RV_JUMP, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_TOP)) { - if (!rover.nfiles) - continue; - ESEL = 0; - update_view(); - } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { - if (!rover.nfiles) - continue; - ESEL = rover.nfiles - 1; - update_view(); - } else if (!strcmp(key, RVK_CD_DOWN)) { - if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) - continue; - if (chdir(ENAME(ESEL)) == -1) { - message(RED, "Cannot access \"%s\".", ENAME(ESEL)); - continue; - } - strcat(CWD, ENAME(ESEL)); - cd(1); - } else if (!strcmp(key, RVK_CD_UP)) { - char *dirname, first; - if (!strcmp(CWD, "/")) - continue; - CWD[strlen(CWD) - 1] = '\0'; - dirname = strrchr(CWD, '/') + 1; - first = dirname[0]; - dirname[0] = '\0'; - cd(1); - dirname[0] = first; - dirname[strlen(dirname)] = '/'; - try_to_sel(dirname); - dirname[0] = '\0'; - if (rover.nfiles > HEIGHT) - SCROLL = ESEL - HEIGHT / 2; - update_view(); - } else if (!strcmp(key, RVK_HOME)) { - strcpy(CWD, getenv("HOME")); - if (CWD[strlen(CWD) - 1] != '/') - strcat(CWD, "/"); - cd(1); - } else if (!strcmp(key, RVK_TARGET)) { - char *bname, first; - int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN - 1); - if (len == -1) - continue; - BUF1[len] = '\0'; - if (access(BUF1, F_OK) == -1) { - char *msg; - switch (errno) { - case EACCES: - msg = "Cannot access \"%s\"."; - break; - case ENOENT: - msg = "\"%s\" does not exist."; - break; - default: - msg = "Cannot navigate to \"%s\"."; - } - strcpy(BUF2, BUF1); /* message() uses BUF1. */ - message(RED, msg, BUF2); - continue; - } - realpath(BUF1, CWD); - len = strlen(CWD); - if (CWD[len - 1] == '/') - CWD[len - 1] = '\0'; - bname = strrchr(CWD, '/') + 1; - first = *bname; - *bname = '\0'; - cd(1); - *bname = first; - if (is_dir) - strcat(CWD, "/"); - try_to_sel(bname); - *bname = '\0'; - update_view(); - } else if (!strcmp(key, RVK_COPY_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) - goto copy_path_fail; - clip_file = fopen(clip_path, "w"); - if (!clip_file) - goto copy_path_fail; - fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); - fclose(clip_file); - goto copy_path_done; -copy_path_fail: - strcpy(CLIPBOARD, CWD); - strcat(CLIPBOARD, ENAME(ESEL)); -copy_path_done:; - } else if (!strcmp(key, RVK_PASTE_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) - goto paste_path_fail; - clip_file = fopen(clip_path, "r"); - if (!clip_file) - goto paste_path_fail; - fscanf(clip_file, "%s\n", CLIPBOARD); - fclose(clip_file); -paste_path_fail: - strcpy(BUF1, CLIPBOARD); - strcpy(CWD, dirname(BUF1)); - if (strcmp(CWD, "/")) - strcat(CWD, "/"); - cd(1); - strcpy(BUF1, CLIPBOARD); - try_to_sel(strstr(CLIPBOARD, basename(BUF1))); - update_view(); - } else if (!strcmp(key, RVK_REFRESH)) { - reload(); - } else if (!strcmp(key, RVK_SHELL)) { - program = user_shell; - if (program) { -#ifdef RV_SHELL - spawn((char *[]){ RV_SHELL, "-c", program, NULL }); -#else - spawn((char *[]){ program, NULL }); -#endif - reload(); - } - } else if (!strcmp(key, RVK_VIEW)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_pager, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_EDIT)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_editor, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_OPEN)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_open, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_SEARCH)) { - int oldsel, oldscroll, length; - if (!rover.nfiles) - continue; - oldsel = ESEL; - oldscroll = SCROLL; - start_line_edit(""); - update_input(RVP_SEARCH, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int sel; - Color color = RED; - length = strlen(INPUT); - if (length) { - for (sel = 0; sel < rover.nfiles; sel++) - if (!strncmp(ENAME(sel), INPUT, length)) - break; - if (sel < rover.nfiles) { - color = GREEN; - ESEL = sel; - if (rover.nfiles > HEIGHT) { - if (sel < 3) - SCROLL = 0; - else if (sel - 3 > rover.nfiles - HEIGHT) - SCROLL = rover.nfiles - HEIGHT; - else - SCROLL = sel - 3; - } - } - } else { - ESEL = oldsel; - SCROLL = oldscroll; - } - update_view(); - update_input(RVP_SEARCH, color); - } - if (edit_stat == CANCEL) { - ESEL = oldsel; - SCROLL = oldscroll; - } - clear_message(); - update_view(); - } else if (!strcmp(key, RVK_TG_FILES)) { - FLAGS ^= SHOW_FILES; - reload(); - } else if (!strcmp(key, RVK_TG_DIRS)) { - FLAGS ^= SHOW_DIRS; - reload(); - } else if (!strcmp(key, RVK_TG_HIDDEN)) { - FLAGS ^= SHOW_HIDDEN; - reload(); - } else if (!strcmp(key, RVK_NEW_FILE)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_FILE, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - } - update_input(RVP_NEW_FILE, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (addfile(INPUT) == 0) { - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_NEW_DIR)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_DIR, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - } - update_input(RVP_NEW_DIR, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (adddir(INPUT) == 0) { - cd(1); - strcat(INPUT, "/"); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s/\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_RENAME)) { - int ok = 0; - char *last; - int isdir; - strcpy(INPUT, ENAME(ESEL)); - last = INPUT + strlen(INPUT) - 1; - if ((isdir = *last == '/')) - *last = '\0'; - start_line_edit(INPUT); - update_input(RVP_RENAME, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - update_input(RVP_RENAME, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (isdir) - strcat(INPUT, "/"); - if (ok) { - if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) { - del_mark(&rover.marks, ENAME(ESEL)); - add_mark(&rover.marks, CWD, INPUT); - } - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_TG_EXEC)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (S_IXUSR & EMODE(ESEL)) - EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); - else - EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; - if (chmod(ENAME(ESEL), EMODE(ESEL))) { - message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); - } else { - message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); - update_view(); - } - } else if (!strcmp(key, RVK_DELETE)) { - if (rover.nfiles) { - message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); - if (rover_getch() == 'Y') { - const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); - reload(); - if (ret) - message(RED, "Could not delete \"%s\".", ENAME(ESEL)); - } else - clear_message(); - } else - message(RED, "No entry selected for deletion."); - } else if (!strcmp(key, RVK_TG_MARK)) { - if (MARKED(ESEL)) - del_mark(&rover.marks, ENAME(ESEL)); - else - add_mark(&rover.marks, CWD, ENAME(ESEL)); - MARKED(ESEL) = !MARKED(ESEL); - ESEL = (ESEL + 1) % rover.nfiles; - update_view(); - } else if (!strcmp(key, RVK_INVMARK)) { - for (i = 0; i < rover.nfiles; i++) { - if (MARKED(i)) - del_mark(&rover.marks, ENAME(i)); - else - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = !MARKED(i); - } - update_view(); - } else if (!strcmp(key, RVK_MARKALL)) { - for (i = 0; i < rover.nfiles; i++) - if (!MARKED(i)) { - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = 1; - } - update_view(); - } else if (!strcmp(key, RVK_MARK_DELETE)) { - if (rover.marks.nentries) { - message(YELLOW, "Delete all marked entries? (Y/n)"); - if (rover_getch() == 'Y') - process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); - else - clear_message(); - } else - message(RED, "No entries marked for deletion."); - } else if (!strcmp(key, RVK_MARK_COPY)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); - else - message(RED, "Cannot copy to the same path."); - } else - message(RED, "No entries marked for copying."); - } else if (!strcmp(key, RVK_MARK_MOVE)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); - else - message(RED, "Cannot move to the same path."); - } else - message(RED, "No entries marked for moving."); - } - } - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - delwin(rover.window); - if (save_cwd_file != NULL) { - fputs(CWD, save_cwd_file); - fclose(save_cwd_file); - } - if (save_marks_file != NULL) { - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) - fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); - } - fclose(save_marks_file); - } - free_marks(&rover.marks); - return 0; -} diff --git a/src/ui_funcs.c b/src/ui_funcs.c new file mode 100644 index 0000000..0a8c2c2 --- /dev/null +++ b/src/ui_funcs.c @@ -0,0 +1,84 @@ +#include "config.h" +#include "ui_funcs.h" + +/* Curses setup. */ +void init_term() +{ + setlocale(LC_ALL, ""); + initscr(); + cbreak(); /* Get one character at a time. */ + timeout(100); /* For getch(). */ + noecho(); + nonl(); /* No NL->CR/NL on output. */ + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); /* Hide blinking cursor. */ + if (has_colors()) { + short bg; + start_color(); +#ifdef NCURSES_EXT_FUNCS + use_default_colors(); + bg = -1; +#else + bg = COLOR_BLACK; +#endif + init_pair(RED, COLOR_RED, bg); + init_pair(GREEN, COLOR_GREEN, bg); + init_pair(YELLOW, COLOR_YELLOW, bg); + init_pair(BLUE, COLOR_BLUE, bg); + init_pair(CYAN, COLOR_CYAN, bg); + init_pair(MAGENTA, COLOR_MAGENTA, bg); + init_pair(WHITE, COLOR_WHITE, bg); + init_pair(BLACK, COLOR_BLACK, bg); + } + atexit((void (*)(void))endwin); + enable_handlers(); +} + +/* Update line input on the screen. */ +void update_input(const char *prompt, Color color, const char *input) +{ + int plen, ilen, maxlen; + wchar_t wbuffer[PATH_MAX]; + + plen = strlen(prompt); + ilen = mbstowcs(NULL, input, 0); + maxlen = STATUSPOS - plen -2; + if (ilen - rover.edit_scroll < maxlen) + rover.edit_scroll = MAX(ilen - maxlen, 0); + else if (rover.edit.left > rover.edit_scroll + maxlen -1) + rover.edit_scroll = rover.edit.left - maxlen; + else if (rover.edit.left < rover.edit_scroll) + rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); + color_set(RVC_PROMPT, NULL); + mvaddstr(LINES -1, 0, prompt); + color_set(color, NULL); + mbstowcs(wbuffer, input, COLS); + mvaddnwstr(LINES -1, plen, &wbuffer[rover.edit_scroll], maxlen); + mvaddch(LINES -1, plen + MIN(ilen - rover.edit_scroll, maxlen +1), ' '); + color_set(DEFAULT, NULL); + if (rover.edit_scroll) + mvaddch(LINES -1, plen -1, '<'); + if (ilen > rover.edit_scroll + maxlen) + mvaddch(LINES -1, plen + maxlen, '>'); + move(LINES -1, plen + rover.edit.left - rover.edit_scroll); +} + +/* Show a message on the status bar. */ +void message(Color color, char *fmt, ...) +{ + int len, pos; + va_list args; + char buffer[PATH_MAX]; + + va_start(args, fmt); + vsnprintf(buffer, MIN(PATH_MAX, STATUSPOS), fmt, args); + va_end(args); + len = strlen(buffer); + pos = (STATUSPOS - len) / 2; + attr_on(A_BOLD, NULL); + color_set(color, NULL); + mvaddstr(LINES - 1, pos, buffer); + color_set(DEFAULT, NULL); + attr_off(A_BOLD, NULL); +} diff --git a/src/ui_funcs.h b/src/ui_funcs.h new file mode 100644 index 0000000..c31eef2 --- /dev/null +++ b/src/ui_funcs.h @@ -0,0 +1,24 @@ +#ifndef _UI_FUNCS_H +#define _UI_FUNCS_H + +#include +#include /* setlocale(), LC_ALL */ +#include /* PATH_MAX */ + +typedef enum Color { + DEFAULT, + RED, + GREEN, + YELLOW, + BLUE, + CYAN, + MAGENTA, + WHITE, + BLACK +} Color; + +void init_term(); +void update_input(const char *prompt, Color color, const char *input); +void message(Color color, char *fmt, ...); + +#endif // _UI_FUNCS_H \ No newline at end of file From 6133ce6c532f3f424bca7d391590786764e84af4 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 29 Dec 2022 20:10:44 +0100 Subject: [PATCH 05/18] continuing improve fixed some bugs continue to verify starting from first call of (cd) in function main() --- src/config.h | 64 ++++++++--------- src/main.c | 53 ++++++++------ src/rover.c | 191 +++++++++++-------------------------------------- src/ui_funcs.c | 126 ++++++++++++++++++++++++++++++-- src/ui_funcs.h | 23 ++++++ 5 files changed, 243 insertions(+), 214 deletions(-) diff --git a/src/config.h b/src/config.h index 27d73fe..6a2b6f7 100644 --- a/src/config.h +++ b/src/config.h @@ -27,6 +27,7 @@ #include #include #include +#include #define RV_VERSION "1.0.1" @@ -67,24 +68,6 @@ #define RVK_MARK_COPY "C" #define RVK_MARK_MOVE "V" -/* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ -#define RVC_CWD GREEN -#define RVC_STATUS CYAN -#define RVC_BORDER BLUE -#define RVC_SCROLLBAR CYAN -#define RVC_LINK CYAN -#define RVC_HIDDEN YELLOW -#define RVC_EXEC GREEN -#define RVC_REG DEFAULT -#define RVC_DIR DEFAULT -#define RVC_CHR MAGENTA -#define RVC_BLK MAGENTA -#define RVC_FIFO BLUE -#define RVC_SOCK MAGENTA -#define RVC_PROMPT DEFAULT -#define RVC_TABNUM DEFAULT -#define RVC_MARKS YELLOW - /* Special symbols used by the TUI. See for available constants. */ #define RVS_SCROLLBAR ACS_CKBOARD #define RVS_MARK ACS_DIAMOND @@ -123,10 +106,8 @@ #define SIGWINCH 28 #endif - /* Listing view parameters. */ -#define HEIGHT (LINES - 4) -#define STATUSPOS (COLS - 16) +#define HEIGHT (LINES - 4) /* Listing view flags. */ #define SHOW_FILES 0x01u @@ -156,7 +137,7 @@ typedef struct Marks { /* Line editing state. */ typedef struct Edit { - wchar_t buffer[PATH_MAX +1]; + wchar_t buffer[PATH_MAX + 1]; int left, right; } Edit; @@ -175,7 +156,7 @@ typedef struct Prog { } Prog; /* Global state. */ -static struct Rover { +struct Rover { int tab; int nfiles; Row *rows; @@ -187,7 +168,9 @@ static struct Rover { volatile sig_atomic_t pending_winch; Prog prog; Tab tabs[10]; -} rover; +}; + +extern struct Rover rover; /* Macros for accessing global state. */ #define ENAME(I) rover.rows[I].name @@ -208,20 +191,30 @@ static struct Rover { /* Line Editing Macros. */ #define EDIT_FULL(E) ((E).left == (E).right) #define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX -1) +#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX - 1) #define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] #define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] #define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) #define EDIT_BACKSPACE(E) (E).left-- #define EDIT_DELETE(E) (E).right++ -#define EDIT_CLEAR(E) \ - do { \ - (E).left = 0; \ - (E).right = PATH_MAX -1; \ - } while (0) - - - +#define EDIT_CLEAR(E) \ + { \ + (E).left = 0; \ + (E).right = PATH_MAX - 1; \ + } +/* Add / at the end of path */ +#define ADDSLASH(path) \ + { \ + if (path[strlen(path) - 1] != '/') \ + strcat(path, "/"); \ + } +/* Safe version of free() don't need assign NULL after free */ +#define FREE(p) \ + { \ + if ((p)) \ + free((p)); \ + (p) = NULL; \ + } typedef enum EditStat { CONTINUE, CONFIRM, @@ -230,6 +223,7 @@ typedef enum EditStat { typedef int (*PROCESS)(const char *path); +// FUnction declarations void init_marks(Marks *marks); void mark_none(Marks *marks); void add_mark(Marks *marks, char *dirpath, char *entry); @@ -240,7 +234,6 @@ void handle_winch(int sig); void enable_handlers(); void disable_handlers(); void reload(); -void update_view(); void sync_signals(); int rover_getch(); int rover_get_wch(wint_t *wch); @@ -248,11 +241,10 @@ void spawn(char **args); void shell_escaped_cat(char *buf, char *str, size_t n); int open_with_env(char *program, char *path); void update_view(); -void clear_message(); int rowcmp(const void *a, const void *b); int ls(Row **rowsp, uint8_t flags); void free_rows(Row **rowsp, int nfiles); -void cd(int reset); +void cd(bool reset); void try_to_sel(const char *target); void reload(); off_t count_dir(const char *path); diff --git a/src/main.c b/src/main.c index 7ca9513..47eb9c3 100644 --- a/src/main.c +++ b/src/main.c @@ -10,6 +10,8 @@ static PROCESS deldir = rmdir; +struct Rover rover; + int main(int argc, char *argv[]) { int i, ch; @@ -18,7 +20,7 @@ int main(int argc, char *argv[]) const char *key, *clip_path; DIR *d; EditStat edit_stat; - FILE *save_cwd_file = NULL, *save_marks_file = NULL, *clip_file; + FILE *save_cwd_file = NULL, *save_marks_file = NULL, *clip_file; if (argc >= 2) { if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { @@ -70,9 +72,11 @@ int main(int argc, char *argv[]) init_term(); rover.nfiles = 0; for (i = 0; i < 10; i++) { - rover.tabs[i].esel = rover.tabs[i].scroll = 0; - rover.tabs[i].flags = RV_FLAGS; + rover.tabs[i].esel = 0; + rover.tabs[i].scroll = 0; + rover.tabs[i].flags = RV_FLAGS; } + strcpy(rover.tabs[0].cwd, getenv("HOME")); for (i = 1; i < argc && i < 10; i++) { if ((d = opendir(argv[i]))) { @@ -81,16 +85,20 @@ int main(int argc, char *argv[]) } else strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); } + getcwd(rover.tabs[i].cwd, PATH_MAX); for (i++; i < 10; i++) - strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); + strcpy(rover.tabs[i].cwd, rover.tabs[i -1].cwd); + for (i = 0; i < 10; i++) - if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') - strcat(rover.tabs[i].cwd, "/"); + ADDSLASH(rover.tabs[i]); + rover.tab = 1; - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + rover.window = subwin(stdscr, LINES -2, COLS, 1, 0); init_marks(&rover.marks); - cd(1); + cd(true); +/* TODO check starting from here */ + strcpy(clipboard, CWD); if (rover.nfiles > 0) strcat(clipboard, ENAME(ESEL)); @@ -102,7 +110,7 @@ int main(int argc, char *argv[]) break; else if (ch >= '0' && ch <= '9') { rover.tab = ch - '0'; - cd(0); + cd(false); } else if (!strcmp(key, RVK_HELP)) { spawn((char *[]){ "man", "rover", NULL }); } else if (!strcmp(key, RVK_DOWN)) { @@ -146,7 +154,7 @@ int main(int argc, char *argv[]) continue; } strcat(CWD, ENAME(ESEL)); - cd(1); + cd(true); } else if (!strcmp(key, RVK_CD_UP)) { char *dirname, first; if (!strcmp(CWD, "/")) @@ -155,7 +163,7 @@ int main(int argc, char *argv[]) dirname = strrchr(CWD, '/') + 1; first = dirname[0]; dirname[0] = '\0'; - cd(1); + cd(true); dirname[0] = first; dirname[strlen(dirname)] = '/'; try_to_sel(dirname); @@ -165,13 +173,12 @@ int main(int argc, char *argv[]) update_view(); } else if (!strcmp(key, RVK_HOME)) { strcpy(CWD, getenv("HOME")); - if (CWD[strlen(CWD) - 1] != '/') - strcat(CWD, "/"); - cd(1); + ADDSLASH(CWD); + cd(true); } else if (!strcmp(key, RVK_TARGET)) { char *bname, first; int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX -1); + ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); if (len == -1) continue; buffer_one[len] = '\0'; @@ -198,7 +205,7 @@ int main(int argc, char *argv[]) bname = strrchr(CWD, '/') + 1; first = *bname; *bname = '\0'; - cd(1); + cd(true); *bname = first; if (is_dir) strcat(CWD, "/"); @@ -233,7 +240,7 @@ copy_path_done:; strcpy(CWD, dirname(buffer_one)); if (strcmp(CWD, "/")) strcat(CWD, "/"); - cd(1); + cd(true); strcpy(buffer_one, clipboard); try_to_sel(strstr(clipboard, basename(buffer_one))); update_view(); @@ -253,17 +260,17 @@ copy_path_done:; if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(user_pager, ENAME(ESEL))) - cd(0); + cd(false); } else if (!strcmp(key, RVK_EDIT)) { if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(user_editor, ENAME(ESEL))) - cd(0); + cd(false); } else if (!strcmp(key, RVK_OPEN)) { if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(user_open, ENAME(ESEL))) - cd(0); + cd(false); } else if (!strcmp(key, RVK_SEARCH)) { int oldsel, oldscroll, length; if (!rover.nfiles) @@ -336,7 +343,7 @@ copy_path_done:; if (edit_stat == CONFIRM) { if (ok) { if (addfile(input) == 0) { - cd(1); + cd(true); try_to_sel(input); update_view(); } else @@ -366,7 +373,7 @@ copy_path_done:; if (edit_stat == CONFIRM) { if (ok) { if (adddir(input) == 0) { - cd(1); + cd(true); strcat(input, "/"); try_to_sel(input); update_view(); @@ -407,7 +414,7 @@ copy_path_done:; del_mark(&rover.marks, ENAME(ESEL)); add_mark(&rover.marks, CWD, input); } - cd(1); + cd(true); try_to_sel(input); update_view(); } else diff --git a/src/rover.c b/src/rover.c index 7cce955..0c99dd9 100644 --- a/src/rover.c +++ b/src/rover.c @@ -6,7 +6,7 @@ void init_marks(Marks *marks) strcpy(marks->dirpath, ""); marks->bulk = BULK_INIT; marks->nentries = 0; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); + marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); } /* Unmark all entries. */ @@ -17,13 +17,12 @@ void mark_none(Marks *marks) strcpy(marks->dirpath, ""); for (i = 0; i < marks->bulk && marks->nentries; i++) if (marks->entries[i]) { - free(marks->entries[i]); - marks->entries[i] = NULL; + FREE(marks->entries[i]); marks->nentries--; } if (marks->bulk > BULK_THRESH) { /* Reset bulk to free some memory. */ - free(marks->entries); + FREE(marks->entries); marks->bulk = BULK_INIT; marks->entries = calloc(marks->bulk, sizeof *marks->entries); } @@ -69,8 +68,7 @@ void del_mark(Marks *marks, char *entry) for (i = 0; i < marks->bulk; i++) if (marks->entries[i] && !strcmp(marks->entries[i], entry)) break; - free(marks->entries[i]); - marks->entries[i] = NULL; + FREE(marks->entries[i]); marks->nentries--; } else mark_none(marks); @@ -82,10 +80,10 @@ void free_marks(Marks *marks) for (i = 0; i < marks->bulk && marks->nentries; i++) if (marks->entries[i]) { - free(marks->entries[i]); + FREE(marks->entries[i]); marks->nentries--; } - free(marks->entries); + FREE(marks->entries); } void handle_usr1(int sig) @@ -214,9 +212,9 @@ int open_with_env(char *program, char *path) if (program) { #ifdef RV_SHELL - strncpy(buffer, program, PATH_MAX -1); + strncpy(buffer, program, PATH_MAX - 1); strncat(buffer, " ", PATH_MAX - strlen(program) - 1); - shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) -2); + shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) - 2); spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); #else spawn((char *[]){ program, path, NULL }); @@ -226,123 +224,6 @@ int open_with_env(char *program, char *path) return 0; } - -/* Update the listing view. */ -void update_view() -{ - int i, j, numsize, ishidden, marking; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX]; - wchar_t wbuffer[PATH_MAX]; - - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, buffer_one); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(wbuffer, CWD, PATH_MAX); - mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window - shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = ENAME(j)[0] == '.'; - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - if (S_ISDIR(EMODE(j))) { - mbstowcs(wbuffer, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(wbuffer, L"/"); - } else { - char *suffix, *suffixes = "BKMGTPEZY"; - off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); - int namecols = wcswidth(wbuffer, length); - for (suffix = suffixes; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - if (*suffix == 'B') - swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", - (int)(COLS - namecols - 6), - (int)human_size / 10, *suffix); - else - swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", - (int)(COLS - namecols - 8), - (int)human_size / 10, (int)human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - if (rover.nfiles > HEIGHT) { - int center, height; - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT - 1) * HEIGHT / rover.nfiles; - if (!height) - height = 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); - } - buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - if (!rover.nfiles) - strcpy(buffer_two, "0/0"); - else - snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, buffer_one); - wrefresh(rover.window); -} - - -/* Clear message area, leaving only status info. */ -void clear_message() -{ - mvhline(LINES - 1, 0, ' ', STATUSPOS); -} - /* Comparison used to sort listing entries. */ int rowcmp(const void *a, const void *b) { @@ -366,15 +247,19 @@ int ls(Row **rowsp, uint8_t flags) if (!(dp = opendir("."))) return -1; + n = -2; /* We don't want the entries "." and "..". */ while (readdir(dp)) n++; + if (n == 0) { closedir(dp); + return 0; } + rewinddir(dp); - rows = malloc(n * sizeof *rows); + rows = malloc(n * sizeof(*rows)); i = 0; while ((ep = readdir(dp))) { if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) @@ -386,15 +271,15 @@ int ls(Row **rowsp, uint8_t flags) stat(ep->d_name, &statbuf); if (S_ISDIR(statbuf.st_mode)) { if (flags & SHOW_DIRS) { - rows[i].name = malloc(strlen(ep->d_name) + 2); + rows[i].name = (char *) malloc(strlen(ep->d_name) +2); strcpy(rows[i].name, ep->d_name); if (!rows[i].islink) - strcat(rows[i].name, "/"); + ADDSLASH(rows[i].name); rows[i].mode = statbuf.st_mode; i++; } } else if (flags & SHOW_FILES) { - rows[i].name = malloc(strlen(ep->d_name) + 1); + rows[i].name = (char *) malloc(strlen(ep->d_name) +1); strcpy(rows[i].name, ep->d_name); rows[i].size = statbuf.st_size; rows[i].mode = statbuf.st_mode; @@ -405,6 +290,7 @@ int ls(Row **rowsp, uint8_t flags) qsort(rows, n, sizeof(*rows), rowcmp); closedir(dp); *rowsp = rows; + return n; } @@ -413,44 +299,50 @@ void free_rows(Row **rowsp, int nfiles) int i; for (i = 0; i < nfiles; i++) - free((*rowsp)[i].name); - free(*rowsp); - *rowsp = NULL; + FREE((*rowsp)[i].name); + FREE(*rowsp); } /* Change working directory to the path in CWD. */ -void cd(int reset) +void cd(bool reset) { int i, j; message(CYAN, "Loading \"%s\"...", CWD); refresh(); if (chdir(CWD) == -1) { - getcwd(CWD, PATH_MAX - 1); - if (CWD[strlen(CWD) - 1] != '/') - strcat(CWD, "/"); - goto done; + getcwd(CWD, PATH_MAX -1); + ADDSLASH(CWD); + + clear_message(); + update_view(); + + return; } + if (reset) ESEL = SCROLL = 0; + if (rover.nfiles) free_rows(&rover.rows, rover.nfiles); + rover.nfiles = ls(&rover.rows, FLAGS); if (!strcmp(CWD, rover.marks.dirpath)) { for (i = 0; i < rover.nfiles; i++) { for (j = 0; j < rover.marks.bulk; j++) - if ( - rover.marks.entries[j] && - !strcmp(rover.marks.entries[j], ENAME(i))) + if ( rover.marks.entries[j] && !strcmp(rover.marks.entries[j], ENAME(i))) break; + MARKED(i) = j < rover.marks.bulk; } } else for (i = 0; i < rover.nfiles; i++) MARKED(i) = 0; -done: + clear_message(); update_view(); + + return; } /* Select a target entry, if it is present. */ @@ -471,11 +363,11 @@ void reload() if (rover.nfiles) { strcpy(input, ENAME(ESEL)); - cd(0); + cd(false); try_to_sel(input); update_view(); } else - cd(1); + cd(true); } off_t count_dir(const char *path) @@ -664,7 +556,7 @@ int cpyfile(const char *srcpath) if (ret < 0) return ret; buffer[ret] = '\0'; - ret = symlink(buffer, dstpath); + ret = symlink(buffer, dstpath); } else { ret = src = open(srcpath, O_RDONLY); if (ret < 0) @@ -724,10 +616,10 @@ void start_line_edit(const char *init_input) curs_set(TRUE); strncpy(input, init_input, PATH_MAX); - rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); - rover.edit.right = PATH_MAX - 1; + rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); + rover.edit.right = PATH_MAX - 1; rover.edit.buffer[PATH_MAX] = L'\0'; - rover.edit_scroll = 0; + rover.edit_scroll = 0; } /* Read input and change editing state accordingly. */ @@ -788,4 +680,3 @@ EditStat get_line_edit() PATH_MAX - length); return CONTINUE; } - diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 0a8c2c2..0e86f2e 100644 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -67,18 +67,134 @@ void update_input(const char *prompt, Color color, const char *input) /* Show a message on the status bar. */ void message(Color color, char *fmt, ...) { - int len, pos; + int pos; va_list args; char buffer[PATH_MAX]; va_start(args, fmt); - vsnprintf(buffer, MIN(PATH_MAX, STATUSPOS), fmt, args); + // vsnprintf(buffer, MIN(PATH_MAX, STATUSPOS), fmt, args); + vsnprintf(buffer, STATUSPOS, fmt, args); va_end(args); - len = strlen(buffer); - pos = (STATUSPOS - len) / 2; + + pos = (STATUSPOS - (int) strlen(buffer)) /2; attr_on(A_BOLD, NULL); color_set(color, NULL); - mvaddstr(LINES - 1, pos, buffer); + mvaddstr(LINES -1, pos, buffer); color_set(DEFAULT, NULL); attr_off(A_BOLD, NULL); } + +/* Update the listing view. */ +void update_view() +{ + int i, j, numsize, ishidden, marking; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX]; + wchar_t wbuffer[PATH_MAX]; + + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, buffer_one); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window + shrinking. In that case, the scroll must be moved to make it visible. */ + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = ENAME(j)[0] == '.'; + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + if (S_ISDIR(EMODE(j))) { + mbstowcs(wbuffer, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(wbuffer, L"/"); + } else { + char *suffix, *suffixes = "BKMGTPEZY"; + off_t human_size = ESIZE(j) * 10; + int length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + int namecols = wcswidth(wbuffer, length); + for (suffix = suffixes; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + if (*suffix == 'B') + swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", + (int)(COLS - namecols - 6), + (int)human_size / 10, *suffix); + else + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", + (int)(COLS - namecols - 8), + (int)human_size / 10, (int)human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); + } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + if (rover.nfiles > HEIGHT) { + int center, height; + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT - 1) * HEIGHT / rover.nfiles; + if (!height) + height = 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); + } + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + if (!rover.nfiles) + strcpy(buffer_two, "0/0"); + else + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); + wrefresh(rover.window); +} + +/* Clear message area, leaving only status info. */ +void clear_message() +{ + mvhline(LINES -1, 0, ' ', STATUSPOS); +} \ No newline at end of file diff --git a/src/ui_funcs.h b/src/ui_funcs.h index c31eef2..27a8461 100644 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -5,6 +5,26 @@ #include /* setlocale(), LC_ALL */ #include /* PATH_MAX */ +#define STATUSPOS (COLS - 16) + +/* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ +#define RVC_CWD GREEN +#define RVC_STATUS CYAN +#define RVC_BORDER BLUE +#define RVC_SCROLLBAR CYAN +#define RVC_LINK CYAN +#define RVC_HIDDEN YELLOW +#define RVC_EXEC GREEN +#define RVC_REG DEFAULT +#define RVC_DIR DEFAULT +#define RVC_CHR MAGENTA +#define RVC_BLK MAGENTA +#define RVC_FIFO BLUE +#define RVC_SOCK MAGENTA +#define RVC_PROMPT DEFAULT +#define RVC_TABNUM DEFAULT +#define RVC_MARKS YELLOW + typedef enum Color { DEFAULT, RED, @@ -17,8 +37,11 @@ typedef enum Color { BLACK } Color; +// Function declarations void init_term(); void update_input(const char *prompt, Color color, const char *input); void message(Color color, char *fmt, ...); +void update_view(); +void clear_message(); #endif // _UI_FUNCS_H \ No newline at end of file From b3ec3521c1ceefbb336bfd301272b79dff799270 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 29 Dec 2022 23:10:09 +0100 Subject: [PATCH 06/18] some improvments config.h in struct Row changed type to bool was int main.c some bug fix rover.c some bug fix --- .clang-format | 0 .gitignore | 0 .vscode/launch.json | 0 .vscode/tasks.json | 0 CHANGES.md | 0 FAQ.md | 0 Makefile | 0 README.md | 0 rover.1 | 0 src/config.h | 4 ++-- src/main.c | 7 +++---- src/rover.c | 7 +++++-- src/ui_funcs.c | 0 src/ui_funcs.h | 0 14 files changed, 10 insertions(+), 8 deletions(-) mode change 100644 => 100755 .clang-format mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .vscode/launch.json mode change 100644 => 100755 .vscode/tasks.json mode change 100644 => 100755 CHANGES.md mode change 100644 => 100755 FAQ.md mode change 100644 => 100755 Makefile mode change 100644 => 100755 README.md mode change 100644 => 100755 rover.1 mode change 100644 => 100755 src/config.h mode change 100644 => 100755 src/main.c mode change 100644 => 100755 src/rover.c mode change 100644 => 100755 src/ui_funcs.c mode change 100644 => 100755 src/ui_funcs.h diff --git a/.clang-format b/.clang-format old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.vscode/launch.json b/.vscode/launch.json old mode 100644 new mode 100755 diff --git a/.vscode/tasks.json b/.vscode/tasks.json old mode 100644 new mode 100755 diff --git a/CHANGES.md b/CHANGES.md old mode 100644 new mode 100755 diff --git a/FAQ.md b/FAQ.md old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/rover.1 b/rover.1 old mode 100644 new mode 100755 diff --git a/src/config.h b/src/config.h old mode 100644 new mode 100755 index 6a2b6f7..d488698 --- a/src/config.h +++ b/src/config.h @@ -123,8 +123,8 @@ typedef struct Row { char *name; off_t size; mode_t mode; - int islink; - int marked; + bool islink; + bool marked; } Row; /* Dynamic array of marked entries. */ diff --git a/src/main.c b/src/main.c old mode 100644 new mode 100755 index 47eb9c3..f8f9d06 --- a/src/main.c +++ b/src/main.c @@ -91,17 +91,16 @@ int main(int argc, char *argv[]) strcpy(rover.tabs[i].cwd, rover.tabs[i -1].cwd); for (i = 0; i < 10; i++) - ADDSLASH(rover.tabs[i]); + ADDSLASH(rover.tabs[i].cwd); rover.tab = 1; rover.window = subwin(stdscr, LINES -2, COLS, 1, 0); init_marks(&rover.marks); cd(true); -/* TODO check starting from here */ - strcpy(clipboard, CWD); if (rover.nfiles > 0) strcat(clipboard, ENAME(ESEL)); +/* TODO use switch() instead of if-else */ while (1) { ch = rover_getch(); key = keyname(ch); @@ -467,7 +466,7 @@ copy_path_done:; for (i = 0; i < rover.nfiles; i++) if (!MARKED(i)) { add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = 1; + MARKED(i) = true; } update_view(); } else if (!strcmp(key, RVK_MARK_DELETE)) { diff --git a/src/rover.c b/src/rover.c old mode 100644 new mode 100755 index 0c99dd9..c58dcfa --- a/src/rover.c +++ b/src/rover.c @@ -134,6 +134,7 @@ void sync_signals() rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles) SCROLL = ESEL - HEIGHT; + update_view(); rover.pending_winch = 0; } @@ -147,6 +148,7 @@ int rover_getch() while ((ch = getch()) == ERR) sync_signals(); + return ch; } @@ -337,7 +339,7 @@ void cd(bool reset) } } else for (i = 0; i < rover.nfiles; i++) - MARKED(i) = 0; + MARKED(i) = false; clear_message(); update_view(); @@ -350,8 +352,9 @@ void try_to_sel(const char *target) { ESEL = 0; if (!ISDIR(target)) - while ((ESEL + 1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) + while ((ESEL +1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) ESEL++; + while ((ESEL + 1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) ESEL++; } diff --git a/src/ui_funcs.c b/src/ui_funcs.c old mode 100644 new mode 100755 diff --git a/src/ui_funcs.h b/src/ui_funcs.h old mode 100644 new mode 100755 From ea39edc2968f25242b0942e5cc665f216d1a3df9 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 30 Dec 2022 19:42:17 +0100 Subject: [PATCH 07/18] a lot of improvment of code now I have to go ahead from main_menu() -> case 'y' --- .vscode/settings.json | 10 + rover.1 | 12 +- src/config.h | 60 +---- src/main.c | 434 +-------------------------------- src/rover.c | 93 +------ src/ui_funcs.c | 553 ++++++++++++++++++++++++++++++++++++++++-- src/ui_funcs.h | 63 ++++- 7 files changed, 637 insertions(+), 588 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ed888d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "files.associations": { + "unistd.h": "c", + "stdlib.h": "c", + "stdarg.h": "c", + "locale.h": "c", + "environments.h": "c", + "unistd_ext.h": "c" + } +} \ No newline at end of file diff --git a/rover.1 b/rover.1 index 15aed34..ccd9756 100755 --- a/rover.1 +++ b/rover.1 @@ -1,4 +1,4 @@ -.TH ROVER 1 2020-06-04 rover\-1.0.1 +.TH ROVER 2 2022-12-31 rover\-2.0.1 .SH NAME rover \- file browser for the terminal .SH SYNOPSIS @@ -75,14 +75,14 @@ pointed to the "source" directory of the operation and then issue the command on another tab that is pointed to the "destination" directory. .SH COMMANDS .TP -.B q +.B ESC Quit rover. .TP -.B j/k -Move cursor down/up. +.B arrow up/down +Move cursor up/down. .TP -.B J/K -Move cursor down/up 10 lines. +.B Page up/down +Move cursor up/down 10 lines. .TP .B g/G Move cursor to top/bottom of listing. diff --git a/src/config.h b/src/config.h index d488698..58c3a9e 100755 --- a/src/config.h +++ b/src/config.h @@ -7,7 +7,6 @@ #define _XOPEN_SOURCE_EXTENDED #define _FILE_OFFSET_BITS 64 -#include #include #include #include @@ -29,44 +28,7 @@ #include #include -#define RV_VERSION "1.0.1" - -/* CTRL+X: "^X" - ALT+X: "M-X" */ -#define RVK_QUIT "q" -#define RVK_HELP "?" -#define RVK_DOWN "j" -#define RVK_UP "k" -#define RVK_JUMP_DOWN "J" -#define RVK_JUMP_UP "K" -#define RVK_JUMP_TOP "g" -#define RVK_JUMP_BOTTOM "G" -#define RVK_CD_DOWN "l" -#define RVK_CD_UP "h" -#define RVK_HOME "H" -#define RVK_TARGET "t" -#define RVK_COPY_PATH "y" -#define RVK_PASTE_PATH "p" -#define RVK_REFRESH "r" -#define RVK_SHELL "^M" -#define RVK_VIEW " " -#define RVK_EDIT "e" -#define RVK_OPEN "o" -#define RVK_SEARCH "/" -#define RVK_TG_FILES "f" -#define RVK_TG_DIRS "d" -#define RVK_TG_HIDDEN "s" -#define RVK_NEW_FILE "n" -#define RVK_NEW_DIR "N" -#define RVK_RENAME "R" -#define RVK_TG_EXEC "E" -#define RVK_DELETE "D" -#define RVK_TG_MARK "m" -#define RVK_INVMARK "M" -#define RVK_MARKALL "a" -#define RVK_MARK_DELETE "X" -#define RVK_MARK_COPY "C" -#define RVK_MARK_MOVE "V" +#define RV_VERSION "2.0.1" /* Special symbols used by the TUI. See for available constants. */ #define RVS_SCROLLBAR ACS_CKBOARD @@ -79,9 +41,6 @@ #define RVP_NEW_DIR RV_PROMPT("new dir") #define RVP_RENAME RV_PROMPT("rename") -/* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ -#define RV_JUMP 10 - /* Default listing view flags. May include SHOW_FILES, SHOW_DIRS and SHOW_HIDDEN. */ #define RV_FLAGS SHOW_FILES | SHOW_DIRS @@ -89,17 +48,6 @@ /* Optional macro to be executed when a batch operation finishes. */ #define RV_ALERT() beep() -/* Shell used to launch external programs. - Defining this macro will force Rover to launch external - programs with `sh -c "$EXTERNAL_PROGRAM [arg]"`. This gives more - flexibility, allowing command-line arguments to be embedded in - environment variables (e.g. PAGER="less -N"). On the other hand, - this requires the presence of a shell and will spawn an additional - process each time an external program is invoked. Leave this macro - undefined if you prefer external programs to be launched with just - `$EXTERNAL_PROGRAM [arg]`. */ -#define RV_SHELL "/bin/sh" - /* This signal is not defined by POSIX, but should be present on all systems that have resizable terminals. */ #ifndef SIGWINCH @@ -229,17 +177,11 @@ void mark_none(Marks *marks); void add_mark(Marks *marks, char *dirpath, char *entry); void del_mark(Marks *marks, char *entry); void free_marks(Marks *marks); -void handle_usr1(int sig); -void handle_winch(int sig); -void enable_handlers(); -void disable_handlers(); void reload(); void sync_signals(); int rover_getch(); int rover_get_wch(wint_t *wch); -void spawn(char **args); void shell_escaped_cat(char *buf, char *str, size_t n); -int open_with_env(char *program, char *path); void update_view(); int rowcmp(const void *a, const void *b); int ls(Row **rowsp, uint8_t flags); diff --git a/src/main.c b/src/main.c index f8f9d06..d45653f 100755 --- a/src/main.c +++ b/src/main.c @@ -1,26 +1,15 @@ #include "config.h" #include "ui_funcs.h" -/* Get user programs from the environment vars */ -#define ROVER_ENV(dst, src) \ - { \ - if ((dst = getenv("ROVER_" #src)) == NULL) \ - dst = getenv(#src); \ - } - -static PROCESS deldir = rmdir; - struct Rover rover; int main(int argc, char *argv[]) { - int i, ch; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; - char *program, *entry, *user_shell, *user_pager, *user_editor, *user_open; - const char *key, *clip_path; + int i; + char clipboard[PATH_MAX]; + char *entry; DIR *d; - EditStat edit_stat; - FILE *save_cwd_file = NULL, *save_marks_file = NULL, *clip_file; + FILE *save_cwd_file = NULL, *save_marks_file = NULL; if (argc >= 2) { if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { @@ -62,13 +51,6 @@ int main(int argc, char *argv[]) } } - ROVER_ENV(user_shell, SHELL); - ROVER_ENV(user_pager, PAGER); - ROVER_ENV(user_editor, VISUAL); - if (!user_editor) - ROVER_ENV(user_editor, EDITOR); - ROVER_ENV(user_open, OPEN); - init_term(); rover.nfiles = 0; for (i = 0; i < 10; i++) { @@ -88,414 +70,21 @@ int main(int argc, char *argv[]) getcwd(rover.tabs[i].cwd, PATH_MAX); for (i++; i < 10; i++) - strcpy(rover.tabs[i].cwd, rover.tabs[i -1].cwd); - + strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); + for (i = 0; i < 10; i++) ADDSLASH(rover.tabs[i].cwd); - + rover.tab = 1; - rover.window = subwin(stdscr, LINES -2, COLS, 1, 0); + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); init_marks(&rover.marks); cd(true); strcpy(clipboard, CWD); if (rover.nfiles > 0) strcat(clipboard, ENAME(ESEL)); -/* TODO use switch() instead of if-else */ - while (1) { - ch = rover_getch(); - key = keyname(ch); - clear_message(); - if (!strcmp(key, RVK_QUIT)) - break; - else if (ch >= '0' && ch <= '9') { - rover.tab = ch - '0'; - cd(false); - } else if (!strcmp(key, RVK_HELP)) { - spawn((char *[]){ "man", "rover", NULL }); - } else if (!strcmp(key, RVK_DOWN)) { - if (!rover.nfiles) - continue; - ESEL = MIN(ESEL + 1, rover.nfiles - 1); - update_view(); - } else if (!strcmp(key, RVK_UP)) { - if (!rover.nfiles) - continue; - ESEL = MAX(ESEL - 1, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_DOWN)) { - if (!rover.nfiles) - continue; - ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); - if (rover.nfiles > HEIGHT) - SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); - update_view(); - } else if (!strcmp(key, RVK_JUMP_UP)) { - if (!rover.nfiles) - continue; - ESEL = MAX(ESEL - RV_JUMP, 0); - SCROLL = MAX(SCROLL - RV_JUMP, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_TOP)) { - if (!rover.nfiles) - continue; - ESEL = 0; - update_view(); - } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { - if (!rover.nfiles) - continue; - ESEL = rover.nfiles - 1; - update_view(); - } else if (!strcmp(key, RVK_CD_DOWN)) { - if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) - continue; - if (chdir(ENAME(ESEL)) == -1) { - message(RED, "Cannot access \"%s\".", ENAME(ESEL)); - continue; - } - strcat(CWD, ENAME(ESEL)); - cd(true); - } else if (!strcmp(key, RVK_CD_UP)) { - char *dirname, first; - if (!strcmp(CWD, "/")) - continue; - CWD[strlen(CWD) - 1] = '\0'; - dirname = strrchr(CWD, '/') + 1; - first = dirname[0]; - dirname[0] = '\0'; - cd(true); - dirname[0] = first; - dirname[strlen(dirname)] = '/'; - try_to_sel(dirname); - dirname[0] = '\0'; - if (rover.nfiles > HEIGHT) - SCROLL = ESEL - HEIGHT / 2; - update_view(); - } else if (!strcmp(key, RVK_HOME)) { - strcpy(CWD, getenv("HOME")); - ADDSLASH(CWD); - cd(true); - } else if (!strcmp(key, RVK_TARGET)) { - char *bname, first; - int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); - if (len == -1) - continue; - buffer_one[len] = '\0'; - if (access(buffer_one, F_OK) == -1) { - char *msg; - switch (errno) { - case EACCES: - msg = "Cannot access \"%s\"."; - break; - case ENOENT: - msg = "\"%s\" does not exist."; - break; - default: - msg = "Cannot navigate to \"%s\"."; - } - strcpy(buffer_two, buffer_one); /* message() uses buffer_one. */ - message(RED, msg, buffer_two); - continue; - } - realpath(buffer_one, CWD); - len = strlen(CWD); - if (CWD[len - 1] == '/') - CWD[len - 1] = '\0'; - bname = strrchr(CWD, '/') + 1; - first = *bname; - *bname = '\0'; - cd(true); - *bname = first; - if (is_dir) - strcat(CWD, "/"); - try_to_sel(bname); - *bname = '\0'; - update_view(); - } else if (!strcmp(key, RVK_COPY_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) - goto copy_path_fail; - clip_file = fopen(clip_path, "w"); - if (!clip_file) - goto copy_path_fail; - fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); - fclose(clip_file); - goto copy_path_done; -copy_path_fail: - strcpy(clipboard, CWD); - strcat(clipboard, ENAME(ESEL)); -copy_path_done:; - } else if (!strcmp(key, RVK_PASTE_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) - goto paste_path_fail; - clip_file = fopen(clip_path, "r"); - if (!clip_file) - goto paste_path_fail; - fscanf(clip_file, "%s\n", clipboard); - fclose(clip_file); -paste_path_fail: - strcpy(buffer_one, clipboard); - strcpy(CWD, dirname(buffer_one)); - if (strcmp(CWD, "/")) - strcat(CWD, "/"); - cd(true); - strcpy(buffer_one, clipboard); - try_to_sel(strstr(clipboard, basename(buffer_one))); - update_view(); - } else if (!strcmp(key, RVK_REFRESH)) { - reload(); - } else if (!strcmp(key, RVK_SHELL)) { - program = user_shell; - if (program) { -#ifdef RV_SHELL - spawn((char *[]){ RV_SHELL, "-c", program, NULL }); -#else - spawn((char *[]){ program, NULL }); -#endif - reload(); - } - } else if (!strcmp(key, RVK_VIEW)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_pager, ENAME(ESEL))) - cd(false); - } else if (!strcmp(key, RVK_EDIT)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_editor, ENAME(ESEL))) - cd(false); - } else if (!strcmp(key, RVK_OPEN)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (open_with_env(user_open, ENAME(ESEL))) - cd(false); - } else if (!strcmp(key, RVK_SEARCH)) { - int oldsel, oldscroll, length; - if (!rover.nfiles) - continue; - oldsel = ESEL; - oldscroll = SCROLL; - start_line_edit(""); - update_input(RVP_SEARCH, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int sel; - Color color = RED; - length = strlen(input); - if (length) { - for (sel = 0; sel < rover.nfiles; sel++) - if (!strncmp(ENAME(sel), input, length)) - break; - if (sel < rover.nfiles) { - color = GREEN; - ESEL = sel; - if (rover.nfiles > HEIGHT) { - if (sel < 3) - SCROLL = 0; - else if (sel - 3 > rover.nfiles - HEIGHT) - SCROLL = rover.nfiles - HEIGHT; - else - SCROLL = sel - 3; - } - } - } else { - ESEL = oldsel; - SCROLL = oldscroll; - } - update_view(); - update_input(RVP_SEARCH, color, input); - } - if (edit_stat == CANCEL) { - ESEL = oldsel; - SCROLL = oldscroll; - } - clear_message(); - update_view(); - } else if (!strcmp(key, RVK_TG_FILES)) { - FLAGS ^= SHOW_FILES; - reload(); - } else if (!strcmp(key, RVK_TG_DIRS)) { - FLAGS ^= SHOW_DIRS; - reload(); - } else if (!strcmp(key, RVK_TG_HIDDEN)) { - FLAGS ^= SHOW_HIDDEN; - reload(); - } else if (!strcmp(key, RVK_NEW_FILE)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_FILE, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(input); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - } - update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (addfile(input) == 0) { - cd(true); - try_to_sel(input); - update_view(); - } else - message(RED, "Could not create \"%s\".", input); - } else - message(RED, "\"%s\" already exists.", input); - } - } else if (!strcmp(key, RVK_NEW_DIR)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_DIR, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(input); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - } - update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (adddir(input) == 0) { - cd(true); - strcat(input, "/"); - try_to_sel(input); - update_view(); - } else - message(RED, "Could not create \"%s/\".", input); - } else - message(RED, "\"%s\" already exists.", input); - } - } else if (!strcmp(key, RVK_RENAME)) { - int ok = 0; - char *last; - int isdir; - strcpy(input, ENAME(ESEL)); - last = input + strlen(input) - 1; - if ((isdir = *last == '/')) - *last = '\0'; - start_line_edit(input); - update_input(RVP_RENAME, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(input); - ok = length; - for (i = 0; i < rover.nfiles; i++) - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { - ok = 0; - break; - } - update_input(RVP_RENAME, (ok ? GREEN : RED), input); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (isdir) - strcat(input, "/"); - if (ok) { - if (!rename(ENAME(ESEL), input) && MARKED(ESEL)) { - del_mark(&rover.marks, ENAME(ESEL)); - add_mark(&rover.marks, CWD, input); - } - cd(true); - try_to_sel(input); - update_view(); - } else - message(RED, "\"%s\" already exists.", input); - } - } else if (!strcmp(key, RVK_TG_EXEC)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) - continue; - if (S_IXUSR & EMODE(ESEL)) - EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); - else - EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; - if (chmod(ENAME(ESEL), EMODE(ESEL))) { - message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); - } else { - message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); - update_view(); - } - } else if (!strcmp(key, RVK_DELETE)) { - if (rover.nfiles) { - message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); - if (rover_getch() == 'Y') { - const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); - reload(); - if (ret) - message(RED, "Could not delete \"%s\".", ENAME(ESEL)); - } else - clear_message(); - } else - message(RED, "No entry selected for deletion."); - } else if (!strcmp(key, RVK_TG_MARK)) { - if (MARKED(ESEL)) - del_mark(&rover.marks, ENAME(ESEL)); - else - add_mark(&rover.marks, CWD, ENAME(ESEL)); - MARKED(ESEL) = !MARKED(ESEL); - ESEL = (ESEL + 1) % rover.nfiles; - update_view(); - } else if (!strcmp(key, RVK_INVMARK)) { - for (i = 0; i < rover.nfiles; i++) { - if (MARKED(i)) - del_mark(&rover.marks, ENAME(i)); - else - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = !MARKED(i); - } - update_view(); - } else if (!strcmp(key, RVK_MARKALL)) { - for (i = 0; i < rover.nfiles; i++) - if (!MARKED(i)) { - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = true; - } - update_view(); - } else if (!strcmp(key, RVK_MARK_DELETE)) { - if (rover.marks.nentries) { - message(YELLOW, "Delete all marked entries? (Y/n)"); - if (rover_getch() == 'Y') - process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); - else - clear_message(); - } else - message(RED, "No entries marked for deletion."); - } else if (!strcmp(key, RVK_MARK_COPY)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); - else - message(RED, "Cannot copy to the same path."); - } else - message(RED, "No entries marked for copying."); - } else if (!strcmp(key, RVK_MARK_MOVE)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); - else - message(RED, "Cannot move to the same path."); - } else - message(RED, "No entries marked for moving."); - } - } + + main_menu(); + if (rover.nfiles) free_rows(&rover.rows, rover.nfiles); delwin(rover.window); @@ -512,5 +101,6 @@ copy_path_done:; fclose(save_marks_file); } free_marks(&rover.marks); + return 0; } diff --git a/src/rover.c b/src/rover.c index c58dcfa..85c4bcf 100755 --- a/src/rover.c +++ b/src/rover.c @@ -86,37 +86,6 @@ void free_marks(Marks *marks) FREE(marks->entries); } -void handle_usr1(int sig) -{ - rover.pending_usr1 = 1; -} - -void handle_winch(int sig) -{ - rover.pending_winch = 1; -} - -void enable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = handle_usr1; - sigaction(SIGUSR1, &sa, NULL); - sa.sa_handler = handle_winch; - sigaction(SIGWINCH, &sa, NULL); -} - -void disable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = SIG_DFL; - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); -} - /* Handle any signals received since last call. */ void sync_signals() { @@ -163,27 +132,6 @@ int rover_get_wch(wint_t *wch) return ret; } -/* Do a fork-exec to external program (e.g. $EDITOR). */ -void spawn(char **args) -{ - pid_t pid; - int status; - - setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); - pid = fork(); - if (pid > 0) { - /* fork() succeeded. */ - disable_handlers(); - endwin(); - waitpid(pid, &status, 0); - enable_handlers(); - kill(getpid(), SIGWINCH); - } else if (pid == 0) { - /* Child process. */ - execvp(args[0], args); - } -} - void shell_escaped_cat(char *buf, char *str, size_t n) { char *p = buf + strlen(buf); @@ -208,23 +156,6 @@ void shell_escaped_cat(char *buf, char *str, size_t n) strncat(p, "'", n); } -int open_with_env(char *program, char *path) -{ - char buffer[PATH_MAX]; - - if (program) { -#ifdef RV_SHELL - strncpy(buffer, program, PATH_MAX - 1); - strncat(buffer, " ", PATH_MAX - strlen(program) - 1); - shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) - 2); - spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); -#else - spawn((char *[]){ program, path, NULL }); -#endif - return 1; - } - return 0; -} /* Comparison used to sort listing entries. */ int rowcmp(const void *a, const void *b) @@ -273,7 +204,7 @@ int ls(Row **rowsp, uint8_t flags) stat(ep->d_name, &statbuf); if (S_ISDIR(statbuf.st_mode)) { if (flags & SHOW_DIRS) { - rows[i].name = (char *) malloc(strlen(ep->d_name) +2); + rows[i].name = (char *)malloc(strlen(ep->d_name) + 2); strcpy(rows[i].name, ep->d_name); if (!rows[i].islink) ADDSLASH(rows[i].name); @@ -281,7 +212,7 @@ int ls(Row **rowsp, uint8_t flags) i++; } } else if (flags & SHOW_FILES) { - rows[i].name = (char *) malloc(strlen(ep->d_name) +1); + rows[i].name = (char *)malloc(strlen(ep->d_name) + 1); strcpy(rows[i].name, ep->d_name); rows[i].size = statbuf.st_size; rows[i].mode = statbuf.st_mode; @@ -292,7 +223,7 @@ int ls(Row **rowsp, uint8_t flags) qsort(rows, n, sizeof(*rows), rowcmp); closedir(dp); *rowsp = rows; - + return n; } @@ -313,10 +244,10 @@ void cd(bool reset) message(CYAN, "Loading \"%s\"...", CWD); refresh(); if (chdir(CWD) == -1) { - getcwd(CWD, PATH_MAX -1); + getcwd(CWD, PATH_MAX - 1); ADDSLASH(CWD); - clear_message(); + mvhline(LINES - 1, 0, ' ', STATUSPOS); update_view(); return; @@ -332,7 +263,7 @@ void cd(bool reset) if (!strcmp(CWD, rover.marks.dirpath)) { for (i = 0; i < rover.nfiles; i++) { for (j = 0; j < rover.marks.bulk; j++) - if ( rover.marks.entries[j] && !strcmp(rover.marks.entries[j], ENAME(i))) + if (rover.marks.entries[j] && !strcmp(rover.marks.entries[j], ENAME(i))) break; MARKED(i) = j < rover.marks.bulk; @@ -341,7 +272,7 @@ void cd(bool reset) for (i = 0; i < rover.nfiles; i++) MARKED(i) = false; - clear_message(); + mvhline(LINES - 1, 0, ' ', STATUSPOS); update_view(); return; @@ -352,7 +283,7 @@ void try_to_sel(const char *target) { ESEL = 0; if (!ISDIR(target)) - while ((ESEL +1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) + while ((ESEL + 1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) ESEL++; while ((ESEL + 1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) @@ -390,7 +321,7 @@ off_t count_dir(const char *path) snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); lstat(subpath, &statbuf); if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); + ADDSLASH(subpath); total += count_dir(subpath); } else total += statbuf.st_size; @@ -455,7 +386,7 @@ int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); lstat(subpath, &statbuf); if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); + ADDSLASH(subpath); ret |= process_dir(pre, proc, pos, subpath); } else ret |= proc(subpath); @@ -475,7 +406,7 @@ void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doin char *entry; char path[PATH_MAX]; - clear_message(); + mvhline(LINES - 1, 0, ' ', STATUSPOS); message(CYAN, "%s...", msg_doing); refresh(); rover.prog = (Prog){ 0, count_marked(), msg_doing }; @@ -670,7 +601,7 @@ EditStat get_line_edit() EDIT_BACKSPACE(rover.edit); } else if (wch == killer) { EDIT_CLEAR(rover.edit); - clear_message(); + mvhline(LINES - 1, 0, ' ', STATUSPOS); } else if (iswprint(wch)) { if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch); diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 0e86f2e..0f6f1e1 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -1,8 +1,42 @@ #include "config.h" #include "ui_funcs.h" +static void handle_usr1(int sig) +{ + rover.pending_usr1 = 1; +} + +static void handle_winch(int sig) +{ + rover.pending_winch = 1; +} + +static void enable_handlers(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + + sa.sa_handler = handle_usr1; + sigaction(SIGUSR1, &sa, NULL); + + sa.sa_handler = handle_winch; + sigaction(SIGWINCH, &sa, NULL); +} + +static void disable_handlers(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + + sa.sa_handler = SIG_DFL; + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGWINCH, &sa, NULL); +} + /* Curses setup. */ -void init_term() +void init_term(void) { setlocale(LC_ALL, ""); initscr(); @@ -39,29 +73,29 @@ void init_term() void update_input(const char *prompt, Color color, const char *input) { int plen, ilen, maxlen; - wchar_t wbuffer[PATH_MAX]; + wchar_t wbuffer[PATH_MAX]; plen = strlen(prompt); ilen = mbstowcs(NULL, input, 0); - maxlen = STATUSPOS - plen -2; + maxlen = STATUSPOS - plen - 2; if (ilen - rover.edit_scroll < maxlen) rover.edit_scroll = MAX(ilen - maxlen, 0); - else if (rover.edit.left > rover.edit_scroll + maxlen -1) + else if (rover.edit.left > rover.edit_scroll + maxlen - 1) rover.edit_scroll = rover.edit.left - maxlen; else if (rover.edit.left < rover.edit_scroll) rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); color_set(RVC_PROMPT, NULL); - mvaddstr(LINES -1, 0, prompt); + mvaddstr(LINES - 1, 0, prompt); color_set(color, NULL); mbstowcs(wbuffer, input, COLS); - mvaddnwstr(LINES -1, plen, &wbuffer[rover.edit_scroll], maxlen); - mvaddch(LINES -1, plen + MIN(ilen - rover.edit_scroll, maxlen +1), ' '); + mvaddnwstr(LINES - 1, plen, &wbuffer[rover.edit_scroll], maxlen); + mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); color_set(DEFAULT, NULL); if (rover.edit_scroll) - mvaddch(LINES -1, plen -1, '<'); + mvaddch(LINES - 1, plen - 1, '<'); if (ilen > rover.edit_scroll + maxlen) - mvaddch(LINES -1, plen + maxlen, '>'); - move(LINES -1, plen + rover.edit.left - rover.edit_scroll); + mvaddch(LINES - 1, plen + maxlen, '>'); + move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); } /* Show a message on the status bar. */ @@ -75,17 +109,17 @@ void message(Color color, char *fmt, ...) // vsnprintf(buffer, MIN(PATH_MAX, STATUSPOS), fmt, args); vsnprintf(buffer, STATUSPOS, fmt, args); va_end(args); - - pos = (STATUSPOS - (int) strlen(buffer)) /2; + + pos = (STATUSPOS - (int)strlen(buffer)) / 2; attr_on(A_BOLD, NULL); color_set(color, NULL); - mvaddstr(LINES -1, pos, buffer); + mvaddstr(LINES - 1, pos, buffer); color_set(DEFAULT, NULL); attr_off(A_BOLD, NULL); } /* Update the listing view. */ -void update_view() +void update_view(void) { int i, j, numsize, ishidden, marking; char buffer_one[PATH_MAX], buffer_two[PATH_MAX]; @@ -193,8 +227,493 @@ void update_view() wrefresh(rover.window); } -/* Clear message area, leaving only status info. */ -void clear_message() +/* Do a fork-exec to external program (e.g. $EDITOR). */ +static void spawn(char **args) +{ + pid_t pid; + int status; + + setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); + pid = fork(); + if (pid > 0) { + /* fork() succeeded. */ + disable_handlers(); + endwin(); + waitpid(pid, &status, 0); + enable_handlers(); + kill(getpid(), SIGWINCH); + } else if (pid == 0) { + /* Child process. */ + execvp(args[0], args); + } +} + +static int open_with_env(char *program, char *path) +{ + char buffer[PATH_MAX]; + + if (program) { +#ifdef RV_SHELL + strncpy(buffer, program, PATH_MAX - 1); + strncat(buffer, " ", PATH_MAX - strlen(program) - 1); + shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) - 2); + spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); +#else + spawn((char *[]){ program, path, NULL }); +#endif + return 1; + } + + return 0; +} + +static PROCESS deldir = rmdir; + +void main_menu(void) { - mvhline(LINES -1, 0, ' ', STATUSPOS); + int i, ch, oldsel, oldscroll, length, ok, sel; + bool quit = false, isdir; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; + char *program, *dir_name, *bname, *last, first; + const char *clip_path; + ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); + Color color = RED; + FILE *clip_file; + EditStat edit_stat; + struct User prg; + + ROVER_ENV(prg.Shell, SHELL); + ROVER_ENV(prg.Pager, PAGER); + ROVER_ENV(prg.Editor, VISUAL); + if (!(prg.Editor)) + ROVER_ENV(prg.Editor, EDITOR); + ROVER_ENV(prg.Open, OPEN); + + do { + ch = rover_getch(); + mvhline(LINES - 1, 0, ' ', STATUSPOS); + switch (ch) { + case KEY_ESC: //Quit Rover + quit = true; + break; + case '0' ... '9': //Change Tab + rover.tab = ch - '0'; + cd(false); + break; + case '?': //Help + //spawn((char *[]){ "man", "rover", NULL }); + spawn((char *[]){ "man", "./rover.1", NULL }); + break; + case KEY_DOWN: //Move cursor down + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + 1, rover.nfiles - 1); + update_view(); + break; + case KEY_UP: //Move cursor up + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - 1, 0); + update_view(); + break; + case KEY_NPAGE: //Move cursordown 10 lines + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); + if (rover.nfiles > HEIGHT) + SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); + update_view(); + break; + case KEY_PPAGE: //Move cursor up 10 lines + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - RV_JUMP, 0); + SCROLL = MAX(SCROLL - RV_JUMP, 0); + update_view(); + break; + case KEY_HOME: //Move cursor to top of listing + if (!rover.nfiles) + continue; + ESEL = 0; + update_view(); + break; + case KEY_END: //Move cursor to bottom of listing + if (!rover.nfiles) + continue; + ESEL = rover.nfiles - 1; + update_view(); + break; + case KEY_SPACE: //Enter selected directory + if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) + continue; + if (chdir(ENAME(ESEL)) == -1) { + message(RED, "Cannot access \"%s\".", ENAME(ESEL)); + continue; + } + strcat(CWD, ENAME(ESEL)); + cd(true); + break; + case KEY_BACKSPACE: //Go to parent directory + if (!strcmp(CWD, "/")) + continue; + CWD[strlen(CWD) - 1] = '\0'; + dir_name = strrchr(CWD, '/') + 1; + first = dir_name[0]; + dir_name[0] = '\0'; + cd(true); + dir_name[0] = first; + dir_name[strlen(dir_name)] = '/'; + try_to_sel(dir_name); + dir_name[0] = '\0'; + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; + update_view(); + break; + case '\\': //Go to home directory + strcpy(CWD, getenv("HOME")); + ADDSLASH(CWD); + cd(true); + break; + case 't': //Go to the target of the selected link + isdir = S_ISDIR(EMODE(ESEL)); + len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); + if (len == -1) + continue; + buffer_one[len] = '\0'; + if (access(buffer_one, F_OK) == -1) { + char *msg; + switch (errno) { + case EACCES: + msg = "Cannot access \"%s\"."; + break; + case ENOENT: + msg = "\"%s\" does not exist."; + break; + default: + msg = "Cannot navigate to \"%s\"."; + } + strcpy(buffer_two, buffer_one); /* message() uses buffer_one. */ + message(RED, msg, buffer_two); + continue; + } + realpath(buffer_one, CWD); + len = strlen(CWD); + if (CWD[len - 1] == '/') + CWD[len - 1] = '\0'; + bname = strrchr(CWD, '/') + 1; + first = *bname; + *bname = '\0'; + cd(true); + *bname = first; + if (isdir) + ADDSLASH(CWD); + try_to_sel(bname); + *bname = '\0'; + update_view(); + break; + case 'y': //Copy location to clipboard + clip_path = getenv("CLIP"); + if (!clip_path) + goto copy_path_fail; + clip_file = fopen(clip_path, "w"); + if (!clip_file) + goto copy_path_fail; + fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); + fclose(clip_file); + goto copy_path_done; +copy_path_fail: + strcpy(clipboard, CWD); + strcat(clipboard, ENAME(ESEL)); +copy_path_done:; + break; + case 'p': //Go to location in clipboard + clip_path = getenv("CLIP"); + if (!clip_path) + goto paste_path_fail; + clip_file = fopen(clip_path, "r"); + if (!clip_file) + goto paste_path_fail; + fscanf(clip_file, "%s\n", clipboard); + fclose(clip_file); +paste_path_fail: + strcpy(buffer_one, clipboard); + strcpy(CWD, dirname(buffer_one)); + if (strcmp(CWD, "/")) + ADDSLASH(CWD); + cd(true); + strcpy(buffer_one, clipboard); + try_to_sel(strstr(clipboard, basename(buffer_one))); + update_view(); + break; + case 'r': //Refresh directory listing + reload(); + break; + case 's': //Open SHELL on the current directory + program = prg.Shell; + if (program) { +#ifdef RV_SHELL + spawn((char *[]){ RV_SHELL, "-c", program, NULL }); +#else + spawn((char *[]){ program, NULL }); +#endif + reload(); + } + break; + case 'v': //Open PAGER with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Pager, ENAME(ESEL))) + cd(false); + break; + case 'e': //Open VISUAL or EDITOR with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Editor, ENAME(ESEL))) + cd(false); + break; + case 'o': //Open OPEN with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Open, ENAME(ESEL))) + cd(false); + break; + case 'i': //Start incremental search + if (!rover.nfiles) + continue; + oldsel = ESEL; + oldscroll = SCROLL; + start_line_edit(""); + update_input(RVP_SEARCH, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + length = strlen(input); + if (length) { + for (sel = 0; sel < rover.nfiles; sel++) + if (!strncmp(ENAME(sel), input, length)) + break; + if (sel < rover.nfiles) { + color = GREEN; + ESEL = sel; + if (rover.nfiles > HEIGHT) { + if (sel < 3) + SCROLL = 0; + else if (sel - 3 > rover.nfiles - HEIGHT) + SCROLL = rover.nfiles - HEIGHT; + else + SCROLL = sel - 3; + } + } + } else { + ESEL = oldsel; + SCROLL = oldscroll; + } + update_view(); + update_input(RVP_SEARCH, color, input); + } + if (edit_stat == CANCEL) { + ESEL = oldsel; + SCROLL = oldscroll; + } + mvhline(LINES - 1, 0, ' ', STATUSPOS); + update_view(); + break; + case 'F': //Toggle file listing + FLAGS ^= SHOW_FILES; + reload(); + break; + case 'D': //Toggle dir listing + FLAGS ^= SHOW_DIRS; + reload(); + break; + case 'H': //Toggle hide listing + FLAGS ^= SHOW_HIDDEN; + reload(); + break; + case 'n': //Create new file + ok = 0; + start_line_edit(""); + update_input(RVP_NEW_FILE, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); + } + mvhline(LINES - 1, 0, ' ', STATUSPOS); + if (edit_stat == CONFIRM) { + if (ok) { + if (addfile(input) == 0) { + cd(true); + try_to_sel(input); + update_view(); + } else + message(RED, "Could not create \"%s\".", input); + } else + message(RED, "\"%s\" already exists.", input); + } + break; + case 'N': //Create new dir + ok = 0; + start_line_edit(""); + update_input(RVP_NEW_DIR, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + int length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) { + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + } + update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); + } + mvhline(LINES - 1, 0, ' ', STATUSPOS); + if (edit_stat == CONFIRM) { + if (ok) { + if (adddir(input) == 0) { + cd(true); + ADDSLASH(input); + try_to_sel(input); + update_view(); + } else + message(RED, "Could not create \"%s/\".", input); + } else + message(RED, "\"%s\" already exists.", input); + } + break; + case KEY_F(2): //Rename selected file or directory + ok = 0; + strcpy(input, ENAME(ESEL)); + last = input + strlen(input) - 1; + if ((isdir = *last == '/')) + *last = '\0'; + start_line_edit(input); + update_input(RVP_RENAME, RED, input); + while ((edit_stat = get_line_edit()) == CONTINUE) { + length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) + if ( + !strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || + !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + break; + } + update_input(RVP_RENAME, (ok ? GREEN : RED), input); + } + mvhline(LINES - 1, 0, ' ', STATUSPOS); + if (edit_stat == CONFIRM) { + if (isdir) + ADDSLASH(input); + if (ok) { + if (!rename(ENAME(ESEL), input) && MARKED(ESEL)) { + del_mark(&rover.marks, ENAME(ESEL)); + add_mark(&rover.marks, CWD, input); + } + cd(true); + try_to_sel(input); + update_view(); + } else + message(RED, "\"%s\" already exists.", input); + } + break; + case 'E': //Toggle execute permission of the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (S_IXUSR & EMODE(ESEL)) + EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); + else + EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; + if (chmod(ENAME(ESEL), EMODE(ESEL))) { + message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); + } else { + message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); + update_view(); + } + break; + case KEY_DC: //Delete selected file or (empty) directory + if (rover.nfiles) { + message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); + if (rover_getch() == 'Y') { + const char *name = ENAME(ESEL); + int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); + reload(); + if (ret) + message(RED, "Could not delete \"%s\".", ENAME(ESEL)); + } else + mvhline(LINES - 1, 0, ' ', STATUSPOS); + } else + message(RED, "No entry selected for deletion."); + break; + case 'm': //Toggle mark on the selected entry + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + else + add_mark(&rover.marks, CWD, ENAME(ESEL)); + MARKED(ESEL) = !MARKED(ESEL); + ESEL = (ESEL + 1) % rover.nfiles; + update_view(); + break; + case 'M': //Toggle mark on all visible entries + for (i = 0; i < rover.nfiles; i++) { + if (MARKED(i)) + del_mark(&rover.marks, ENAME(i)); + else + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = !MARKED(i); + } + update_view(); + break; + case 'a': //Mark all visible entries + for (i = 0; i < rover.nfiles; i++) + if (!MARKED(i)) { + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = true; + } + update_view(); + break; + case 'X': //Delete all marked entries + if (rover.marks.nentries) { + message(YELLOW, "Delete all marked entries? (Y/n)"); + if (rover_getch() == 'Y') + process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); + else + mvhline(LINES - 1, 0, ' ', STATUSPOS); + } else + message(RED, "No entries marked for deletion."); + break; + case 'C': //Copy all marked entries + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); + else + message(RED, "Cannot copy to the same path."); + } else + message(RED, "No entries marked for copying."); + break; + case 'V': //Move all marked entries + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, movfile, deldir, "Moving", "Moved"); + else + message(RED, "Cannot move to the same path."); + } else + message(RED, "No entries marked for moving."); + break; + + } + } while(!quit); + + return; } \ No newline at end of file diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 27a8461..a0c2da8 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -1,12 +1,29 @@ #ifndef _UI_FUNCS_H #define _UI_FUNCS_H +#define _GNU_SOURCE /* for environ */ #include #include /* setlocale(), LC_ALL */ #include /* PATH_MAX */ +#include +#include /* environ PAGER SHELL EDITOR VISUAL */ #define STATUSPOS (COLS - 16) +/* Shell used to launch external programs. + Defining this macro will force Rover to launch external + programs with `sh -c "$EXTERNAL_PROGRAM [arg]"`. This gives more + flexibility, allowing command-line arguments to be embedded in + environment variables (e.g. PAGER="less -N"). On the other hand, + this requires the presence of a shell and will spawn an additional + process each time an external program is invoked. Leave this macro + undefined if you prefer external programs to be launched with just + `$EXTERNAL_PROGRAM [arg]`. */ +#define RV_SHELL "/bin/sh" + +/* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ +#define RV_JUMP 10 + /* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ #define RVC_CWD GREEN #define RVC_STATUS CYAN @@ -25,6 +42,39 @@ #define RVC_TABNUM DEFAULT #define RVC_MARKS YELLOW +/* KEY not defined by curses.h */ +#ifndef KEY_SPACE +#define KEY_SPACE 32 +#endif +#ifndef KEY_ESC +#define KEY_ESC 27 +#endif +#ifndef KEY_TAB +#define KEY_TAB 9 +#endif +#ifndef KEY_RETURN +#define KEY_RETURN 10 +#endif +#ifndef KEY_CTRL_DEL +#define KEY_CTRL_DEL 519 +#endif +#ifndef KEY_CTRL_BS +#define KEY_CTRL_BS 8 +#endif +#ifndef KEY_CTRL_LEFT +#define KEY_CTRL_LEFT 545 +#endif +#ifndef KEY_CTRL_RIGHT +#define KEY_CTRL_RIGHT 560 +#endif + +/* Get user programs from the environment vars */ +#define ROVER_ENV(dst, src) \ + { \ + if ((dst = getenv("ROVER_" #src)) == NULL) \ + dst = getenv(#src); \ + } + typedef enum Color { DEFAULT, RED, @@ -37,11 +87,18 @@ typedef enum Color { BLACK } Color; +struct User { + char *Shell; + char *Pager; + char *Editor; + char *Open; +}; + // Function declarations -void init_term(); +void init_term(void); void update_input(const char *prompt, Color color, const char *input); void message(Color color, char *fmt, ...); -void update_view(); -void clear_message(); +void update_view(void); +void main_menu(void); #endif // _UI_FUNCS_H \ No newline at end of file From 57eebfc82b6945e120795532c95ddcc66f551ea1 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Dec 2022 20:27:15 +0100 Subject: [PATCH 08/18] a lot of improvments deleted all call to GOTO modify rover.1 organized better functions TODO delete file manager --- rover.1 | 18 +- src/config.h | 38 +--- src/main.c | 21 ++ src/rover.c | 323 +----------------------------- src/ui_funcs.c | 532 +++++++++++++++++++++++++++++++++++++------------ src/ui_funcs.h | 23 ++- 6 files changed, 465 insertions(+), 490 deletions(-) diff --git a/rover.1 b/rover.1 index ccd9756..6d0eae1 100755 --- a/rover.1 +++ b/rover.1 @@ -78,22 +78,22 @@ another tab that is pointed to the "destination" directory. .B ESC Quit rover. .TP -.B arrow up/down +.B Arrow up/down Move cursor up/down. .TP .B Page up/down Move cursor up/down 10 lines. .TP -.B g/G -Move cursor to top/bottom of listing. +.B HOME/END +Move cursor to top/bottom of the list. .TP -.B l +.B Arrow Right Enter selected directory. .TP -.B h +.B Arrow Left Go to parent directory. .TP -.B H +.B h Go to \fB$HOME\fR directory. .TP .B t @@ -123,19 +123,19 @@ Open \fB$OPEN\fR with the selected file. .B / Start incremental search. .TP -.B f/d/s +.B F/D/H Toggle file/directory/hidden listing. .TP .B n/N Create new file/directory. .TP -.B R +.B F2 Rename selected file or directory. .TP .B E Toggle execute permission of the selected file. .TP -.B D +.B CANC Delete selected file or (empty) directory. .TP .B m diff --git a/src/config.h b/src/config.h index 58c3a9e..f9de997 100755 --- a/src/config.h +++ b/src/config.h @@ -18,7 +18,6 @@ #include /* setlocale(), LC_ALL */ #include /* chdir(), getcwd(), read(), close(), ... */ #include /* DIR, struct dirent, opendir(), ... */ -#include #include #include /* open() */ #include /* waitpid() */ @@ -136,20 +135,6 @@ extern struct Rover rover; #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define ISDIR(E) (strchr((E), '/') != NULL) -/* Line Editing Macros. */ -#define EDIT_FULL(E) ((E).left == (E).right) -#define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX - 1) -#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] -#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] -#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) -#define EDIT_BACKSPACE(E) (E).left-- -#define EDIT_DELETE(E) (E).right++ -#define EDIT_CLEAR(E) \ - { \ - (E).left = 0; \ - (E).right = PATH_MAX - 1; \ - } /* Add / at the end of path */ #define ADDSLASH(path) \ { \ @@ -163,43 +148,22 @@ extern struct Rover rover; free((p)); \ (p) = NULL; \ } -typedef enum EditStat { - CONTINUE, - CONFIRM, - CANCEL -} EditStat; typedef int (*PROCESS)(const char *path); // FUnction declarations -void init_marks(Marks *marks); -void mark_none(Marks *marks); -void add_mark(Marks *marks, char *dirpath, char *entry); -void del_mark(Marks *marks, char *entry); -void free_marks(Marks *marks); -void reload(); +void reload(void); void sync_signals(); -int rover_getch(); -int rover_get_wch(wint_t *wch); -void shell_escaped_cat(char *buf, char *str, size_t n); void update_view(); int rowcmp(const void *a, const void *b); int ls(Row **rowsp, uint8_t flags); void free_rows(Row **rowsp, int nfiles); void cd(bool reset); void try_to_sel(const char *target); -void reload(); off_t count_dir(const char *path); off_t count_marked(); -int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path); -void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done); void update_progress(off_t delta); -int delfile(const char *path); -int addfile(const char *path); int cpyfile(const char *srcpath); -int adddir(const char *path); int movfile(const char *srcpath); -void start_line_edit(const char *init_input); -EditStat get_line_edit(); #endif // _CONFIG_H \ No newline at end of file diff --git a/src/main.c b/src/main.c index d45653f..44833ce 100755 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,27 @@ struct Rover rover; +static void init_marks(Marks *marks) +{ + strcpy(marks->dirpath, ""); + marks->bulk = BULK_INIT; + marks->nentries = 0; + marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); +} + +static void free_marks(Marks *marks) +{ + int i; + + for (i = 0; marks->nentries && i < marks->bulk; i++) + if (marks->entries[i]) { + FREE(marks->entries[i]); + marks->nentries--; + } + + FREE(marks->entries); +} + int main(int argc, char *argv[]) { int i; diff --git a/src/rover.c b/src/rover.c index 85c4bcf..bfcf0f2 100755 --- a/src/rover.c +++ b/src/rover.c @@ -1,91 +1,6 @@ #include "config.h" #include "ui_funcs.h" -void init_marks(Marks *marks) -{ - strcpy(marks->dirpath, ""); - marks->bulk = BULK_INIT; - marks->nentries = 0; - marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); -} - -/* Unmark all entries. */ -void mark_none(Marks *marks) -{ - int i; - - strcpy(marks->dirpath, ""); - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - FREE(marks->entries[i]); - marks->nentries--; - } - if (marks->bulk > BULK_THRESH) { - /* Reset bulk to free some memory. */ - FREE(marks->entries); - marks->bulk = BULK_INIT; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); - } -} - -void add_mark(Marks *marks, char *dirpath, char *entry) -{ - int i; - - if (!strcmp(marks->dirpath, dirpath)) { - /* Append mark to directory. */ - if (marks->nentries == marks->bulk) { - /* Expand bulk to accomodate new entry. */ - int extra = marks->bulk / 2; - marks->bulk += extra; /* bulk *= 1.5; */ - marks->entries = realloc(marks->entries, - marks->bulk * sizeof *marks->entries); - memset(&marks->entries[marks->nentries], 0, - extra * sizeof *marks->entries); - i = marks->nentries; - } else { - /* Search for empty slot (there must be one). */ - for (i = 0; i < marks->bulk; i++) - if (!marks->entries[i]) - break; - } - } else { - /* Directory changed. Discard old marks. */ - mark_none(marks); - strcpy(marks->dirpath, dirpath); - i = 0; - } - marks->entries[i] = malloc(strlen(entry) + 1); - strcpy(marks->entries[i], entry); - marks->nentries++; -} - -void del_mark(Marks *marks, char *entry) -{ - int i; - - if (marks->nentries > 1) { - for (i = 0; i < marks->bulk; i++) - if (marks->entries[i] && !strcmp(marks->entries[i], entry)) - break; - FREE(marks->entries[i]); - marks->nentries--; - } else - mark_none(marks); -} - -void free_marks(Marks *marks) -{ - int i; - - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - FREE(marks->entries[i]); - marks->nentries--; - } - FREE(marks->entries); -} - /* Handle any signals received since last call. */ void sync_signals() { @@ -109,54 +24,6 @@ void sync_signals() } } -/* This function must be used in place of getch(). - It handles signals while waiting for user input. */ -int rover_getch() -{ - int ch; - - while ((ch = getch()) == ERR) - sync_signals(); - - return ch; -} - -/* This function must be used in place of get_wch(). - It handles signals while waiting for user input. */ -int rover_get_wch(wint_t *wch) -{ - wint_t ret; - - while ((ret = get_wch(wch)) == (wint_t)ERR) - sync_signals(); - return ret; -} - -void shell_escaped_cat(char *buf, char *str, size_t n) -{ - char *p = buf + strlen(buf); - *p++ = '\''; - for (n--; n; n--, str++) { - switch (*str) { - case '\'': - if (n < 4) - goto done; - strcpy(p, "'\\''"); - n -= 4; - p += 4; - break; - case '\0': - goto done; - default: - *p = *str; - p++; - } - } -done: - strncat(p, "'", n); -} - - /* Comparison used to sort listing entries. */ int rowcmp(const void *a, const void *b) { @@ -354,123 +221,19 @@ off_t count_marked() return total; } -/* Recursively process a source directory using CWD as destination root. - For each node (i.e. directory), do the following: - 1. call pre(destination); - 2. call proc() on every child leaf (i.e. files); - 3. recurse into every child node; - 4. call pos(source). - E.g. to move directory /src/ (and all its contents) inside /dst/: - strcpy(CWD, "/dst/"); - process_dir(adddir, movfile, deldir, "/src/"); */ -int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) -{ - int ret; - DIR *dp; - struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - - ret = 0; - if (pre) { - char dstpath[PATH_MAX]; - strcpy(dstpath, CWD); - strcat(dstpath, path + strlen(rover.marks.dirpath)); - ret |= pre(dstpath); - } - if (!(dp = opendir(path))) - return -1; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - ADDSLASH(subpath); - ret |= process_dir(pre, proc, pos, subpath); - } else - ret |= proc(subpath); - } - closedir(dp); - if (pos) - ret |= pos(path); - return ret; -} - -/* Process all marked entries using CWD as destination root. - All marked entries that are directories will be recursively processed. - See process_dir() for details on the parameters. */ -void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) -{ - int i, ret; - char *entry; - char path[PATH_MAX]; - - mvhline(LINES - 1, 0, ' ', STATUSPOS); - message(CYAN, "%s...", msg_doing); - refresh(); - rover.prog = (Prog){ 0, count_marked(), msg_doing }; - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - ret = 0; - snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); - if (ISDIR(entry)) { - if (!strncmp(path, CWD, strlen(path))) - ret = -1; - else - ret = process_dir(pre, proc, pos, path); - } else - ret = proc(path); - if (!ret) { - del_mark(&rover.marks, entry); - reload(); - } - } - } - rover.prog.total = 0; - reload(); - if (!rover.marks.nentries) - message(GREEN, "%s all marked entries.", msg_done); - else - message(RED, "Some errors occured while %s.", msg_doing); - RV_ALERT(); -} - void update_progress(off_t delta) { int percent; if (!rover.prog.total) return; + rover.prog.partial += delta; percent = (int)(rover.prog.partial * 100 / rover.prog.total); message(CYAN, "%s...%d%%", rover.prog.msg, percent); refresh(); -} -/* Wrappers for file operations. */ -int delfile(const char *path) -{ - int ret; - struct stat st; - - ret = lstat(path, &st); - if (ret < 0) - return ret; - update_progress(st.st_size); - return unlink(path); -} - -int addfile(const char *path) -{ - /* Using creat(2) because mknod(2) doesn't seem to be portable. */ - int ret; - - ret = creat(path, 0644); - if (ret < 0) - return ret; - return close(ret); + return; } int cpyfile(const char *srcpath) @@ -510,17 +273,6 @@ int cpyfile(const char *srcpath) return ret; } -int adddir(const char *path) -{ - int ret; - struct stat st; - - ret = stat(CWD, &st); - if (ret < 0) - return ret; - return mkdir(path, st.st_mode); -} - int movfile(const char *srcpath) { int ret; @@ -543,74 +295,3 @@ int movfile(const char *srcpath) } return ret; } - -void start_line_edit(const char *init_input) -{ - char input[PATH_MAX]; - - curs_set(TRUE); - strncpy(input, init_input, PATH_MAX); - rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); - rover.edit.right = PATH_MAX - 1; - rover.edit.buffer[PATH_MAX] = L'\0'; - rover.edit_scroll = 0; -} - -/* Read input and change editing state accordingly. */ -EditStat get_line_edit() -{ - wchar_t eraser, killer, wch; - int ret, length; - char input[PATH_MAX]; - - ret = rover_get_wch((wint_t *)&wch); - erasewchar(&eraser); - killwchar(&killer); - if (ret == KEY_CODE_YES) { - if (wch == KEY_ENTER) { - curs_set(FALSE); - return CONFIRM; - } else if (wch == KEY_LEFT) { - if (EDIT_CAN_LEFT(rover.edit)) - EDIT_LEFT(rover.edit); - } else if (wch == KEY_RIGHT) { - if (EDIT_CAN_RIGHT(rover.edit)) - EDIT_RIGHT(rover.edit); - } else if (wch == KEY_UP) { - while (EDIT_CAN_LEFT(rover.edit)) - EDIT_LEFT(rover.edit); - } else if (wch == KEY_DOWN) { - while (EDIT_CAN_RIGHT(rover.edit)) - EDIT_RIGHT(rover.edit); - } else if (wch == KEY_BACKSPACE) { - if (EDIT_CAN_LEFT(rover.edit)) - EDIT_BACKSPACE(rover.edit); - } else if (wch == KEY_DC) { - if (EDIT_CAN_RIGHT(rover.edit)) - EDIT_DELETE(rover.edit); - } - } else { - if (wch == L'\r' || wch == L'\n') { - curs_set(FALSE); - return CONFIRM; - } else if (wch == L'\t') { - curs_set(FALSE); - return CANCEL; - } else if (wch == eraser) { - if (EDIT_CAN_LEFT(rover.edit)) - EDIT_BACKSPACE(rover.edit); - } else if (wch == killer) { - EDIT_CLEAR(rover.edit); - mvhline(LINES - 1, 0, ' ', STATUSPOS); - } else if (iswprint(wch)) { - if (!EDIT_FULL(rover.edit)) - EDIT_INSERT(rover.edit, wch); - } - } - /* Encode edit contents in input. */ - rover.edit.buffer[rover.edit.left] = L'\0'; - length = wcstombs(input, rover.edit.buffer, PATH_MAX); - wcstombs(&input[length], &rover.edit.buffer[rover.edit.right + 1], - PATH_MAX - length); - return CONTINUE; -} diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 0f6f1e1..cc36e3c 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -11,28 +11,54 @@ static void handle_winch(int sig) rover.pending_winch = 1; } -static void enable_handlers(void) +static void handlers(bool enable) { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); - - sa.sa_handler = handle_usr1; - sigaction(SIGUSR1, &sa, NULL); - - sa.sa_handler = handle_winch; - sigaction(SIGWINCH, &sa, NULL); + + if (enable) { + sa.sa_handler = handle_usr1; + sigaction(SIGUSR1, &sa, NULL); + + sa.sa_handler = handle_winch; + sigaction(SIGWINCH, &sa, NULL); + } else { + sa.sa_handler = SIG_DFL; + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGWINCH, &sa, NULL); + } + return; } -static void disable_handlers(void) +/* Update line input on the screen. */ + +static void update_input(const char *prompt, Color color, const char *input) { - struct sigaction sa; + int plen, ilen, maxlen; + wchar_t wbuffer[PATH_MAX]; - memset(&sa, 0, sizeof(struct sigaction)); - - sa.sa_handler = SIG_DFL; - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); + plen = strlen(prompt); + ilen = mbstowcs(NULL, input, 0); + maxlen = STATUSPOS - plen - 2; + if ((ilen - rover.edit_scroll) < maxlen) + rover.edit_scroll = MAX(ilen - maxlen, 0); + else if (rover.edit.left > (rover.edit_scroll + maxlen - 1)) + rover.edit_scroll = rover.edit.left - maxlen; + else if (rover.edit.left < rover.edit_scroll) + rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); + color_set(RVC_PROMPT, NULL); + mvaddstr(LINES - 1, 0, prompt); + color_set(color, NULL); + mbstowcs(wbuffer, input, COLS); + mvaddnwstr(LINES - 1, plen, &wbuffer[rover.edit_scroll], maxlen); + mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); + color_set(DEFAULT, NULL); + if (rover.edit_scroll) + mvaddch(LINES - 1, plen - 1, '<'); + if (ilen > (rover.edit_scroll + maxlen)) + mvaddch(LINES - 1, plen + maxlen, '>'); + move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); } /* Curses setup. */ @@ -66,36 +92,7 @@ void init_term(void) init_pair(BLACK, COLOR_BLACK, bg); } atexit((void (*)(void))endwin); - enable_handlers(); -} - -/* Update line input on the screen. */ -void update_input(const char *prompt, Color color, const char *input) -{ - int plen, ilen, maxlen; - wchar_t wbuffer[PATH_MAX]; - - plen = strlen(prompt); - ilen = mbstowcs(NULL, input, 0); - maxlen = STATUSPOS - plen - 2; - if (ilen - rover.edit_scroll < maxlen) - rover.edit_scroll = MAX(ilen - maxlen, 0); - else if (rover.edit.left > rover.edit_scroll + maxlen - 1) - rover.edit_scroll = rover.edit.left - maxlen; - else if (rover.edit.left < rover.edit_scroll) - rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); - color_set(RVC_PROMPT, NULL); - mvaddstr(LINES - 1, 0, prompt); - color_set(color, NULL); - mbstowcs(wbuffer, input, COLS); - mvaddnwstr(LINES - 1, plen, &wbuffer[rover.edit_scroll], maxlen); - mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); - color_set(DEFAULT, NULL); - if (rover.edit_scroll) - mvaddch(LINES - 1, plen - 1, '<'); - if (ilen > rover.edit_scroll + maxlen) - mvaddch(LINES - 1, plen + maxlen, '>'); - move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); + handlers(true); //enable handlers } /* Show a message on the status bar. */ @@ -121,9 +118,10 @@ void message(Color color, char *fmt, ...) /* Update the listing view. */ void update_view(void) { - int i, j, numsize, ishidden, marking; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX]; + int i, j, numsize, ishidden, marking, length, namecols; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; wchar_t wbuffer[PATH_MAX]; + off_t human_size; mvhline(0, 0, ' ', COLS); attr_on(A_BOLD, NULL); @@ -178,18 +176,17 @@ void update_view(void) if (ISLINK(j)) wcscat(wbuffer, L"/"); } else { - char *suffix, *suffixes = "BKMGTPEZY"; - off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); - int namecols = wcswidth(wbuffer, length); - for (suffix = suffixes; human_size >= 10240; suffix++) + human_size = ESIZE(j) * 10; + length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + namecols = wcswidth(wbuffer, length); + for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) human_size = (human_size + 512) / 1024; if (*suffix == 'B') - swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", + swprintf(wbuffer + length, PATH_MAX - length, L"%*d Bytes", (int)(COLS - namecols - 6), - (int)human_size / 10, *suffix); + (int)human_size / 10); else - swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %cb", (int)(COLS - namecols - 8), (int)human_size / 10, (int)human_size % 10, *suffix); } @@ -237,16 +234,37 @@ static void spawn(char **args) pid = fork(); if (pid > 0) { /* fork() succeeded. */ - disable_handlers(); + handlers(false); //disable handlers endwin(); waitpid(pid, &status, 0); - enable_handlers(); + handlers(true); //enable handlers kill(getpid(), SIGWINCH); } else if (pid == 0) { /* Child process. */ execvp(args[0], args); } } +static void shell_escaped_cat(char *buf, char *str, size_t n) +{ + char *p = buf + strlen(buf); + + *p++ = '\''; + for (n--; n; n--, str++) { + if (*str == '\'') { + if (n < 4) + break; + strcpy(p, "'\\''"); + n -= 4; + p += 4; + } else if (*str == '\0') + break; + else { + *p = *str; + p++; + } + } + strncat(p, "'", n); +} static int open_with_env(char *program, char *path) { @@ -256,7 +274,7 @@ static int open_with_env(char *program, char *path) #ifdef RV_SHELL strncpy(buffer, program, PATH_MAX - 1); strncat(buffer, " ", PATH_MAX - strlen(program) - 1); - shell_escaped_cat(buffer, path, PATH_MAX - strlen(program) - 2); + shell_escaped_cat(buffer, path, (size_t)PATH_MAX - strlen(program) - 2); spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); #else spawn((char *[]){ program, path, NULL }); @@ -267,6 +285,282 @@ static int open_with_env(char *program, char *path) return 0; } +static void start_line_edit(const char *init_input) +{ + char input[PATH_MAX]; + + curs_set(TRUE); + strncpy(input, init_input, PATH_MAX); + rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); + rover.edit.right = PATH_MAX - 1; + rover.edit.buffer[PATH_MAX] = L'\0'; //buffuer lenght is defined with PATH_MAX +1 + rover.edit_scroll = 0; +} + +/* This function must be used in place of getch(). + It handles signals while waiting for user input. */ + static int rover_getch(void) +{ + int ch; + + keypad(rover.window, true); + + while ((ch = getch()) == ERR) + sync_signals(); + + keypad(rover.window, false); + + return ch; +} + +/* Read input and change editing state accordingly. */ +static EditStat get_line_edit(char *string) +{ + int ch, length; + + ch = rover_getch(); + switch (ch) { + case '\r': + case '\n': + curs_set(FALSE); + return CONFIRM; + break; + case KEY_ESC: + curs_set(FALSE); + return CANCEL; + break; + case KEY_LEFT: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_RIGHT: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_HOME: + case KEY_UP: + while (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_END: + case KEY_DOWN: + while (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_BACKSPACE: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_BACKSPACE(rover.edit); + break; + case KEY_DC: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_DELETE(rover.edit); + break; + case (KEY_CANCEL): + EDIT_CLEAR(rover.edit); + mvhline(LINES - 1, 0, ' ', STATUSPOS); + default: + if (iswprint(ch) && !EDIT_FULL(rover.edit)) + EDIT_INSERT(rover.edit, ch); + break; + } + + //Encode edit contents in input. + rover.edit.buffer[rover.edit.left] = L'\0'; + length = wcstombs(string, rover.edit.buffer, PATH_MAX); + wcstombs(&string[length], &rover.edit.buffer[rover.edit.right + 1], PATH_MAX - length); + + return CONTINUE; +} + +static int addfile(const char *path) +{ /* Using creat(2) because mknod(2) doesn't seem to be portable. */ + int ret; + + ret = creat(path, 0644); + if (ret < 0) + return ret; + + return close(ret); +} + +static int adddir(const char *path) +{ + int ret; + struct stat st; + + ret = stat(CWD, &st); + if (ret < 0) + return ret; + + return mkdir(path, st.st_mode); +} + +/* Unmark all entries. */ +static void mark_none(Marks *marks) +{ + int i; + + strcpy(marks->dirpath, ""); + for (i = 0; marks->nentries && i < marks->bulk; i++) + if (marks->entries[i]) { + FREE(marks->entries[i]); + marks->nentries--; + } + + if (marks->bulk > BULK_THRESH) { + /* Reset bulk to free some memory. */ + FREE(marks->entries); + marks->bulk = BULK_INIT; + marks->entries = (char **)calloc(marks->bulk, sizeof *marks->entries); + } +} + +static void add_mark(Marks *marks, char *dirpath, char *entry) +{ + int i, extra; + + if (!strcmp(marks->dirpath, dirpath)) { + /* Append mark to directory. */ + if (marks->nentries == marks->bulk) { + /* Expand bulk to accomodate new entry. */ + extra = marks->bulk / 2; + marks->bulk += extra; /* bulk *= 1.5; */ + marks->entries = (char **) realloc(marks->entries, marks->bulk * sizeof(*marks->entries)); + memset(&marks->entries[marks->nentries], 0, extra * sizeof *marks->entries); + i = marks->nentries; + } else { + /* Search for empty slot (there must be one). */ + for (i = 0; i < marks->bulk; i++) + if (!marks->entries[i]) + break; + } + } else { + /* Directory changed. Discard old marks. */ + mark_none(marks); + strcpy(marks->dirpath, dirpath); + i = 0; + } + + marks->entries[i] = (char *) malloc(strlen(entry) + 1); + strcpy(marks->entries[i], entry); + marks->nentries++; +} + +void del_mark(Marks *marks, char *entry) +{ + int i; + + if (marks->nentries > 1) { + for (i = 0; i < marks->bulk; i++) + if (marks->entries[i] && !strcmp(marks->entries[i], entry)) + break; + + FREE(marks->entries[i]); + marks->nentries--; + } else + mark_none(marks); +} + +/* Recursively process a source directory using CWD as destination root. + For each node (i.e. directory), do the following: + 1. call pre(destination); + 2. call proc() on every child leaf (i.e. files); + 3. recurse into every child node; + 4. call pos(source). + E.g. to move directory /src/ (and all its contents) inside /dst/: + strcpy(CWD, "/dst/"); + process_dir(adddir, movfile, deldir, "/src/"); */ +static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) +{ + int ret; + DIR *dp; + struct dirent *ep; + struct stat statbuf; + char subpath[PATH_MAX], dstpath[PATH_MAX]; + + ret = 0; + if (pre) { + strcpy(dstpath, CWD); + strcat(dstpath, path + strlen(rover.marks.dirpath)); + ret |= pre(dstpath); + } + + if (!(dp = opendir(path))) + return -1; + + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); + lstat(subpath, &statbuf); + if (S_ISDIR(statbuf.st_mode)) { + ADDSLASH(subpath); + ret |= process_dir(pre, proc, pos, subpath); + } else + ret |= proc(subpath); + } + closedir(dp); + if (pos) + ret |= pos(path); + + return ret; +} + +/* Process all marked entries using CWD as destination root. + All marked entries that are directories will be recursively processed. + See process_dir() for details on the parameters. */ +static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) +{ + int i, ret; + char *entry; + char path[PATH_MAX]; + + mvhline(LINES - 1, 0, ' ', STATUSPOS); + message(CYAN, "%s...", msg_doing); + refresh(); + rover.prog = (Prog){ 0, count_marked(), msg_doing }; + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) { + ret = 0; + snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); + if (ISDIR(entry)) { + if (!strncmp(path, CWD, strlen(path))) + ret = -1; + else + ret = process_dir(pre, proc, pos, path); + } else + ret = proc(path); + if (!ret) { + del_mark(&rover.marks, entry); + reload(); + } + } + } + rover.prog.total = 0; + reload(); + if (!rover.marks.nentries) + message(GREEN, "%s all marked entries.", msg_done); + else + message(RED, "Some errors occured while %s.", msg_doing); + RV_ALERT(); +} + +/* Wrappers for file operations. */ +static int delfile(const char *path) +{ + int ret; + struct stat st; + + ret = lstat(path, &st); + if (ret < 0) + return ret; + + update_progress(st.st_size); + + return unlink(path); +} + static PROCESS deldir = rmdir; void main_menu(void) @@ -274,7 +568,7 @@ void main_menu(void) int i, ch, oldsel, oldscroll, length, ok, sel; bool quit = false, isdir; char buffer_one[PATH_MAX], buffer_two[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; - char *program, *dir_name, *bname, *last, first; + char *program, *dir_name, *bname, *last, *msg, first; const char *clip_path; ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); Color color = RED; @@ -301,8 +595,11 @@ void main_menu(void) cd(false); break; case '?': //Help - //spawn((char *[]){ "man", "rover", NULL }); - spawn((char *[]){ "man", "./rover.1", NULL }); + if (access("./rover.1", R_OK)) + bname = "rover"; //default man path + else + bname = "./rover.1"; + spawn((char *[]){ "man", bname, NULL }); break; case KEY_DOWN: //Move cursor down if (!rover.nfiles) @@ -343,7 +640,7 @@ void main_menu(void) ESEL = rover.nfiles - 1; update_view(); break; - case KEY_SPACE: //Enter selected directory + case KEY_RIGHT: //Enter selected directory if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue; if (chdir(ENAME(ESEL)) == -1) { @@ -353,7 +650,7 @@ void main_menu(void) strcat(CWD, ENAME(ESEL)); cd(true); break; - case KEY_BACKSPACE: //Go to parent directory + case KEY_LEFT: //Go to parent directory if (!strcmp(CWD, "/")) continue; CWD[strlen(CWD) - 1] = '\0'; @@ -369,7 +666,7 @@ void main_menu(void) SCROLL = ESEL - HEIGHT / 2; update_view(); break; - case '\\': //Go to home directory + case 'h': //Go to home directory strcpy(CWD, getenv("HOME")); ADDSLASH(CWD); cd(true); @@ -381,7 +678,6 @@ void main_menu(void) continue; buffer_one[len] = '\0'; if (access(buffer_one, F_OK) == -1) { - char *msg; switch (errno) { case EACCES: msg = "Cannot access \"%s\"."; @@ -413,29 +709,25 @@ void main_menu(void) break; case 'y': //Copy location to clipboard clip_path = getenv("CLIP"); - if (!clip_path) - goto copy_path_fail; - clip_file = fopen(clip_path, "w"); - if (!clip_file) - goto copy_path_fail; - fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); - fclose(clip_file); - goto copy_path_done; -copy_path_fail: - strcpy(clipboard, CWD); - strcat(clipboard, ENAME(ESEL)); -copy_path_done:; + if (clip_path) { + clip_file = fopen(clip_path, "w"); + if (clip_file) { + fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); + fclose(clip_file); + } + } + if (!clip_path || !clip_file) + sprintf(clipboard, "%s%s", CWD, ENAME(ESEL)); break; case 'p': //Go to location in clipboard clip_path = getenv("CLIP"); - if (!clip_path) - goto paste_path_fail; - clip_file = fopen(clip_path, "r"); - if (!clip_file) - goto paste_path_fail; - fscanf(clip_file, "%s\n", clipboard); - fclose(clip_file); -paste_path_fail: + if (clip_path) { + clip_file = fopen(clip_path, "r"); + if (clip_file) { + fscanf(clip_file, "%s\n", clipboard); + fclose(clip_file); + } + } strcpy(buffer_one, clipboard); strcpy(CWD, dirname(buffer_one)); if (strcmp(CWD, "/")) @@ -484,7 +776,7 @@ copy_path_done:; oldscroll = SCROLL; start_line_edit(""); update_input(RVP_SEARCH, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { + while ((edit_stat = get_line_edit(input)) == CONTINUE) { length = strlen(input); if (length) { for (sel = 0; sel < rover.nfiles; sel++) @@ -532,16 +824,14 @@ copy_path_done:; ok = 0; start_line_edit(""); update_input(RVP_NEW_FILE, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(input); - ok = length; + while ((edit_stat = get_line_edit(input)) == CONTINUE) { + length = strlen(input); + ok = length; for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { + if (!strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { ok = 0; - break; + i = rover.nfiles; //in order to exit from nested loop without using break; } } update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); @@ -563,16 +853,14 @@ copy_path_done:; ok = 0; start_line_edit(""); update_input(RVP_NEW_DIR, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(input); - ok = length; + while ((edit_stat = get_line_edit(input)) == CONTINUE) { + length = strlen(input); + ok = length; for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { + if (!strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { ok = 0; - break; + i = rover.nfiles; //in order to exit from nested loop without using break; } } update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); @@ -595,20 +883,19 @@ copy_path_done:; ok = 0; strcpy(input, ENAME(ESEL)); last = input + strlen(input) - 1; - if ((isdir = *last == '/')) + isdir = *last; + if (*last == '/') *last = '\0'; start_line_edit(input); update_input(RVP_RENAME, RED, input); - while ((edit_stat = get_line_edit()) == CONTINUE) { + while ((edit_stat = get_line_edit(input)) == CONTINUE) { length = strlen(input); ok = length; for (i = 0; i < rover.nfiles; i++) - if ( - !strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/"))) { + if (!strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { ok = 0; - break; + i = rover.nfiles; //in order to exit from nested loop without using break; } update_input(RVP_RENAME, (ok ? GREEN : RED), input); } @@ -646,11 +933,13 @@ copy_path_done:; if (rover.nfiles) { message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); if (rover_getch() == 'Y') { - const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); + bname = ENAME(ESEL); + ok = ISDIR(ENAME(ESEL)) ? deldir(bname) : delfile(bname); reload(); - if (ret) + if (ok) message(RED, "Could not delete \"%s\".", ENAME(ESEL)); + else + message(YELLOW, "\"%s\" deleted!", bname); } else mvhline(LINES - 1, 0, ' ', STATUSPOS); } else @@ -702,18 +991,17 @@ copy_path_done:; } else message(RED, "No entries marked for copying."); break; - case 'V': //Move all marked entries - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); - else - message(RED, "Cannot move to the same path."); - } else - message(RED, "No entries marked for moving."); - break; - + case 'V': //Move all marked entries + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, movfile, deldir, "Moving", "Moved"); + else + message(RED, "Cannot move to the same path."); + } else + message(RED, "No entries marked for moving."); + break; } - } while(!quit); + } while (!quit); return; } \ No newline at end of file diff --git a/src/ui_funcs.h b/src/ui_funcs.h index a0c2da8..274c19b 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -7,6 +7,7 @@ #include /* PATH_MAX */ #include #include /* environ PAGER SHELL EDITOR VISUAL */ +#include /* dirname() */ #define STATUSPOS (COLS - 16) @@ -68,6 +69,21 @@ #define KEY_CTRL_RIGHT 560 #endif +/* Line Editing Macros. */ +#define EDIT_FULL(E) ((E).left == (E).right) +#define EDIT_CAN_LEFT(E) ((E).left) +#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX - 1) +#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] +#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] +#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) +#define EDIT_BACKSPACE(E) (E).left-- +#define EDIT_DELETE(E) (E).right++ +#define EDIT_CLEAR(E) \ + { \ + (E).left = 0; \ + (E).right = PATH_MAX - 1; \ + } + /* Get user programs from the environment vars */ #define ROVER_ENV(dst, src) \ { \ @@ -94,9 +110,14 @@ struct User { char *Open; }; +typedef enum EditStat { + CONTINUE, + CONFIRM, + CANCEL +} EditStat; + // Function declarations void init_term(void); -void update_input(const char *prompt, Color color, const char *input); void message(Color color, char *fmt, ...); void update_view(void); void main_menu(void); From 78920925c33987a72b4dc1831f3ce4e25089814c Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Jan 2023 12:32:54 +0100 Subject: [PATCH 09/18] some improvments renamed config.h in rover.h add os_funcs.c and os_funcs.h replace delfile() and deldir() with rm() --- .vscode/settings.json | 10 - .vscode/tasks.json | 2 +- Makefile | 2 +- README.md | 2 +- src/main.c | 44 +++- src/os_funcs.c | 115 +++++++++ src/os_funcs.h | 28 +++ src/rover.c | 218 ++++++++++++++--- src/{config.h => rover.h} | 42 +++- src/ui_funcs.c | 487 +++++++++++++++----------------------- src/ui_funcs.h | 14 +- 11 files changed, 603 insertions(+), 361 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 src/os_funcs.c create mode 100644 src/os_funcs.h rename src/{config.h => rover.h} (79%) mode change 100755 => 100644 diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 8ed888d..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "files.associations": { - "unistd.h": "c", - "stdlib.h": "c", - "stdarg.h": "c", - "locale.h": "c", - "environments.h": "c", - "unistd_ext.h": "c" - } -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 25d5521..e538adc 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,7 +12,7 @@ "-O0", "-ggdb", "-D_DEFAULT_SOURCE", // pkg-config --cflags ncursesw - /* "-D_XOPEN_SOURCE=600", already defined in config.h */// pkg-config --cflags ncursesw + /* "-D_XOPEN_SOURCE=600", already defined in rover.h */ // pkg-config --cflags ncursesw "${workspaceFolder}/src/*c", "-o", "${workspaceFolder}/rover", diff --git a/Makefile b/Makefile index c2890fe..2e428cd 100755 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LIBS_NCURSESW := `$(PKG_CONFIG) --libs ncursesw` all: rover -rover: rover.c config.h +rover: rover.c rover.h $(CC) $(CFLAGS) $(CFLAGS_NCURSESW) -o $@ $< $(LDFLAGS) $(LIBS_NCURSESW) install: rover diff --git a/README.md b/README.md index 40c09ed..4353700 100755 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Configuration ============= Rover configuration (mostly key bindings and colors) can only be changed by -editing the file `config.h` and rebuilding the binary. +editing the file `rover.h` and rebuilding the binary. Note that the external programs executed by some Rover commands may be changed via the appropriate environment variables. For example, to specify an editor: diff --git a/src/main.c b/src/main.c index 44833ce..d10c612 100755 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,42 @@ -#include "config.h" #include "ui_funcs.h" +#include "rover.h" struct Rover rover; +char rover_home_path[RV_PATH_MAX]; + +/* Curses setup. */ +static void init_term(void) +{ + setlocale(LC_ALL, ""); + initscr(); + cbreak(); /* Get one character at a time. */ + timeout(100); /* For getch(). */ + noecho(); + nonl(); /* No NL->CR/NL on output. */ + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); /* Hide blinking cursor. */ + if (has_colors()) { + short bg; + start_color(); +#ifdef NCURSES_EXT_FUNCS + use_default_colors(); + bg = -1; +#else + bg = COLOR_BLACK; +#endif + init_pair(RED, COLOR_RED, bg); + init_pair(GREEN, COLOR_GREEN, bg); + init_pair(YELLOW, COLOR_YELLOW, bg); + init_pair(BLUE, COLOR_BLUE, bg); + init_pair(CYAN, COLOR_CYAN, bg); + init_pair(MAGENTA, COLOR_MAGENTA, bg); + init_pair(WHITE, COLOR_WHITE, bg); + init_pair(BLACK, COLOR_BLACK, bg); + } + atexit((void (*)(void))endsession); + handlers(true); //enable handlers +} static void init_marks(Marks *marks) { @@ -72,6 +107,9 @@ int main(int argc, char *argv[]) } } + strcpy(clipboard, argv[0]); + strcpy(rover_home_path, dirname(clipboard)); //get the home directory of rover + init_term(); rover.nfiles = 0; for (i = 0; i < 10; i++) { @@ -122,6 +160,6 @@ int main(int argc, char *argv[]) fclose(save_marks_file); } free_marks(&rover.marks); - - return 0; + + return EXIT_SUCCESS; } diff --git a/src/os_funcs.c b/src/os_funcs.c new file mode 100644 index 0000000..6a5a179 --- /dev/null +++ b/src/os_funcs.c @@ -0,0 +1,115 @@ +#include "rover.h" +#include "os_funcs.h" +#include "ui_funcs.h" + +mode_t filetype(const char *filename) +{ + struct stat sb; + + if (lstat(filename, &sb) == -1) { + return false; + } + + return (sb.st_mode & S_IFMT); +} + +/* Wrappers for file operations. */ +int rm(const char *fname) +{ + int result; + char *errnomsg; + + errno = 0; + result = remove(fname); + switch (errno) { + case EACCES: + errnomsg = "Write access to the file or directory is not allowed for the process's effective UID, or one of the directories in pathname did not allow search permission."; + break; + case EBUSY: + errnomsg = "The file or directory cannot be unlinked because it is being used by the system or another process that prevents its removal."; + break; + case EFAULT: + errnomsg = "The file or directory points outside your accessible address space."; + break; + case EIO: + errnomsg = "An I/O error occurred."; + break; + case ELOOP: + errnomsg = "Too many symbolic links were encountered in translating."; + break; + case ENAMETOOLONG: + errnomsg = "pathname was too long."; + break; + case ENOENT: + errnomsg = "A component of path does not exist or is a dangling symbolic link, or pathname is empty."; + break; + case ENOMEM: + errnomsg = "Insufficient kernel memory was available."; + break; + case ENOTDIR: + errnomsg = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EPERM: + errnomsg = "The system does not allow unlinking of directories, or unlinking of directories requires privileges that the calling process doesn't have."; + break; + case EROFS: + errnomsg = "The file or directory refers to a file on a read-only filesystem."; + break; + case EBADF: + errnomsg = "pathname is relative but dirfd is neither AT_FDCWD nor a valid file descriptor."; + break; + case EINVAL: + errnomsg = "An invalid flag value was specified in flags."; + break; + case EISDIR: + errnomsg = "pathname refers to a directory, and AT_REMOVEDIR was not specified in flags."; + break; + default: + errnomsg = "Generic error not specified by OS."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Error removing \"%s\" errno[%d]: %s", fname, errno, errnomsg); //write detailed error message to log file + + return result; +} + +bool isvalidfilename(const char *filename, bool wildcard) +{ + mode_t st_mode; + char dir_template[] = "/tmp/rover-tmpdir.XXXXXX", forbidden[] = "/<>\"|:&", extmatch[] = "?*+@!", temp_file[FILENAME_MAX], *temp_dir = NULL; + FILE *fd; + + if (strpbrk(filename, forbidden)) //check for invalid characters for filename + return false; + if (strlen(filename) > 255) //check if the lenght of file exceed the max acceppted by filesystem + return false; + if (strpbrk(filename, extmatch)) { //check if filname contains wildchars FNM_EXTMATCH + if(wildcard) + return true; + + return false; + } + temp_dir = mkdtemp(dir_template); + if (temp_dir == NULL) + return false; + if (!mkdir(temp_dir, S_IRWXU | S_IRWXG | S_IRWXO)) //creating temp dir 0777 + return false; + + sprintf(temp_file, "%s/%s", temp_dir, filename); + fd = fopen(temp_file, "w"); //try to create an empty file using "filename" + if (fd == NULL) { + rm(temp_dir); + + return false; + } + fclose(fd); + + st_mode = filetype(temp_file); //check if the tempo file is regular + if (rm(temp_file)) + return false; + if (rm(temp_dir)) + return false; + + return (st_mode == S_IFREG); +} diff --git a/src/os_funcs.h b/src/os_funcs.h new file mode 100644 index 0000000..4e64ce2 --- /dev/null +++ b/src/os_funcs.h @@ -0,0 +1,28 @@ +#ifndef _OS_FUNCS_H +#define _OS_FUNCS_H + +#include /* lstat() mkdir() */ +#include /* mode_t */ +#include +#include /* mkdtemp() */ +#include /* sprintf() fopen() fclose() */ +#include /* remove() */ +#include +#include + +/* +returns the file type with mask of & S_IFMT +*/ +mode_t filetype(const char *filename); +/* +remove file from disk +return 0 if success or -1 in case of error +*/ +int rm(const char *fname); +/* +check if the specified name is a valid file name +return true if is valid or false if is not valid +*/ +bool isvalidfilename(const char *filename, bool wildcard); + +#endif // _OS_FUNCS_H \ No newline at end of file diff --git a/src/rover.c b/src/rover.c index bfcf0f2..0f8eb57 100755 --- a/src/rover.c +++ b/src/rover.c @@ -1,8 +1,162 @@ -#include "config.h" +#include "rover.h" #include "ui_funcs.h" +void logfile(const char *format, ...) +{ + char filelog[PATH_MAX]; + FILE *fd; + time_t current_time; + struct tm *local; + static unsigned int logcount = 0; // each session start from 0 + va_list args; + + sprintf(filelog, "%s/%s.log", rover_home_path, ROVER); + fd = fopen(filelog, "a+"); // a+ create or append that is useful in a log file + if (!fd) { + fprintf(stderr, "ERROR APPEND/WRITE LOG FILE\n"); + sleep(3); + + return; + } + + time(¤t_time); // get time + local = localtime(¤t_time); // convert to local + fprintf(fd, "%d/%02d/%02d ", local->tm_year + 1900, local->tm_mon + 1, local->tm_mday); // write current date yyyy/mm/dd + fprintf(fd, "%02d:%02d:%02d ", local->tm_hour, local->tm_min, local->tm_sec); // write local time hh:mm:ss + fprintf(fd, "[%03u] ", logcount++); // write the index log of current session + + va_start(args, format); + vfprintf(fd, format, args); // write the info recieved from function arguments + va_end(args); + + fclose(fd); + + return; +} + +/* atexit func */ +int endsession(void) +{ + handlers(false); + curs_set(TRUE); + keypad(stdscr, TRUE); + nl(); + echo(); + timeout(0); + endwin(); + + return EXIT_SUCCESS; +} + +void update_view() +{ + int i, j, numsize, ishidden, marking, length, namecols, center, height; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; + wchar_t wbuffer[PATH_MAX]; + off_t human_size; + + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, buffer_one); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window + shrinking. In that case, the scroll must be moved to make it visible. */ + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = ENAME(j)[0] == '.'; + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + if (S_ISDIR(EMODE(j))) { + mbstowcs(wbuffer, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(wbuffer, L"/"); + } else { + human_size = ESIZE(j) * 10; + length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + namecols = wcswidth(wbuffer, length); + for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + if (*suffix == 'B') + swprintf(wbuffer + length, PATH_MAX - length, L"%*d Bytes", + (int) (COLS - namecols - 6), + (int) human_size / 10); + else + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %cb", + (int) (COLS - namecols - 8), + (int) human_size / 10, (int) human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); + } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + if (rover.nfiles > HEIGHT) { + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT-1) * HEIGHT / rover.nfiles; + if (!height) + height = 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height); + } + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + if (!rover.nfiles) + strcpy(buffer_two, "0/0"); + else + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + snprintf((buffer_one+3), (PATH_MAX-3), "%12s", buffer_two); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); + wrefresh(rover.window); +} + /* Handle any signals received since last call. */ -void sync_signals() +void sync_signals(void) { if (rover.pending_usr1) { /* SIGUSR1 received: refresh directory listing. */ @@ -28,12 +182,13 @@ void sync_signals() int rowcmp(const void *a, const void *b) { int isdir1, isdir2, cmpdir; - const Row *r1 = a; - const Row *r2 = b; - isdir1 = S_ISDIR(r1->mode); + const Row *r1 = a, *r2 = b; + + isdir1 = S_ISDIR(r1->mode); isdir2 = S_ISDIR(r2->mode); cmpdir = isdir2 - isdir1; - return cmpdir ? cmpdir : strcoll(r1->name, r2->name); + + return (cmpdir ? cmpdir : strcoll(r1->name, r2->name)); } /* Get all entries in current working directory. */ @@ -110,36 +265,32 @@ void cd(bool reset) message(CYAN, "Loading \"%s\"...", CWD); refresh(); + if (chdir(CWD) == -1) { getcwd(CWD, PATH_MAX - 1); ADDSLASH(CWD); - - mvhline(LINES - 1, 0, ' ', STATUSPOS); - update_view(); - - return; - } - - if (reset) - ESEL = SCROLL = 0; - - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - - rover.nfiles = ls(&rover.rows, FLAGS); - if (!strcmp(CWD, rover.marks.dirpath)) { - for (i = 0; i < rover.nfiles; i++) { - for (j = 0; j < rover.marks.bulk; j++) - if (rover.marks.entries[j] && !strcmp(rover.marks.entries[j], ENAME(i))) - break; - - MARKED(i) = j < rover.marks.bulk; + } else { + if (reset) + ESEL = SCROLL = 0; + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + + rover.nfiles = ls(&rover.rows, FLAGS); + if (!strcmp(CWD, rover.marks.dirpath)) { + for (i = 0; i < rover.nfiles; i++) { + for (j = 0; j < rover.marks.bulk; j++) { + if (rover.marks.entries[j] && + !strcmp(rover.marks.entries[j], ENAME(i))) + break; + } + MARKED(i) = j < rover.marks.bulk; + } + } else { + for (i = 0; i < rover.nfiles; i++) + MARKED(i) = false; } - } else - for (i = 0; i < rover.nfiles; i++) - MARKED(i) = false; - - mvhline(LINES - 1, 0, ' ', STATUSPOS); + } + CLEAR_MESSAGE(); update_view(); return; @@ -194,6 +345,7 @@ off_t count_dir(const char *path) total += statbuf.st_size; } closedir(dp); + return total; } @@ -218,6 +370,7 @@ off_t count_marked() } } chdir(CWD); + return total; } @@ -270,6 +423,7 @@ int cpyfile(const char *srcpath) close(dst); ret = 0; } + return ret; } diff --git a/src/config.h b/src/rover.h old mode 100755 new mode 100644 similarity index 79% rename from src/config.h rename to src/rover.h index f9de997..f07e17f --- a/src/config.h +++ b/src/rover.h @@ -1,11 +1,15 @@ -#ifndef _CONFIG_H -#define _CONFIG_H +#ifndef _ROVER_H +#define _ROVER_H #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif +#ifndef _XOPEN_SOURCE_EXTENDED #define _XOPEN_SOURCE_EXTENDED +#endif +#ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 +#endif #include #include @@ -26,6 +30,7 @@ #include #include #include +#include #define RV_VERSION "2.0.1" @@ -117,8 +122,6 @@ struct Rover { Tab tabs[10]; }; -extern struct Rover rover; - /* Macros for accessing global state. */ #define ENAME(I) rover.rows[I].name #define ESIZE(I) rover.rows[I].size @@ -141,6 +144,12 @@ extern struct Rover rover; if (path[strlen(path) - 1] != '/') \ strcat(path, "/"); \ } + +#define DELSLASH(path) \ + { \ + if (path[strlen(path) - 1] == '/') \ + path[strlen(path) - 1] = '\0'; \ + } /* Safe version of free() don't need assign NULL after free */ #define FREE(p) \ { \ @@ -149,21 +158,36 @@ extern struct Rover rover; (p) = NULL; \ } +#define ROVER "rover" +#define LOG_INFO false +#define LOG_ERR true +#define RV_PATH_MAX (PATH_MAX - 16) + +#define LOG(flag, msg, ...) \ + { \ + logfile("{%s} %s %s() <%d>: " msg "\n", (flag ? "ERR" : "INFO"), basename(__FILE__), __func__, __LINE__, ##__VA_ARGS__); \ + } + typedef int (*PROCESS)(const char *path); -// FUnction declarations +extern struct Rover rover; +extern char rover_home_path[RV_PATH_MAX]; + +// Functions declaration +void logfile(const char *format, ...); +int endsession(void); void reload(void); -void sync_signals(); -void update_view(); +void sync_signals(void); +void update_view(void); int rowcmp(const void *a, const void *b); int ls(Row **rowsp, uint8_t flags); void free_rows(Row **rowsp, int nfiles); void cd(bool reset); void try_to_sel(const char *target); off_t count_dir(const char *path); -off_t count_marked(); +off_t count_marked(void); void update_progress(off_t delta); int cpyfile(const char *srcpath); int movfile(const char *srcpath); -#endif // _CONFIG_H \ No newline at end of file +#endif // _ROVER_H \ No newline at end of file diff --git a/src/ui_funcs.c b/src/ui_funcs.c index cc36e3c..3687e68 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -1,17 +1,18 @@ -#include "config.h" +#include "rover.h" #include "ui_funcs.h" +#include "os_funcs.h" -static void handle_usr1(int sig) +void handle_usr1(int sig) { rover.pending_usr1 = 1; } -static void handle_winch(int sig) +void handle_winch(int sig) { rover.pending_winch = 1; } -static void handlers(bool enable) +void handlers(bool enable) { struct sigaction sa; @@ -28,11 +29,9 @@ static void handlers(bool enable) sigaction(SIGUSR1, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); } - return; } /* Update line input on the screen. */ - static void update_input(const char *prompt, Color color, const char *input) { int plen, ilen, maxlen; @@ -47,6 +46,7 @@ static void update_input(const char *prompt, Color color, const char *input) rover.edit_scroll = rover.edit.left - maxlen; else if (rover.edit.left < rover.edit_scroll) rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); + color_set(RVC_PROMPT, NULL); mvaddstr(LINES - 1, 0, prompt); color_set(color, NULL); @@ -61,40 +61,6 @@ static void update_input(const char *prompt, Color color, const char *input) move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); } -/* Curses setup. */ -void init_term(void) -{ - setlocale(LC_ALL, ""); - initscr(); - cbreak(); /* Get one character at a time. */ - timeout(100); /* For getch(). */ - noecho(); - nonl(); /* No NL->CR/NL on output. */ - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - curs_set(FALSE); /* Hide blinking cursor. */ - if (has_colors()) { - short bg; - start_color(); -#ifdef NCURSES_EXT_FUNCS - use_default_colors(); - bg = -1; -#else - bg = COLOR_BLACK; -#endif - init_pair(RED, COLOR_RED, bg); - init_pair(GREEN, COLOR_GREEN, bg); - init_pair(YELLOW, COLOR_YELLOW, bg); - init_pair(BLUE, COLOR_BLUE, bg); - init_pair(CYAN, COLOR_CYAN, bg); - init_pair(MAGENTA, COLOR_MAGENTA, bg); - init_pair(WHITE, COLOR_WHITE, bg); - init_pair(BLACK, COLOR_BLACK, bg); - } - atexit((void (*)(void))endwin); - handlers(true); //enable handlers -} - /* Show a message on the status bar. */ void message(Color color, char *fmt, ...) { @@ -115,115 +81,6 @@ void message(Color color, char *fmt, ...) attr_off(A_BOLD, NULL); } -/* Update the listing view. */ -void update_view(void) -{ - int i, j, numsize, ishidden, marking, length, namecols; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; - wchar_t wbuffer[PATH_MAX]; - off_t human_size; - - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, buffer_one); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(wbuffer, CWD, PATH_MAX); - mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window - shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = ENAME(j)[0] == '.'; - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - if (S_ISDIR(EMODE(j))) { - mbstowcs(wbuffer, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(wbuffer, L"/"); - } else { - human_size = ESIZE(j) * 10; - length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); - namecols = wcswidth(wbuffer, length); - for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - if (*suffix == 'B') - swprintf(wbuffer + length, PATH_MAX - length, L"%*d Bytes", - (int)(COLS - namecols - 6), - (int)human_size / 10); - else - swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %cb", - (int)(COLS - namecols - 8), - (int)human_size / 10, (int)human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - if (rover.nfiles > HEIGHT) { - int center, height; - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT - 1) * HEIGHT / rover.nfiles; - if (!height) - height = 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); - } - buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - if (!rover.nfiles) - strcpy(buffer_two, "0/0"); - else - snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, buffer_one); - wrefresh(rover.window); -} - /* Do a fork-exec to external program (e.g. $EDITOR). */ static void spawn(char **args) { @@ -244,6 +101,7 @@ static void spawn(char **args) execvp(args[0], args); } } + static void shell_escaped_cat(char *buf, char *str, size_t n) { char *p = buf + strlen(buf); @@ -299,16 +157,16 @@ static void start_line_edit(const char *init_input) /* This function must be used in place of getch(). It handles signals while waiting for user input. */ - static int rover_getch(void) +static int rover_getch(void) { int ch; - keypad(rover.window, true); + keypad(rover.window, TRUE); while ((ch = getch()) == ERR) sync_signals(); - keypad(rover.window, false); + keypad(rover.window, FALSE); return ch; } @@ -320,51 +178,51 @@ static EditStat get_line_edit(char *string) ch = rover_getch(); switch (ch) { - case '\r': - case '\n': - curs_set(FALSE); - return CONFIRM; - break; - case KEY_ESC: - curs_set(FALSE); - return CANCEL; - break; - case KEY_LEFT: - if (EDIT_CAN_LEFT(rover.edit)) - EDIT_LEFT(rover.edit); - break; - case KEY_RIGHT: - if (EDIT_CAN_RIGHT(rover.edit)) - EDIT_RIGHT(rover.edit); - break; - case KEY_HOME: - case KEY_UP: - while (EDIT_CAN_LEFT(rover.edit)) - EDIT_LEFT(rover.edit); - break; - case KEY_END: - case KEY_DOWN: - while (EDIT_CAN_RIGHT(rover.edit)) - EDIT_RIGHT(rover.edit); - break; - case KEY_BACKSPACE: - if (EDIT_CAN_LEFT(rover.edit)) - EDIT_BACKSPACE(rover.edit); - break; - case KEY_DC: - if (EDIT_CAN_RIGHT(rover.edit)) - EDIT_DELETE(rover.edit); - break; - case (KEY_CANCEL): - EDIT_CLEAR(rover.edit); - mvhline(LINES - 1, 0, ' ', STATUSPOS); - default: - if (iswprint(ch) && !EDIT_FULL(rover.edit)) - EDIT_INSERT(rover.edit, ch); - break; + case KEY_ENTER: + case KEY_RETURN: + curs_set(FALSE); + return CONFIRM; + break; + case KEY_ESC: + curs_set(FALSE); + return CANCEL; + break; + case KEY_LEFT: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_RIGHT: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_HOME: + case KEY_UP: + while (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_END: + case KEY_DOWN: + while (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_BACKSPACE: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_BACKSPACE(rover.edit); + break; + case KEY_DC: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_DELETE(rover.edit); + break; + case (KEY_CANCEL): + EDIT_CLEAR(rover.edit); + CLEAR_MESSAGE(); + default: + if (iswprint(ch) && !EDIT_FULL(rover.edit)) + EDIT_INSERT(rover.edit, ch); + break; } - //Encode edit contents in input. + //Encode edit contents in input. rover.edit.buffer[rover.edit.left] = L'\0'; length = wcstombs(string, rover.edit.buffer, PATH_MAX); wcstombs(&string[length], &rover.edit.buffer[rover.edit.right + 1], PATH_MAX - length); @@ -373,7 +231,7 @@ static EditStat get_line_edit(char *string) } static int addfile(const char *path) -{ /* Using creat(2) because mknod(2) doesn't seem to be portable. */ +{ /* Using creat(2) because mknod(2) doesn't seem to be portable. */ int ret; ret = creat(path, 0644); @@ -425,7 +283,7 @@ static void add_mark(Marks *marks, char *dirpath, char *entry) /* Expand bulk to accomodate new entry. */ extra = marks->bulk / 2; marks->bulk += extra; /* bulk *= 1.5; */ - marks->entries = (char **) realloc(marks->entries, marks->bulk * sizeof(*marks->entries)); + marks->entries = (char **)realloc(marks->entries, marks->bulk * sizeof(*marks->entries)); memset(&marks->entries[marks->nentries], 0, extra * sizeof *marks->entries); i = marks->nentries; } else { @@ -441,7 +299,7 @@ static void add_mark(Marks *marks, char *dirpath, char *entry) i = 0; } - marks->entries[i] = (char *) malloc(strlen(entry) + 1); + marks->entries[i] = (char *)malloc(strlen(entry) + 1); strcpy(marks->entries[i], entry); marks->nentries++; } @@ -469,7 +327,7 @@ void del_mark(Marks *marks, char *entry) 4. call pos(source). E.g. to move directory /src/ (and all its contents) inside /dst/: strcpy(CWD, "/dst/"); - process_dir(adddir, movfile, deldir, "/src/"); */ + process_dir(adddir, movfile, rm, "/src/"); */ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) { int ret; @@ -487,7 +345,7 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) if (!(dp = opendir(path))) return -1; - + while ((ep = readdir(dp))) { if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; @@ -499,7 +357,7 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) } else ret |= proc(subpath); } - closedir(dp); + closedir(dp); if (pos) ret |= pos(path); @@ -515,7 +373,7 @@ static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *m char *entry; char path[PATH_MAX]; - mvhline(LINES - 1, 0, ' ', STATUSPOS); + CLEAR_MESSAGE(); message(CYAN, "%s...", msg_doing); refresh(); rover.prog = (Prog){ 0, count_marked(), msg_doing }; @@ -546,31 +404,14 @@ static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *m RV_ALERT(); } -/* Wrappers for file operations. */ -static int delfile(const char *path) -{ - int ret; - struct stat st; - - ret = lstat(path, &st); - if (ret < 0) - return ret; - - update_progress(st.st_size); - - return unlink(path); -} - -static PROCESS deldir = rmdir; - void main_menu(void) { int i, ch, oldsel, oldscroll, length, ok, sel; bool quit = false, isdir; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; + char buffer_one[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; char *program, *dir_name, *bname, *last, *msg, first; const char *clip_path; - ssize_t len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); + ssize_t link; Color color = RED; FILE *clip_file; EditStat edit_stat; @@ -585,21 +426,21 @@ void main_menu(void) do { ch = rover_getch(); - mvhline(LINES - 1, 0, ' ', STATUSPOS); + CLEAR_MESSAGE(); switch (ch) { case KEY_ESC: //Quit Rover - quit = true; + message(RED, "Press any key to exit or ESC to back"); + if (rover_getch() != KEY_ESC) + quit = true; + CLEAR_MESSAGE(); break; case '0' ... '9': //Change Tab rover.tab = ch - '0'; cd(false); break; case '?': //Help - if (access("./rover.1", R_OK)) - bname = "rover"; //default man path - else - bname = "./rover.1"; - spawn((char *[]){ "man", bname, NULL }); + sprintf(buffer_one, "%s/%s.1", rover_home_path, ROVER); // local manfile + spawn((char *[]){ "man", access(buffer_one, R_OK) ? ROVER : buffer_one, NULL }); break; case KEY_DOWN: //Move cursor down if (!rover.nfiles) @@ -653,13 +494,15 @@ void main_menu(void) case KEY_LEFT: //Go to parent directory if (!strcmp(CWD, "/")) continue; - CWD[strlen(CWD) - 1] = '\0'; - dir_name = strrchr(CWD, '/') + 1; - first = dir_name[0]; - dir_name[0] = '\0'; + //length = strlen(CWD) - 1; + //CWD[length] = '\0'; + DELSLASH(CWD); + dir_name = strrchr(CWD, '/') + 1; + first = dir_name[0]; + dir_name[0] = '\0'; cd(true); - dir_name[0] = first; - dir_name[strlen(dir_name)] = '/'; + dir_name[0] = first; + ADDSLASH(dir_name); try_to_sel(dir_name); dir_name[0] = '\0'; if (rover.nfiles > HEIGHT) @@ -673,10 +516,11 @@ void main_menu(void) break; case 't': //Go to the target of the selected link isdir = S_ISDIR(EMODE(ESEL)); - len = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); - if (len == -1) - continue; - buffer_one[len] = '\0'; + link = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); + if (link == -1) + continue; + buffer_one[link] = '\0'; + errno = 0; if (access(buffer_one, F_OK) == -1) { switch (errno) { case EACCES: @@ -688,14 +532,13 @@ void main_menu(void) default: msg = "Cannot navigate to \"%s\"."; } - strcpy(buffer_two, buffer_one); /* message() uses buffer_one. */ - message(RED, msg, buffer_two); + message(RED, msg, buffer_one); continue; } realpath(buffer_one, CWD); - len = strlen(CWD); - if (CWD[len - 1] == '/') - CWD[len - 1] = '\0'; + link = strlen(CWD); + if (CWD[link - 1] == '/') + CWD[link - 1] = '\0'; bname = strrchr(CWD, '/') + 1; first = *bname; *bname = '\0'; @@ -805,7 +648,7 @@ void main_menu(void) ESEL = oldsel; SCROLL = oldscroll; } - mvhline(LINES - 1, 0, ' ', STATUSPOS); + CLEAR_MESSAGE(); update_view(); break; case 'F': //Toggle file listing @@ -821,68 +664,100 @@ void main_menu(void) reload(); break; case 'n': //Create new file - ok = 0; start_line_edit(""); - update_input(RVP_NEW_FILE, RED, input); - while ((edit_stat = get_line_edit(input)) == CONTINUE) { - length = strlen(input); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if (!strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { + DELSLASH(input); + update_input(RVP_NEW_FILE, YELLOW, input); + do { + edit_stat = get_line_edit(input); + ok = strlen(input); + for (i = 0; ok && i < rover.nfiles; i++) { + if (!strcmp(ENAME(i), input)) { ok = 0; - i = rover.nfiles; //in order to exit from nested loop without using break; + update_input(RVP_NEW_FILE, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" already exists!", input); + rover_getch(); + CLEAR_MESSAGE(); + curs_set(TRUE); + edit_stat = CONTINUE; + } + } else if (!isvalidfilename(input, false)) { + ok = 0; + update_input(RVP_NEW_FILE, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" invalid filename!", input); + rover_getch(); + CLEAR_MESSAGE(); + curs_set(TRUE); + edit_stat = CONTINUE; + } } } update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); - } - mvhline(LINES - 1, 0, ' ', STATUSPOS); - if (edit_stat == CONFIRM) { - if (ok) { - if (addfile(input) == 0) { - cd(true); - try_to_sel(input); - update_view(); - } else - message(RED, "Could not create \"%s\".", input); + } while (edit_stat == CONTINUE); + + CLEAR_MESSAGE(); + if (ok && edit_stat == CONFIRM) { + if (addfile(input) == 0) { + cd(true); + try_to_sel(input); + update_view(); } else - message(RED, "\"%s\" already exists.", input); + message(RED, "Could not create \"%s\".", input); } break; case 'N': //Create new dir - ok = 0; start_line_edit(""); - update_input(RVP_NEW_DIR, RED, input); - while ((edit_stat = get_line_edit(input)) == CONTINUE) { - length = strlen(input); - ok = length; + DELSLASH(input); + update_input(RVP_NEW_DIR, YELLOW, input); + do { + edit_stat = get_line_edit(input); + ok = strlen(input); for (i = 0; i < rover.nfiles; i++) { - if (!strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { + if (!strncmp(ENAME(i), input, length)) { + ok = 0; + update_input(RVP_NEW_DIR, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" already exists!", input); + rover_getch(); + CLEAR_MESSAGE(); + curs_set(TRUE); + edit_stat = CONTINUE; + } + } else if (!isvalidfilename(input, false)) { ok = 0; - i = rover.nfiles; //in order to exit from nested loop without using break; + update_input(RVP_NEW_DIR, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" invalid dirname!", input); + rover_getch(); + CLEAR_MESSAGE(); + curs_set(TRUE); + edit_stat = CONTINUE; + } } } update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); - } - mvhline(LINES - 1, 0, ' ', STATUSPOS); - if (edit_stat == CONFIRM) { - if (ok) { - if (adddir(input) == 0) { - cd(true); - ADDSLASH(input); - try_to_sel(input); - update_view(); - } else - message(RED, "Could not create \"%s/\".", input); + } while (edit_stat == CONTINUE); + + CLEAR_MESSAGE(); + if (ok && edit_stat == CONFIRM) { + if (adddir(input) == 0) { + cd(true); + ADDSLASH(input); + try_to_sel(input); + update_view(); } else - message(RED, "\"%s\" already exists.", input); + message(RED, "Could not create \"%s/\".", input); } break; case KEY_F(2): //Rename selected file or directory ok = 0; strcpy(input, ENAME(ESEL)); - last = input + strlen(input) - 1; + last = input + strlen(input) - 1; isdir = *last; if (*last == '/') *last = '\0'; @@ -893,13 +768,13 @@ void main_menu(void) ok = length; for (i = 0; i < rover.nfiles; i++) if (!strncmp(ENAME(i), input, length) && - (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { + (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { ok = 0; - i = rover.nfiles; //in order to exit from nested loop without using break; + i = rover.nfiles; //in order to exit from nested loop without using break; } update_input(RVP_RENAME, (ok ? GREEN : RED), input); } - mvhline(LINES - 1, 0, ' ', STATUSPOS); + CLEAR_MESSAGE(); if (edit_stat == CONFIRM) { if (isdir) ADDSLASH(input); @@ -933,17 +808,27 @@ void main_menu(void) if (rover.nfiles) { message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); if (rover_getch() == 'Y') { - bname = ENAME(ESEL); - ok = ISDIR(ENAME(ESEL)) ? deldir(bname) : delfile(bname); + strcpy(buffer_one, ENAME(ESEL)); + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + ok = rm(buffer_one); reload(); - if (ok) - message(RED, "Could not delete \"%s\".", ENAME(ESEL)); - else - message(YELLOW, "\"%s\" deleted!", bname); - } else - mvhline(LINES - 1, 0, ' ', STATUSPOS); - } else - message(RED, "No entry selected for deletion."); + if (ok == 0) { + CLEAR_MESSAGE(); + message(YELLOW, "\"%s\" deleted!", buffer_one); + rover_getch(); + } else { + CLEAR_MESSAGE(); + message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer_one, ROVER); //print error message + rover_getch(); + CLEAR_MESSAGE(); + } + } + CLEAR_MESSAGE(); + } else { + message(RED, "No entry for deletion."); + rover_getch(); + } break; case 'm': //Toggle mark on the selected entry if (MARKED(ESEL)) @@ -976,9 +861,9 @@ void main_menu(void) if (rover.marks.nentries) { message(YELLOW, "Delete all marked entries? (Y/n)"); if (rover_getch() == 'Y') - process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); + process_marked(NULL, rm, rm, "Deleting", "Deleted"); else - mvhline(LINES - 1, 0, ' ', STATUSPOS); + CLEAR_MESSAGE(); } else message(RED, "No entries marked for deletion."); break; @@ -994,7 +879,7 @@ void main_menu(void) case 'V': //Move all marked entries if (rover.marks.nentries) { if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); + process_marked(adddir, movfile, rm, "Moving", "Moved"); else message(RED, "Cannot move to the same path."); } else diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 274c19b..1f57371 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -1,7 +1,10 @@ #ifndef _UI_FUNCS_H #define _UI_FUNCS_H -#define _GNU_SOURCE /* for environ */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* for asprintf() environ */ +#endif + #include #include /* setlocale(), LC_ALL */ #include /* PATH_MAX */ @@ -54,7 +57,7 @@ #define KEY_TAB 9 #endif #ifndef KEY_RETURN -#define KEY_RETURN 10 +#define KEY_RETURN '\r' #endif #ifndef KEY_CTRL_DEL #define KEY_CTRL_DEL 519 @@ -91,6 +94,9 @@ dst = getenv(#src); \ } +/* Clear message line */ +#define CLEAR_MESSAGE() mvhline(LINES - 1, 0, ' ', STATUSPOS) + typedef enum Color { DEFAULT, RED, @@ -117,7 +123,9 @@ typedef enum EditStat { } EditStat; // Function declarations -void init_term(void); +void handle_usr1(int sig); +void handle_winch(int sig); +void handlers(bool enable); void message(Color color, char *fmt, ...); void update_view(void); void main_menu(void); From 526c1a8ab2da002665e3e890a9e4ef7f52cf470c Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Jan 2023 23:39:33 +0100 Subject: [PATCH 10/18] improve some funcs open_with_env() improved shell_escaped_cat() removed removed cors_set() where not necessary --- .gitignore | 1 + src/main.c | 5 ++++- src/rover.c | 17 ++++++++++++----- src/ui_funcs.c | 43 ++++++++----------------------------------- 4 files changed, 25 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 087824b..46ee909 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ rover +rover.log diff --git a/src/main.c b/src/main.c index d10c612..1bb050b 100755 --- a/src/main.c +++ b/src/main.c @@ -9,16 +9,19 @@ static void init_term(void) { setlocale(LC_ALL, ""); initscr(); + cbreak(); /* Get one character at a time. */ - timeout(100); /* For getch(). */ + nodelay(stdscr, TRUE); /* For getch(). */ noecho(); nonl(); /* No NL->CR/NL on output. */ intrflush(stdscr, FALSE); keypad(stdscr, TRUE); curs_set(FALSE); /* Hide blinking cursor. */ + if (has_colors()) { short bg; start_color(); + #ifdef NCURSES_EXT_FUNCS use_default_colors(); bg = -1; diff --git a/src/rover.c b/src/rover.c index 0f8eb57..32eda24 100755 --- a/src/rover.c +++ b/src/rover.c @@ -38,11 +38,18 @@ void logfile(const char *format, ...) int endsession(void) { handlers(false); + delwin(rover.window); +/* curs_set(TRUE); - keypad(stdscr, TRUE); + keypad(stdscr, FALSE); + intrflush(stdscr, TRUE); nl(); echo(); - timeout(0); + nodelay(stdscr, FALSE); + nocbreak(); + refresh(); + */ + clear(); endwin(); return EXIT_SUCCESS; @@ -149,7 +156,7 @@ void update_view() strcpy(buffer_two, "0/0"); else snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); - snprintf((buffer_one+3), (PATH_MAX-3), "%12s", buffer_two); + snprintf(buffer_one+3, PATH_MAX, "%12s", buffer_two); color_set(RVC_STATUS, NULL); mvaddstr(LINES - 1, STATUSPOS, buffer_one); wrefresh(rover.window); @@ -168,9 +175,9 @@ void sync_signals(void) delwin(rover.window); endwin(); refresh(); - clear(); rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles) + if (HEIGHT < rover.nfiles && + SCROLL + HEIGHT > rover.nfiles) SCROLL = ESEL - HEIGHT; update_view(); diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 3687e68..efc3680 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -69,7 +69,6 @@ void message(Color color, char *fmt, ...) char buffer[PATH_MAX]; va_start(args, fmt); - // vsnprintf(buffer, MIN(PATH_MAX, STATUSPOS), fmt, args); vsnprintf(buffer, STATUSPOS, fmt, args); va_end(args); @@ -102,37 +101,13 @@ static void spawn(char **args) } } -static void shell_escaped_cat(char *buf, char *str, size_t n) -{ - char *p = buf + strlen(buf); - - *p++ = '\''; - for (n--; n; n--, str++) { - if (*str == '\'') { - if (n < 4) - break; - strcpy(p, "'\\''"); - n -= 4; - p += 4; - } else if (*str == '\0') - break; - else { - *p = *str; - p++; - } - } - strncat(p, "'", n); -} - static int open_with_env(char *program, char *path) { char buffer[PATH_MAX]; if (program) { #ifdef RV_SHELL - strncpy(buffer, program, PATH_MAX - 1); - strncat(buffer, " ", PATH_MAX - strlen(program) - 1); - shell_escaped_cat(buffer, path, (size_t)PATH_MAX - strlen(program) - 2); + snprintf(buffer, PATH_MAX, "%s \'%s\'", program, path); spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); #else spawn((char *[]){ program, path, NULL }); @@ -161,13 +136,9 @@ static int rover_getch(void) { int ch; - keypad(rover.window, TRUE); - while ((ch = getch()) == ERR) sync_signals(); - keypad(rover.window, FALSE); - return ch; } @@ -196,11 +167,13 @@ static EditStat get_line_edit(char *string) EDIT_RIGHT(rover.edit); break; case KEY_HOME: + case KEY_PPAGE: case KEY_UP: while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit); break; case KEY_END: + case KEY_NPAGE: case KEY_DOWN: while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit); @@ -213,9 +186,13 @@ static EditStat get_line_edit(char *string) if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit); break; - case (KEY_CANCEL): + case KEY_CANCEL: EDIT_CLEAR(rover.edit); CLEAR_MESSAGE(); + break; + case KEY_F(1) ... KEY_F(12): + case KEY_IC: + break; default: if (iswprint(ch) && !EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, ch); @@ -679,7 +656,6 @@ void main_menu(void) message(RED, "\"%s\" already exists!", input); rover_getch(); CLEAR_MESSAGE(); - curs_set(TRUE); edit_stat = CONTINUE; } } else if (!isvalidfilename(input, false)) { @@ -690,7 +666,6 @@ void main_menu(void) message(RED, "\"%s\" invalid filename!", input); rover_getch(); CLEAR_MESSAGE(); - curs_set(TRUE); edit_stat = CONTINUE; } } @@ -724,7 +699,6 @@ void main_menu(void) message(RED, "\"%s\" already exists!", input); rover_getch(); CLEAR_MESSAGE(); - curs_set(TRUE); edit_stat = CONTINUE; } } else if (!isvalidfilename(input, false)) { @@ -735,7 +709,6 @@ void main_menu(void) message(RED, "\"%s\" invalid dirname!", input); rover_getch(); CLEAR_MESSAGE(); - curs_set(TRUE); edit_stat = CONTINUE; } } From 943961ad759b73b4d4b7cb6a9446421a18d6067b Mon Sep 17 00:00:00 2001 From: Sandro Date: Tue, 3 Jan 2023 22:41:35 +0100 Subject: [PATCH 11/18] improve file copy and so on implement filecopy() with better performance organize better the functions in different files fix some bugs --- src/os_funcs.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++- src/os_funcs.h | 15 ++- src/rover.c | 79 ++------------- src/rover.h | 3 - src/ui_funcs.c | 6 +- src/ui_funcs.h | 1 + 6 files changed, 280 insertions(+), 80 deletions(-) diff --git a/src/os_funcs.c b/src/os_funcs.c index 6a5a179..73b8ca6 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -20,7 +20,15 @@ int rm(const char *fname) char *errnomsg; errno = 0; - result = remove(fname); + + result = filetype(fname); + if (result == S_IFREG || result == S_IFLNK) + result = unlink(fname); + else if (result == S_IFDIR) + result = rmdir(fname); + else + LOG(LOG_ERR, "\"%s\" is not a regular file, symbolic link or directory.", fname); + switch (errno) { case EACCES: errnomsg = "Write access to the file or directory is not allowed for the process's effective UID, or one of the directories in pathname did not allow search permission."; @@ -113,3 +121,249 @@ bool isvalidfilename(const char *filename, bool wildcard) return (st_mode == S_IFREG); } + +static ino_t inodeof(const char *filename) +{ + struct stat sb; + + if (lstat(filename, &sb) == -1) + LOG(LOG_INFO, "Error reading i-node number of \"%s\"", filename); + + return (ino_t)sb.st_ino; +} + +static int opennew(const char *fname, mode_t mode, ino_t source_inode) +{ + int handle = -1L; + bool ok = true; + + if (access(fname, F_OK) == 0) { //check if the target file already exists + if (inodeof(fname) == source_inode) + return -2; + + message(RED, "Warning destination file already exist. Overwrite (Y/n)?"); + if (rover_getch() == 'Y') + rm(fname); + else + ok = false; + + CLEAR_MESSAGE(); + } + if(ok) + handle = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); + + return handle; +} + +static ssize_t filecopy(const char *source, const char *todir) +{ + int input, output; + struct stat fileinfo = { 0 }; + ssize_t result = -1L; + off_t chunk, bytesCount = 0; + char *errnomsg, *filename, *data, *ptr, *end, destination[FILENAME_MAX]; + bool trayAgain = false; /* Applications may wish to fall back to read(2)/write(2) in + the case where sendfile() fails with EINVAL or ENOSYS. */ + + filename = strdup(source); // duplicate because of basename() modify the string + sprintf(destination, "%s%s", todir, basename(filename)); // build complete destination path dir+fname + + input = open(source, O_RDONLY); //try opening the source file + if (input == -1) { + LOG(LOG_ERR, "File open error: %s", source); + + return result; + } + + errno = 0; + if (fstat(input, &fileinfo)) { //read the attributes of the source file + switch (errno) { + case EACCES: + errnomsg = "Search permission is denied for one of the directories in the path prefix of source file."; + break; + case EBADF: + errnomsg = "fd is not a valid open file descriptor."; + break; + case EFAULT: + errnomsg = "Bad address."; + break; + case ELOOP: + errnomsg = "Too many symbolic links encountered while traversing the path."; + break; + case ENAMETOOLONG: + errnomsg = "Source pathname is too long."; + break; + case ENOENT: + errnomsg = "A component of source pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + errnomsg = "Out of memory (i.e., kernel memory)."; + break; + case ENOTDIR: + errnomsg = "A component of the path prefix of pathname is not a directory."; + break; + case EOVERFLOW: + errnomsg = "Source pathname refers to a file whose size, inode number, or number of blocks cannot be represented in, respectively, the types off_t, ino_t, or blkcnt_t.\nThis error can occur when, for example, an application compiled on a 32-bit platform without -D_FILE_OFFSET_BITS=64 calls stat() on a file whose size exceeds (1<<31)-1 bytes."; + break; + case 0: + default: + errnomsg = NULL; + break; + } + LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", source, errno, errnomsg); + message(RED, "Error reading info from source file. more info in \"%s.log\"", ROVER); + rover_getch(); + CLEAR_MESSAGE(); + + return result; + } + + output = opennew(destination, fileinfo.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source + if (output < 0) { //try to open the destinatiion file + close(input); + errnomsg = (output == -1 ? "Error opening destination file" : "Destination file must be in a different directory of the source file"); + + LOG(LOG_ERR, "%s", errnomsg); + message(RED, "%s", errnomsg); + rover_getch(); + CLEAR_MESSAGE(); + + return result; + } + + errno = 0; + bytesCount = fileinfo.st_size; + result = sendfile(output, input, NULL, bytesCount); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ + + switch (errno) { + case EAGAIN: + errnomsg = "Nonblocking I/O has been selected using O_NONBLOCK and the write would block."; + break; + case EBADF: + errnomsg = "The input file was not opened for reading or the output file was not opened for writing."; + break; + case EFAULT: + errnomsg = "Bad address."; + break; + case EINVAL: + case ENOSYS: + errnomsg = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; + trayAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ + break; + case EIO: + errnomsg = "Unspecified error while reading from in_fd."; + break; + case ENOMEM: + errnomsg = "Insufficient memory to read from in_fd."; + break; + case EOVERFLOW: + errnomsg = "count is too large, the operation would result in exceeding the maximum size of either the input file or the output file."; + break; + case ESPIPE: + errnomsg = "offset is not NULL but the input file is not seekable."; + break; + case 0: + default: + errnomsg = NULL; + break; + } + if (errno) //if an error has occurred + LOG(LOG_ERR, "File copy error errno[%d]: %s", errno, errnomsg); + + if (result != fileinfo.st_size) { //verify that all bytes have been copied + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", result, fileinfo.st_size); + rover_getch(); + CLEAR_MESSAGE(); + } + + if(trayAgain) { + LOG(LOG_INFO, "%s try to copy again with read()/write() instead of sendfile().", ROVER); + errno = 0; + chunk = MIN(fileinfo.st_size, DEFAULT_CHUNK); //set better performance for copy + data = malloc((size_t) chunk); /* Allocate temporary data buffer. */ + if (data) { + do { /* Copy loop. */ + bytesCount = read(input, data, chunk); + if (bytesCount <= 0) { + FREE(data); + break; + } + /* Write that same chunk. */ + ptr = data; + for(end = (char *) (data + bytesCount); ptr < end; ptr += bytesCount) { + bytesCount = write(output, ptr, (size_t) (end - ptr)); + if (bytesCount <= 0) { + FREE(data); + trayAgain = false; // exit from do while + break; // exit from for + } + } + } while(trayAgain); + if(!errno) + LOG(LOG_INFO, "%s copy with success using read()/write() instead of sendfile().", ROVER); + } + } + close(input); //close the handle + close(output); //close the handle + if(errno) + unlink(destination); /* Remove output file. */ + + return result; //return the number of bytes copied +} + +int cpyfile(const char *srcpath) +{ + char dstpath[PATH_MAX], buffer[PATH_MAX]; + ssize_t result = -1L; + mode_t mode; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + + mode = filetype(srcpath); + if (!mode) + return (int) result; + + if (mode == S_IFLNK) { + result = readlink(srcpath, buffer, PATH_MAX); + if (result < 0) { + message(RED, "Error reading symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + } else { + buffer[result] = '\0'; + result = symlink(buffer, dstpath); + } + } else if (mode == S_IFREG) { + result = filecopy(srcpath, dstpath); + } else { + message(RED, "Error: source file must be a regular file or symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + } + + return (int) result; +} + +int movfile(const char *srcpath) +{ + char dstpath[PATH_MAX]; + ssize_t result = -1L; + mode_t mode; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + + if (rename(srcpath, dstpath) == 0) { + mode = filetype(dstpath); + if (!mode) + return result; + } else if (errno == EXDEV) { + result = filecopy(srcpath, dstpath); + if (result < 0) + return result; + result = rm(srcpath); + } + + return result; +} diff --git a/src/os_funcs.h b/src/os_funcs.h index 4e64ce2..57da8a3 100644 --- a/src/os_funcs.h +++ b/src/os_funcs.h @@ -6,9 +6,13 @@ #include #include /* mkdtemp() */ #include /* sprintf() fopen() fclose() */ -#include /* remove() */ +#include /* AT_REMOVEDIR */ +#include /* unlinkat() */ #include #include +#include + +#define DEFAULT_CHUNK 262144 /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ /* returns the file type with mask of & S_IFMT @@ -24,5 +28,14 @@ check if the specified name is a valid file name return true if is valid or false if is not valid */ bool isvalidfilename(const char *filename, bool wildcard); +/* +copy source file in CWD using filecopy() func +*/ +int cpyfile(const char *srcpath); +/* +move source file in CWD using rename() or the combination of filecopy() + rm() funcs +*/ +int movfile(const char *srcpath); + #endif // _OS_FUNCS_H \ No newline at end of file diff --git a/src/rover.c b/src/rover.c index 32eda24..fb185db 100755 --- a/src/rover.c +++ b/src/rover.c @@ -258,9 +258,7 @@ int ls(Row **rowsp, uint8_t flags) void free_rows(Row **rowsp, int nfiles) { - int i; - - for (i = 0; i < nfiles; i++) + for (int i = 0; i < nfiles; i++) FREE((*rowsp)[i].name); FREE(*rowsp); } @@ -329,17 +327,17 @@ void reload() cd(true); } -off_t count_dir(const char *path) +static off_t count_dir(const char *path) { DIR *dp; struct dirent *ep; struct stat statbuf; char subpath[PATH_MAX]; - off_t total; + off_t total = 0; if (!(dp = opendir(path))) - return 0; - total = 0; + return total; + while ((ep = readdir(dp))) { if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; @@ -358,14 +356,12 @@ off_t count_dir(const char *path) off_t count_marked() { - int i; char *entry; - off_t total; + off_t total = 0; struct stat statbuf; - total = 0; chdir(rover.marks.dirpath); - for (i = 0; i < rover.marks.bulk; i++) { + for (int i = 0; i < rover.marks.bulk; i++) { entry = rover.marks.entries[i]; if (entry) { if (ISDIR(entry)) { @@ -395,64 +391,3 @@ void update_progress(off_t delta) return; } - -int cpyfile(const char *srcpath) -{ - int src, dst, ret; - size_t size; - struct stat st; - char buf[BUFSIZ], dstpath[PATH_MAX], buffer[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = lstat(srcpath, &st); - if (ret < 0) - return ret; - if (S_ISLNK(st.st_mode)) { - ret = readlink(srcpath, buffer, PATH_MAX - 1); - if (ret < 0) - return ret; - buffer[ret] = '\0'; - ret = symlink(buffer, dstpath); - } else { - ret = src = open(srcpath, O_RDONLY); - if (ret < 0) - return ret; - ret = dst = creat(dstpath, st.st_mode); - if (ret < 0) - return ret; - while ((size = read(src, buf, BUFSIZ)) > 0) { - write(dst, buf, size); - update_progress(size); - sync_signals(); - } - close(src); - close(dst); - ret = 0; - } - - return ret; -} - -int movfile(const char *srcpath) -{ - int ret; - struct stat st; - char dstpath[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = rename(srcpath, dstpath); - if (ret == 0) { - ret = lstat(dstpath, &st); - if (ret < 0) - return ret; - update_progress(st.st_size); - } else if (errno == EXDEV) { - ret = cpyfile(srcpath); - if (ret < 0) - return ret; - ret = unlink(srcpath); - } - return ret; -} diff --git a/src/rover.h b/src/rover.h index f07e17f..1164657 100644 --- a/src/rover.h +++ b/src/rover.h @@ -184,10 +184,7 @@ int ls(Row **rowsp, uint8_t flags); void free_rows(Row **rowsp, int nfiles); void cd(bool reset); void try_to_sel(const char *target); -off_t count_dir(const char *path); off_t count_marked(void); void update_progress(off_t delta); -int cpyfile(const char *srcpath); -int movfile(const char *srcpath); #endif // _ROVER_H \ No newline at end of file diff --git a/src/ui_funcs.c b/src/ui_funcs.c index efc3680..86b4195 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -132,7 +132,7 @@ static void start_line_edit(const char *init_input) /* This function must be used in place of getch(). It handles signals while waiting for user input. */ -static int rover_getch(void) +int rover_getch(void) { int ch; @@ -353,7 +353,7 @@ static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *m CLEAR_MESSAGE(); message(CYAN, "%s...", msg_doing); refresh(); - rover.prog = (Prog){ 0, count_marked(), msg_doing }; + rover.prog = (Prog){ 0, count_marked(), msg_doing }; // init progress for (i = 0; i < rover.marks.bulk; i++) { entry = rover.marks.entries[i]; if (entry) { @@ -372,7 +372,7 @@ static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *m } } } - rover.prog.total = 0; + rover.prog.total = 0; // reset progress reload(); if (!rover.marks.nentries) message(GREEN, "%s all marked entries.", msg_done); diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 1f57371..71e5dfa 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -126,6 +126,7 @@ typedef enum EditStat { void handle_usr1(int sig); void handle_winch(int sig); void handlers(bool enable); +int rover_getch(void); void message(Color color, char *fmt, ...); void update_view(void); void main_menu(void); From 3875ab392d4228915807c90ef0ea77955cbf9cb0 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 5 Jan 2023 00:21:51 +0100 Subject: [PATCH 12/18] fix some bugs fix rover_home_path with getcwd() moved delwin() from main() to endsession() improveed filetype() now is fileinfo() that return also size improved movfile() improved update_view() --- src/main.c | 17 ++- src/os_funcs.c | 259 ++++++++++++++++++++++++++--------- src/os_funcs.h | 21 +-- src/rover.c | 359 ++++++++++++++++--------------------------------- src/rover.h | 4 - src/ui_funcs.c | 40 ++++-- 6 files changed, 354 insertions(+), 346 deletions(-) diff --git a/src/main.c b/src/main.c index 1bb050b..703e098 100755 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ -#include "ui_funcs.h" #include "rover.h" +#include "ui_funcs.h" +#include "os_funcs.h" struct Rover rover; char rover_home_path[RV_PATH_MAX]; @@ -110,9 +111,6 @@ int main(int argc, char *argv[]) } } - strcpy(clipboard, argv[0]); - strcpy(rover_home_path, dirname(clipboard)); //get the home directory of rover - init_term(); rover.nfiles = 0; for (i = 0; i < 10; i++) { @@ -130,7 +128,9 @@ int main(int argc, char *argv[]) strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); } - getcwd(rover.tabs[i].cwd, PATH_MAX); + getcwd(rover_home_path, RV_PATH_MAX); //get the current work directory for rover.log and rover manual file + strcpy(rover.tabs[i].cwd, rover_home_path); + for (i++; i < 10; i++) strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); @@ -140,16 +140,15 @@ int main(int argc, char *argv[]) rover.tab = 1; rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); init_marks(&rover.marks); - cd(true); + if(!cd(true)) + exit(EXIT_FAILURE); + strcpy(clipboard, CWD); if (rover.nfiles > 0) strcat(clipboard, ENAME(ESEL)); main_menu(); - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - delwin(rover.window); if (save_cwd_file != NULL) { fputs(CWD, save_cwd_file); fclose(save_cwd_file); diff --git a/src/os_funcs.c b/src/os_funcs.c index 73b8ca6..1a91e82 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -2,29 +2,40 @@ #include "os_funcs.h" #include "ui_funcs.h" -mode_t filetype(const char *filename) +/* +assign at param size the total size in bytes +returns the type mode of file specified in fname +*/ +mode_t fileinfo(const char *fname, off_t *size) { struct stat sb; - if (lstat(filename, &sb) == -1) { - return false; + *size = -1L; + if (lstat(fname, &sb) == -1) { + return 0; } + if (size) //check in NULL pointer + *size = sb.st_size; - return (sb.st_mode & S_IFMT); + return sb.st_mode; } -/* Wrappers for file operations. */ +/* +Delete fname even is file, symbolic link or directory +On success, zero is returned. On error, -1 is returned +*/ int rm(const char *fname) { - int result; + int result = -1; + mode_t mode; char *errnomsg; - errno = 0; + errno = 0; - result = filetype(fname); - if (result == S_IFREG || result == S_IFLNK) + mode = fileinfo(fname, NULL); + if (S_ISREG(mode) || S_ISLNK(mode)) result = unlink(fname); - else if (result == S_IFDIR) + else if (S_ISDIR(mode)) result = rmdir(fname); else LOG(LOG_ERR, "\"%s\" is not a regular file, symbolic link or directory.", fname); @@ -82,44 +93,48 @@ int rm(const char *fname) return result; } +/* +check if the specified name is a valid file name +return true if is valid or false if is not valid +*/ bool isvalidfilename(const char *filename, bool wildcard) { - mode_t st_mode; char dir_template[] = "/tmp/rover-tmpdir.XXXXXX", forbidden[] = "/<>\"|:&", extmatch[] = "?*+@!", temp_file[FILENAME_MAX], *temp_dir = NULL; FILE *fd; + bool result = false; if (strpbrk(filename, forbidden)) //check for invalid characters for filename - return false; + return result; if (strlen(filename) > 255) //check if the lenght of file exceed the max acceppted by filesystem - return false; + return result; if (strpbrk(filename, extmatch)) { //check if filname contains wildchars FNM_EXTMATCH - if(wildcard) - return true; + if (wildcard) + return result; - return false; + return result; } temp_dir = mkdtemp(dir_template); if (temp_dir == NULL) - return false; + return result; if (!mkdir(temp_dir, S_IRWXU | S_IRWXG | S_IRWXO)) //creating temp dir 0777 - return false; + return result; sprintf(temp_file, "%s/%s", temp_dir, filename); fd = fopen(temp_file, "w"); //try to create an empty file using "filename" if (fd == NULL) { rm(temp_dir); - return false; + return result; } fclose(fd); - st_mode = filetype(temp_file); //check if the tempo file is regular + result = S_ISREG(fileinfo(temp_file, NULL)); //check if the temp file is regular if (rm(temp_file)) return false; if (rm(temp_dir)) return false; - return (st_mode == S_IFREG); + return result; //return true if filename its valid } static ino_t inodeof(const char *filename) @@ -135,7 +150,7 @@ static ino_t inodeof(const char *filename) static int opennew(const char *fname, mode_t mode, ino_t source_inode) { int handle = -1L; - bool ok = true; + bool ok = true; if (access(fname, F_OK) == 0) { //check if the target file already exists if (inodeof(fname) == source_inode) @@ -149,7 +164,7 @@ static int opennew(const char *fname, mode_t mode, ino_t source_inode) CLEAR_MESSAGE(); } - if(ok) + if (ok) handle = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); return handle; @@ -158,9 +173,9 @@ static int opennew(const char *fname, mode_t mode, ino_t source_inode) static ssize_t filecopy(const char *source, const char *todir) { int input, output; - struct stat fileinfo = { 0 }; - ssize_t result = -1L; - off_t chunk, bytesCount = 0; + struct stat sb = { 0 }; + ssize_t result = -1L; + off_t chunk, bytesCount = 0; char *errnomsg, *filename, *data, *ptr, *end, destination[FILENAME_MAX]; bool trayAgain = false; /* Applications may wish to fall back to read(2)/write(2) in the case where sendfile() fails with EINVAL or ENOSYS. */ @@ -176,7 +191,7 @@ static ssize_t filecopy(const char *source, const char *todir) } errno = 0; - if (fstat(input, &fileinfo)) { //read the attributes of the source file + if (fstat(input, &sb)) { //read the attributes of the source file switch (errno) { case EACCES: errnomsg = "Search permission is denied for one of the directories in the path prefix of source file."; @@ -218,7 +233,7 @@ static ssize_t filecopy(const char *source, const char *todir) return result; } - output = opennew(destination, fileinfo.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source + output = opennew(destination, sb.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source if (output < 0) { //try to open the destinatiion file close(input); errnomsg = (output == -1 ? "Error opening destination file" : "Destination file must be in a different directory of the source file"); @@ -231,9 +246,9 @@ static ssize_t filecopy(const char *source, const char *todir) return result; } - errno = 0; - bytesCount = fileinfo.st_size; - result = sendfile(output, input, NULL, bytesCount); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ + errno = 0; + bytesCount = sb.st_size; + result = sendfile(output, input, NULL, bytesCount); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ switch (errno) { case EAGAIN: @@ -247,7 +262,7 @@ static ssize_t filecopy(const char *source, const char *todir) break; case EINVAL: case ENOSYS: - errnomsg = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; + errnomsg = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; trayAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ break; case EIO: @@ -269,18 +284,18 @@ static ssize_t filecopy(const char *source, const char *todir) } if (errno) //if an error has occurred LOG(LOG_ERR, "File copy error errno[%d]: %s", errno, errnomsg); - - if (result != fileinfo.st_size) { //verify that all bytes have been copied - message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", result, fileinfo.st_size); + + if (result != sb.st_size) { //verify that all bytes have been copied + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", result, sb.st_size); rover_getch(); - CLEAR_MESSAGE(); + CLEAR_MESSAGE(); } - if(trayAgain) { + if (trayAgain) { LOG(LOG_INFO, "%s try to copy again with read()/write() instead of sendfile().", ROVER); errno = 0; - chunk = MIN(fileinfo.st_size, DEFAULT_CHUNK); //set better performance for copy - data = malloc((size_t) chunk); /* Allocate temporary data buffer. */ + chunk = MIN(sb.st_size, DEFAULT_CHUNK); //set better performance for copy + data = malloc((size_t)chunk); /* Allocate temporary data buffer. */ if (data) { do { /* Copy loop. */ bytesCount = read(input, data, chunk); @@ -290,27 +305,30 @@ static ssize_t filecopy(const char *source, const char *todir) } /* Write that same chunk. */ ptr = data; - for(end = (char *) (data + bytesCount); ptr < end; ptr += bytesCount) { - bytesCount = write(output, ptr, (size_t) (end - ptr)); + for (end = (char *)(data + bytesCount); ptr < end; ptr += bytesCount) { + bytesCount = write(output, ptr, (size_t)(end - ptr)); if (bytesCount <= 0) { - FREE(data); trayAgain = false; // exit from do while break; // exit from for } } - } while(trayAgain); - if(!errno) + } while (trayAgain); + FREE(data); + if (!errno) LOG(LOG_INFO, "%s copy with success using read()/write() instead of sendfile().", ROVER); } } close(input); //close the handle close(output); //close the handle - if(errno) + if (errno) unlink(destination); /* Remove output file. */ return result; //return the number of bytes copied } +/* +copy source file in CWD using filecopy() func +*/ int cpyfile(const char *srcpath) { char dstpath[PATH_MAX], buffer[PATH_MAX]; @@ -320,11 +338,11 @@ int cpyfile(const char *srcpath) strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - mode = filetype(srcpath); + mode = fileinfo(srcpath, NULL); if (!mode) - return (int) result; + return (int)result; - if (mode == S_IFLNK) { + if (S_ISLNK(mode)) { result = readlink(srcpath, buffer, PATH_MAX); if (result < 0) { message(RED, "Error reading symbolic link"); @@ -334,36 +352,153 @@ int cpyfile(const char *srcpath) buffer[result] = '\0'; result = symlink(buffer, dstpath); } - } else if (mode == S_IFREG) { + } else if (S_ISREG(mode)) { result = filecopy(srcpath, dstpath); } else { message(RED, "Error: source file must be a regular file or symbolic link"); rover_getch(); - CLEAR_MESSAGE(); + CLEAR_MESSAGE(); } - - return (int) result; + + return (int)result; } +/* +move source file in CWD using rename() or the combination of filecopy() + rm() funcs +*/ int movfile(const char *srcpath) { char dstpath[PATH_MAX]; - ssize_t result = -1L; - mode_t mode; + int result; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - if (rename(srcpath, dstpath) == 0) { - mode = filetype(dstpath); - if (!mode) - return result; - } else if (errno == EXDEV) { - result = filecopy(srcpath, dstpath); - if (result < 0) - return result; + errno = 0; + result = rename(srcpath, dstpath); + if (errno == EXDEV) { + filecopy(srcpath, dstpath); result = rm(srcpath); } + if (!access(dstpath, F_OK)) + return -1; return result; } + +/* Comparison used to sort listing entries. */ +static int rowcmp(const void *a, const void *b) +{ + int isdir1, isdir2, cmpdir; + const Row *r1 = a, *r2 = b; + + isdir1 = S_ISDIR(r1->mode); + isdir2 = S_ISDIR(r2->mode); + cmpdir = isdir2 - isdir1; + + return (cmpdir ? cmpdir : strcoll(r1->name, r2->name)); +} + +/* Get all entries in current working directory. */ +static int ls(Row **rowsp, uint8_t flags) +{ + DIR *dp; + struct dirent *ep; + Row *rows; + mode_t mode; + off_t size = -1L; + int n, i = 0; + + if (!(dp = opendir("."))) + return -1; + + for (n = 0; (ep = readdir(dp)); n++) + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + n--; // We don't want the entries "." and "..". + + if (n == 0) { + closedir(dp); + + return 0; + } + + rewinddir(dp); + rows = malloc(n * sizeof *rows); + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') + continue; + + mode = fileinfo(ep->d_name, &size); + rows[i].islink = (S_ISLNK(mode)); + if (S_ISDIR(mode)) { + if (flags & SHOW_DIRS) { + rows[i].name = (char *) malloc(strlen(ep->d_name) + 2); + strcpy(rows[i].name, ep->d_name); + if (!rows[i].islink) + ADDSLASH(rows[i].name); + rows[i].mode = mode; + i++; + } + } else if (flags & SHOW_FILES) { + rows[i].name = malloc(strlen(ep->d_name) + 1); + strcpy(rows[i].name, ep->d_name); + rows[i].size = size; + rows[i].mode = mode; + i++; + } + } + n = i; // Ignore unused space in array caused by filters. + qsort(rows, n, sizeof(*rows), rowcmp); + closedir(dp); + *rowsp = rows; + + return n; +} + +/* Change working directory to the path in CWD. */ +bool cd(bool reset) +{ + int i, j; + + message(CYAN, "Loading \"%s\"...", CWD); + refresh(); + + if (chdir(CWD) == -1) { + getcwd(CWD, PATH_MAX - 1); + ADDSLASH(CWD); + } else { + if (reset) + ESEL = SCROLL = 0; + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + + rover.nfiles = ls(&rover.rows, FLAGS); + if(rover.nfiles < 0) { + message(RED, "Error reading current directory"); + rover_getch(); + CLEAR_MESSAGE(); + + return false; + } + + if (!strcmp(CWD, rover.marks.dirpath)) { + for (i = 0; i < rover.nfiles; i++) { + for (j = 0; j < rover.marks.bulk; j++) { + if (rover.marks.entries[j] && + !strcmp(rover.marks.entries[j], ENAME(i))) + break; + } + MARKED(i) = j < rover.marks.bulk; + } + } else { + for (i = 0; i < rover.nfiles; i++) + MARKED(i) = false; + } + } + CLEAR_MESSAGE(); + update_view(); + + return true; +} diff --git a/src/os_funcs.h b/src/os_funcs.h index 57da8a3..0c08c7d 100644 --- a/src/os_funcs.h +++ b/src/os_funcs.h @@ -14,28 +14,11 @@ #define DEFAULT_CHUNK 262144 /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ -/* -returns the file type with mask of & S_IFMT -*/ -mode_t filetype(const char *filename); -/* -remove file from disk -return 0 if success or -1 in case of error -*/ +mode_t fileinfo(const char *fname, off_t *size); int rm(const char *fname); -/* -check if the specified name is a valid file name -return true if is valid or false if is not valid -*/ bool isvalidfilename(const char *filename, bool wildcard); -/* -copy source file in CWD using filecopy() func -*/ int cpyfile(const char *srcpath); -/* -move source file in CWD using rename() or the combination of filecopy() + rm() funcs -*/ int movfile(const char *srcpath); - +bool cd(bool reset); #endif // _OS_FUNCS_H \ No newline at end of file diff --git a/src/rover.c b/src/rover.c index fb185db..7aaf358 100755 --- a/src/rover.c +++ b/src/rover.c @@ -1,5 +1,6 @@ #include "rover.h" #include "ui_funcs.h" +#include "os_funcs.h" void logfile(const char *format, ...) { @@ -13,8 +14,9 @@ void logfile(const char *format, ...) sprintf(filelog, "%s/%s.log", rover_home_path, ROVER); fd = fopen(filelog, "a+"); // a+ create or append that is useful in a log file if (!fd) { - fprintf(stderr, "ERROR APPEND/WRITE LOG FILE\n"); - sleep(3); + message(RED, "ERROR APPEND/WRITE LOG FILE \"%s.log\"", ROVER); + rover_getch(); + CLEAR_MESSAGE(); return; } @@ -38,7 +40,9 @@ void logfile(const char *format, ...) int endsession(void) { handlers(false); - delwin(rover.window); + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + delwin(rover.window); /* curs_set(TRUE); keypad(stdscr, FALSE); @@ -47,9 +51,9 @@ int endsession(void) echo(); nodelay(stdscr, FALSE); nocbreak(); + */ + clear(); refresh(); - */ - clear(); endwin(); return EXIT_SUCCESS; @@ -62,198 +66,111 @@ void update_view() wchar_t wbuffer[PATH_MAX]; off_t human_size; - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, buffer_one); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(wbuffer, CWD, PATH_MAX); - mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, buffer_one); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = ENAME(j)[0] == '.'; - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - if (S_ISDIR(EMODE(j))) { - mbstowcs(wbuffer, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(wbuffer, L"/"); - } else { - human_size = ESIZE(j) * 10; - length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); - namecols = wcswidth(wbuffer, length); - for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - if (*suffix == 'B') - swprintf(wbuffer + length, PATH_MAX - length, L"%*d Bytes", - (int) (COLS - namecols - 6), - (int) human_size / 10); - else - swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %cb", - (int) (COLS - namecols - 8), - (int) human_size / 10, (int) human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - if (rover.nfiles > HEIGHT) { - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT-1) * HEIGHT / rover.nfiles; - if (!height) - height = 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height); - } - buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - if (!rover.nfiles) - strcpy(buffer_two, "0/0"); - else - snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(buffer_one+3, PATH_MAX, "%12s", buffer_two); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, buffer_one); - wrefresh(rover.window); -} - -/* Handle any signals received since last call. */ -void sync_signals(void) -{ - if (rover.pending_usr1) { - /* SIGUSR1 received: refresh directory listing. */ - reload(); - rover.pending_usr1 = 0; - } - if (rover.pending_winch) { - /* SIGWINCH received: resize application accordingly. */ - delwin(rover.window); - endwin(); - refresh(); - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - if (HEIGHT < rover.nfiles && - SCROLL + HEIGHT > rover.nfiles) - SCROLL = ESEL - HEIGHT; - - update_view(); - rover.pending_winch = 0; - } -} - -/* Comparison used to sort listing entries. */ -int rowcmp(const void *a, const void *b) -{ - int isdir1, isdir2, cmpdir; - const Row *r1 = a, *r2 = b; - - isdir1 = S_ISDIR(r1->mode); - isdir2 = S_ISDIR(r2->mode); - cmpdir = isdir2 - isdir1; - - return (cmpdir ? cmpdir : strcoll(r1->name, r2->name)); -} - -/* Get all entries in current working directory. */ -int ls(Row **rowsp, uint8_t flags) -{ - DIR *dp; - struct dirent *ep; - struct stat statbuf; - Row *rows; - int i, n; - - if (!(dp = opendir("."))) - return -1; - - n = -2; /* We don't want the entries "." and "..". */ - while (readdir(dp)) - n++; - - if (n == 0) { - closedir(dp); + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = (ENAME(j)[0] == '.'); + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + /* following a series of if else in order to set colors of listing */ + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + + if (S_ISDIR(EMODE(j))) { + mbstowcs(wbuffer, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(wbuffer, L"/"); + } else { + human_size = ESIZE(j) * 10; + length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + namecols = wcswidth(wbuffer, length); + + for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + + if (*suffix == 'B') + swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", + (int)(COLS - namecols - 6), + (int)human_size / 10, *suffix); + else + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", + (int)(COLS - namecols - 8), + (int)human_size / 10, (int)human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); - return 0; + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); } - - rewinddir(dp); - rows = malloc(n * sizeof(*rows)); - i = 0; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') - continue; - lstat(ep->d_name, &statbuf); - rows[i].islink = S_ISLNK(statbuf.st_mode); - stat(ep->d_name, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - if (flags & SHOW_DIRS) { - rows[i].name = (char *)malloc(strlen(ep->d_name) + 2); - strcpy(rows[i].name, ep->d_name); - if (!rows[i].islink) - ADDSLASH(rows[i].name); - rows[i].mode = statbuf.st_mode; - i++; - } - } else if (flags & SHOW_FILES) { - rows[i].name = (char *)malloc(strlen(ep->d_name) + 1); - strcpy(rows[i].name, ep->d_name); - rows[i].size = statbuf.st_size; - rows[i].mode = statbuf.st_mode; - i++; - } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + + if (rover.nfiles > HEIGHT) { + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT - 1) * HEIGHT / rover.nfiles; + height = height ? height : 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); } - n = i; /* Ignore unused space in array caused by filters. */ - qsort(rows, n, sizeof(*rows), rowcmp); - closedir(dp); - *rowsp = rows; - - return n; + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + + if (!rover.nfiles) + strcpy(buffer_two, "0/0"); + else + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + + snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); + wrefresh(rover.window); } void free_rows(Row **rowsp, int nfiles) @@ -263,44 +180,6 @@ void free_rows(Row **rowsp, int nfiles) FREE(*rowsp); } -/* Change working directory to the path in CWD. */ -void cd(bool reset) -{ - int i, j; - - message(CYAN, "Loading \"%s\"...", CWD); - refresh(); - - if (chdir(CWD) == -1) { - getcwd(CWD, PATH_MAX - 1); - ADDSLASH(CWD); - } else { - if (reset) - ESEL = SCROLL = 0; - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - - rover.nfiles = ls(&rover.rows, FLAGS); - if (!strcmp(CWD, rover.marks.dirpath)) { - for (i = 0; i < rover.nfiles; i++) { - for (j = 0; j < rover.marks.bulk; j++) { - if (rover.marks.entries[j] && - !strcmp(rover.marks.entries[j], ENAME(i))) - break; - } - MARKED(i) = j < rover.marks.bulk; - } - } else { - for (i = 0; i < rover.nfiles; i++) - MARKED(i) = false; - } - } - CLEAR_MESSAGE(); - update_view(); - - return; -} - /* Select a target entry, if it is present. */ void try_to_sel(const char *target) { @@ -314,7 +193,7 @@ void try_to_sel(const char *target) } /* Reload CWD, but try to keep selection. */ -void reload() +void reload(void) { char input[PATH_MAX]; @@ -331,9 +210,8 @@ static off_t count_dir(const char *path) { DIR *dp; struct dirent *ep; - struct stat statbuf; char subpath[PATH_MAX]; - off_t total = 0; + off_t total = 0L, size = -1L; if (!(dp = opendir(path))) return total; @@ -342,12 +220,12 @@ static off_t count_dir(const char *path) if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { + + if (S_ISDIR(fileinfo(subpath, &size))) { ADDSLASH(subpath); total += count_dir(subpath); } else - total += statbuf.st_size; + total += size; } closedir(dp); @@ -357,8 +235,7 @@ static off_t count_dir(const char *path) off_t count_marked() { char *entry; - off_t total = 0; - struct stat statbuf; + off_t total = 0L, size = -1L; chdir(rover.marks.dirpath); for (int i = 0; i < rover.marks.bulk; i++) { @@ -367,8 +244,8 @@ off_t count_marked() if (ISDIR(entry)) { total += count_dir(entry); } else { - lstat(entry, &statbuf); - total += statbuf.st_size; + fileinfo(entry, &size); + total += size; } } } diff --git a/src/rover.h b/src/rover.h index 1164657..7703b15 100644 --- a/src/rover.h +++ b/src/rover.h @@ -177,12 +177,8 @@ extern char rover_home_path[RV_PATH_MAX]; void logfile(const char *format, ...); int endsession(void); void reload(void); -void sync_signals(void); void update_view(void); -int rowcmp(const void *a, const void *b); -int ls(Row **rowsp, uint8_t flags); void free_rows(Row **rowsp, int nfiles); -void cd(bool reset); void try_to_sel(const char *target); off_t count_marked(void); void update_progress(off_t delta); diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 86b4195..f0e975a 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -130,6 +130,29 @@ static void start_line_edit(const char *init_input) rover.edit_scroll = 0; } +/* Handle any signals received since last call. */ +static void sync_signals(void) +{ + if (rover.pending_usr1) { + /* SIGUSR1 received: refresh directory listing. */ + reload(); + rover.pending_usr1 = 0; + } + if (rover.pending_winch) { + /* SIGWINCH received: resize application accordingly. */ + delwin(rover.window); + endwin(); + refresh(); + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + if (HEIGHT < rover.nfiles && + SCROLL + HEIGHT > rover.nfiles) + SCROLL = ESEL - HEIGHT; + + update_view(); + rover.pending_winch = 0; + } +} + /* This function must be used in place of getch(). It handles signals while waiting for user input. */ int rover_getch(void) @@ -220,14 +243,13 @@ static int addfile(const char *path) static int adddir(const char *path) { - int ret; - struct stat st; + mode_t mode; - ret = stat(CWD, &st); - if (ret < 0) - return ret; + mode = fileinfo(CWD, NULL); + if (!mode) + return -1; - return mkdir(path, st.st_mode); + return mkdir(path, mode); } /* Unmark all entries. */ @@ -310,7 +332,6 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) int ret; DIR *dp; struct dirent *ep; - struct stat statbuf; char subpath[PATH_MAX], dstpath[PATH_MAX]; ret = 0; @@ -327,8 +348,7 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { + if (S_ISDIR(fileinfo(subpath, NULL))) { ADDSLASH(subpath); ret |= process_dir(pre, proc, pos, subpath); } else @@ -471,8 +491,6 @@ void main_menu(void) case KEY_LEFT: //Go to parent directory if (!strcmp(CWD, "/")) continue; - //length = strlen(CWD) - 1; - //CWD[length] = '\0'; DELSLASH(CWD); dir_name = strrchr(CWD, '/') + 1; first = dir_name[0]; From 463d53510246e50dcf15b3ab05b5ac8fcfa45e95 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 5 Jan 2023 16:58:35 +0100 Subject: [PATCH 13/18] improve input and manual some minor bugs fix add raw() to init_term() in order to manage Ctrl+C implement fileexist() ADDSLASH() and DELSLASH() now discard / root dir improve input, now many functions are called by function keys --- rover.1 | 65 ++++++++++---------- src/main.c | 1 + src/os_funcs.c | 61 ++++++++++++++++++- src/os_funcs.h | 1 + src/rover.h | 20 ++++--- src/ui_funcs.c | 157 +++++++++++++++++++++++++------------------------ src/ui_funcs.h | 41 +++++++++---- 7 files changed, 211 insertions(+), 135 deletions(-) diff --git a/rover.1 b/rover.1 index 6d0eae1..7aea114 100755 --- a/rover.1 +++ b/rover.1 @@ -75,76 +75,73 @@ pointed to the "source" directory of the operation and then issue the command on another tab that is pointed to the "destination" directory. .SH COMMANDS .TP -.B ESC +.B Quit rover. .TP -.B Arrow up/down +.B Arrow / Move cursor up/down. .TP -.B Page up/down +.B Page / Move cursor up/down 10 lines. .TP -.B HOME/END +.B / Move cursor to top/bottom of the list. .TP -.B Arrow Right -Enter selected directory. +.B Arrow / +Enter selected directory/Go to parent directory. .TP -.B Arrow Left -Go to parent directory. -.TP -.B h +.B / Go to \fB$HOME\fR directory. .TP -.B t -Go to the target of the selected link. +.B l +Go to the target of the selected symbolic link. .TP -.B y +.B +C Copy location to clipboard. .TP -.B p +.B +V Go to location in clipboard. .TP -.B r +.B Refresh directory listing. .TP -.B -Open \fB$SHELL\fR on the current directory. +.B t +Open terminal \fB$SHELL\fR on the current directory. .TP -.B +.B Open \fB$PAGER\fR with the selected file. .TP -.B e +.B Open \fB$VISUAL\fR or \fB$EDITOR\fR with the selected file. .TP -.B o +.B Open \fB$OPEN\fR with the selected file. .TP -.B / +.B Start incremental search. .TP .B F/D/H Toggle file/directory/hidden listing. .TP -.B n/N +.B / Create new file/directory. .TP -.B F2 +.B Rename selected file or directory. .TP -.B E +.B Toggle execute permission of the selected file. .TP -.B CANC +.B Delete selected file or (empty) directory. .TP -.B m +.B Toggle mark on the selected entry. .TP -.B M +.B +s Toggle mark on all visible entries. .TP -.B a +.B +a Mark all visible entries. .TP .B X/C/V @@ -161,25 +158,25 @@ keys will insert characters at the cursor position. The following shortcuts are available for line editing: .TP .B -Finish editing and \fBcancel\fR command. +Clear line (remove all characters). .TP .B Finish editing and \fBconfirm\fR command. .TP -.B / +.B Arrow / Move insertion cursor left/right. .TP -.B / +.B Arrow / Move insertion cursor to beginning/end of string. .TP .B Remove one character before cursor. .TP -.B +.B Remove one character after cursor. .TP -.B +u -Clear line (remove all characters). +.B +If line is empty than finish editing and \fBcancel\fR command otherwise clear line (remove all characters). .SH ENVIRONMENT VARIABLES .TP .B HOME diff --git a/src/main.c b/src/main.c index 703e098..761c1be 100755 --- a/src/main.c +++ b/src/main.c @@ -18,6 +18,7 @@ static void init_term(void) intrflush(stdscr, FALSE); keypad(stdscr, TRUE); curs_set(FALSE); /* Hide blinking cursor. */ + raw(); if (has_colors()) { short bg; diff --git a/src/os_funcs.c b/src/os_funcs.c index 1a91e82..452a75c 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -10,7 +10,6 @@ mode_t fileinfo(const char *fname, off_t *size) { struct stat sb; - *size = -1L; if (lstat(fname, &sb) == -1) { return 0; } @@ -20,6 +19,64 @@ mode_t fileinfo(const char *fname, off_t *size) return sb.st_mode; } +/* +check if file or directory is accessible +in case of error write detailed error message in log file +return 0 if accesible otherwise -1 +*/ +int fileexist(const char *fname) +{ + int result; + char *errnomsg; + + errno = 0; + + result = access(fname, F_OK); + switch (errno) + { + case EACCES: + errnomsg = "The requested access would be denied to the file, or search permission is denied for one of the directories in the path prefix of pathname."; + break; + case EFAULT: + errnomsg = "pathname points outside your accessible address space."; + break; + case EINVAL: + errnomsg = "mode was incorrectly specified."; + break; + case EIO: + errnomsg = "An I/O error occurred."; + break; + case ELOOP: + errnomsg = "Too many symbolic links were encountered in resolving pathname."; + break; + case ENAMETOOLONG: + errnomsg = "pathname is too long."; + break; + case ENOENT: + errnomsg = "A component of pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + errnomsg = "Insufficient kernel memory was available."; + break; + case ENOTDIR: + errnomsg = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EROFS: + errnomsg = "Write permission was requested for a file on a read-only filesystem."; + break; + case ETXTBSY: + errnomsg = "Write access was requested to an executable which is being executed."; + break; + default: + errnomsg = "Unspecified error."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Unable to access: \"%s\" errno[%d]: %s", fname, errno, errnomsg); //write detailed error message to log file + + return result; +} + /* Delete fname even is file, symbolic link or directory On success, zero is returned. On error, -1 is returned @@ -380,7 +437,7 @@ int movfile(const char *srcpath) filecopy(srcpath, dstpath); result = rm(srcpath); } - if (!access(dstpath, F_OK)) + if (!fileexist(dstpath)) return -1; return result; diff --git a/src/os_funcs.h b/src/os_funcs.h index 0c08c7d..c6f4526 100644 --- a/src/os_funcs.h +++ b/src/os_funcs.h @@ -15,6 +15,7 @@ #define DEFAULT_CHUNK 262144 /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ mode_t fileinfo(const char *fname, off_t *size); +int fileexist(const char *fname); int rm(const char *fname); bool isvalidfilename(const char *filename, bool wildcard); int cpyfile(const char *srcpath); diff --git a/src/rover.h b/src/rover.h index 7703b15..717c9b4 100644 --- a/src/rover.h +++ b/src/rover.h @@ -138,17 +138,19 @@ struct Rover { #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define ISDIR(E) (strchr((E), '/') != NULL) -/* Add / at the end of path */ -#define ADDSLASH(path) \ - { \ - if (path[strlen(path) - 1] != '/') \ - strcat(path, "/"); \ +/* Check if not root dir than add / at the end of path */ +#define ADDSLASH(_path) \ + { \ + if (strcmp(_path, "/") && \ + _path[strlen(_path) - 1] != '/') \ + strcat(_path, "/"); \ } -#define DELSLASH(path) \ - { \ - if (path[strlen(path) - 1] == '/') \ - path[strlen(path) - 1] = '\0'; \ +#define DELSLASH(_path) \ + { \ + if (strcmp(_path, "/") && \ + _path[strlen(_path) - 1] == '/') \ + _path[strlen(_path) - 1] = '\0'; \ } /* Safe version of free() don't need assign NULL after free */ #define FREE(p) \ diff --git a/src/ui_funcs.c b/src/ui_funcs.c index f0e975a..4326fd4 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -169,17 +169,25 @@ int rover_getch(void) static EditStat get_line_edit(char *string) { int ch, length; - ch = rover_getch(); switch (ch) { case KEY_ENTER: - case KEY_RETURN: + case KEY_RETURN: curs_set(FALSE); return CONFIRM; break; case KEY_ESC: - curs_set(FALSE); - return CANCEL; + if(strlen(string)) { + EDIT_CLEAR(rover.edit); + CLEAR_MESSAGE(); + } else { + curs_set(FALSE); + return CANCEL; + } + break; + case KEY_TAB: + EDIT_CLEAR(rover.edit); + CLEAR_MESSAGE(); break; case KEY_LEFT: if (EDIT_CAN_LEFT(rover.edit)) @@ -209,11 +217,9 @@ static EditStat get_line_edit(char *string) if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit); break; - case KEY_CANCEL: - EDIT_CLEAR(rover.edit); - CLEAR_MESSAGE(); + case KEY_CANCEL: break; - case KEY_F(1) ... KEY_F(12): + case KEY_F(1)... KEY_F(12): case KEY_IC: break; default: @@ -405,14 +411,15 @@ void main_menu(void) { int i, ch, oldsel, oldscroll, length, ok, sel; bool quit = false, isdir; - char buffer_one[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; - char *program, *dir_name, *bname, *last, *msg, first; + char buffer[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; + char *program, *last; const char *clip_path; ssize_t link; Color color = RED; FILE *clip_file; EditStat edit_stat; struct User prg; + mode_t mode; ROVER_ENV(prg.Shell, SHELL); ROVER_ENV(prg.Pager, PAGER); @@ -436,8 +443,9 @@ void main_menu(void) cd(false); break; case '?': //Help - sprintf(buffer_one, "%s/%s.1", rover_home_path, ROVER); // local manfile - spawn((char *[]){ "man", access(buffer_one, R_OK) ? ROVER : buffer_one, NULL }); + case KEY_F(1): + sprintf(buffer, "%s/%s.1", rover_home_path, ROVER); // local manfile + spawn((char *[]){ "man", access(buffer, R_OK) ? ROVER : buffer, NULL }); break; case KEY_DOWN: //Move cursor down if (!rover.nfiles) @@ -492,60 +500,48 @@ void main_menu(void) if (!strcmp(CWD, "/")) continue; DELSLASH(CWD); - dir_name = strrchr(CWD, '/') + 1; - first = dir_name[0]; - dir_name[0] = '\0'; + strcpy(buffer, strrchr(CWD, '/') + 1); //copy the cwd for try_to_sel + ADDSLASH(buffer); + dirname(CWD); + ADDSLASH(CWD); cd(true); - dir_name[0] = first; - ADDSLASH(dir_name); - try_to_sel(dir_name); - dir_name[0] = '\0'; + try_to_sel(buffer); if (rover.nfiles > HEIGHT) SCROLL = ESEL - HEIGHT / 2; update_view(); break; - case 'h': //Go to home directory + case '/': //Go to home directory strcpy(CWD, getenv("HOME")); ADDSLASH(CWD); cd(true); break; - case 't': //Go to the target of the selected link - isdir = S_ISDIR(EMODE(ESEL)); - link = readlink(ENAME(ESEL), buffer_one, PATH_MAX - 1); + case 'l': //Go to the target of the selected link + link = readlink(ENAME(ESEL), buffer, PATH_MAX - 1); if (link == -1) - continue; - buffer_one[link] = '\0'; - errno = 0; - if (access(buffer_one, F_OK) == -1) { - switch (errno) { - case EACCES: - msg = "Cannot access \"%s\"."; - break; - case ENOENT: - msg = "\"%s\" does not exist."; - break; - default: - msg = "Cannot navigate to \"%s\"."; - } - message(RED, msg, buffer_one); + continue; + buffer[link] = '\0'; // needed because of readlink return not null-terminated string + if (fileexist(buffer) != 0) { + message(RED, "File doesn't exist, more detail in %s.log", ROVER); + rover_getch(); + CLEAR_MESSAGE(); continue; } - realpath(buffer_one, CWD); - link = strlen(CWD); - if (CWD[link - 1] == '/') - CWD[link - 1] = '\0'; - bname = strrchr(CWD, '/') + 1; - first = *bname; - *bname = '\0'; - cd(true); - *bname = first; - if (isdir) + mode = fileinfo(buffer, NULL); + strcpy(CWD, buffer); + if (S_ISREG(mode)) { + strcpy(CWD, buffer); + strcpy(buffer, strrchr(CWD, '/') +1 ); + dirname(CWD); ADDSLASH(CWD); - try_to_sel(bname); - *bname = '\0'; + } + cd(true); + if(S_ISREG(mode)) + try_to_sel(buffer); + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; update_view(); break; - case 'y': //Copy location to clipboard + case KEY_CTRL_C: //Copy location to clipboard clip_path = getenv("CLIP"); if (clip_path) { clip_file = fopen(clip_path, "w"); @@ -557,7 +553,7 @@ void main_menu(void) if (!clip_path || !clip_file) sprintf(clipboard, "%s%s", CWD, ENAME(ESEL)); break; - case 'p': //Go to location in clipboard + case KEY_CTRL_V: //Go to location in clipboard clip_path = getenv("CLIP"); if (clip_path) { clip_file = fopen(clip_path, "r"); @@ -566,19 +562,18 @@ void main_menu(void) fclose(clip_file); } } - strcpy(buffer_one, clipboard); - strcpy(CWD, dirname(buffer_one)); - if (strcmp(CWD, "/")) - ADDSLASH(CWD); + strcpy(buffer, clipboard); + strcpy(CWD, dirname(buffer)); + ADDSLASH(CWD); cd(true); - strcpy(buffer_one, clipboard); - try_to_sel(strstr(clipboard, basename(buffer_one))); + strcpy(buffer, clipboard); + try_to_sel(strstr(clipboard, basename(buffer))); update_view(); break; - case 'r': //Refresh directory listing + case KEY_F(5): //Refresh directory listing reload(); break; - case 's': //Open SHELL on the current directory + case 't': //Open SHELL on the current directory program = prg.Shell; if (program) { #ifdef RV_SHELL @@ -589,25 +584,25 @@ void main_menu(void) reload(); } break; - case 'v': //Open PAGER with the selected file + case KEY_F(6): //Open PAGER with the selected file if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(prg.Pager, ENAME(ESEL))) cd(false); break; - case 'e': //Open VISUAL or EDITOR with the selected file + case KEY_F(7): //Open VISUAL or EDITOR with the selected file if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(prg.Editor, ENAME(ESEL))) cd(false); break; - case 'o': //Open OPEN with the selected file + case KEY_F(8): //Open OPEN with the selected file if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (open_with_env(prg.Open, ENAME(ESEL))) cd(false); break; - case 'i': //Start incremental search + case KEY_F(3): //Start incremental search if (!rover.nfiles) continue; oldsel = ESEL; @@ -658,7 +653,7 @@ void main_menu(void) FLAGS ^= SHOW_HIDDEN; reload(); break; - case 'n': //Create new file + case KEY_F(9): //Create new file start_line_edit(""); DELSLASH(input); update_input(RVP_NEW_FILE, YELLOW, input); @@ -690,18 +685,21 @@ void main_menu(void) } update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); } while (edit_stat == CONTINUE); - CLEAR_MESSAGE(); + if (ok && edit_stat == CONFIRM) { if (addfile(input) == 0) { cd(true); try_to_sel(input); update_view(); - } else + } else { message(RED, "Could not create \"%s\".", input); + rover_getch(); + CLEAR_MESSAGE(); + } } break; - case 'N': //Create new dir + case KEY_F(10): //Create new dir start_line_edit(""); DELSLASH(input); update_input(RVP_NEW_DIR, YELLOW, input); @@ -741,8 +739,11 @@ void main_menu(void) ADDSLASH(input); try_to_sel(input); update_view(); - } else + } else { message(RED, "Could not create \"%s/\".", input); + rover_getch(); + CLEAR_MESSAGE(); + } } break; case KEY_F(2): //Rename selected file or directory @@ -781,7 +782,7 @@ void main_menu(void) message(RED, "\"%s\" already exists.", input); } break; - case 'E': //Toggle execute permission of the selected file + case KEY_F(4): //Toggle execute permission of the selected file if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; if (S_IXUSR & EMODE(ESEL)) @@ -799,18 +800,18 @@ void main_menu(void) if (rover.nfiles) { message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); if (rover_getch() == 'Y') { - strcpy(buffer_one, ENAME(ESEL)); + strcpy(buffer, ENAME(ESEL)); if (MARKED(ESEL)) del_mark(&rover.marks, ENAME(ESEL)); - ok = rm(buffer_one); + ok = rm(buffer); reload(); if (ok == 0) { CLEAR_MESSAGE(); - message(YELLOW, "\"%s\" deleted!", buffer_one); + message(YELLOW, "\"%s\" deleted!", buffer); rover_getch(); } else { CLEAR_MESSAGE(); - message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer_one, ROVER); //print error message + message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer, ROVER); //print error message rover_getch(); CLEAR_MESSAGE(); } @@ -821,7 +822,7 @@ void main_menu(void) rover_getch(); } break; - case 'm': //Toggle mark on the selected entry + case KEY_SPACE: //Toggle mark on the selected entry if (MARKED(ESEL)) del_mark(&rover.marks, ENAME(ESEL)); else @@ -830,7 +831,7 @@ void main_menu(void) ESEL = (ESEL + 1) % rover.nfiles; update_view(); break; - case 'M': //Toggle mark on all visible entries + case KEY_CTRL_S: //Toggle mark on all visible entries for (i = 0; i < rover.nfiles; i++) { if (MARKED(i)) del_mark(&rover.marks, ENAME(i)); @@ -840,7 +841,7 @@ void main_menu(void) } update_view(); break; - case 'a': //Mark all visible entries + case KEY_CTRL_A: //Mark all visible entries for (i = 0; i < rover.nfiles; i++) if (!MARKED(i)) { add_mark(&rover.marks, CWD, ENAME(i)); @@ -876,6 +877,8 @@ void main_menu(void) } else message(RED, "No entries marked for moving."); break; + default: + LOG(LOG_INFO, "0x%02X", ch); } } while (!quit); diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 71e5dfa..8350420 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -33,11 +33,11 @@ #define RVC_STATUS CYAN #define RVC_BORDER BLUE #define RVC_SCROLLBAR CYAN -#define RVC_LINK CYAN -#define RVC_HIDDEN YELLOW -#define RVC_EXEC GREEN -#define RVC_REG DEFAULT -#define RVC_DIR DEFAULT +#define RVC_LINK YELLOW +#define RVC_HIDDEN BLUE +#define RVC_EXEC CYAN +#define RVC_REG GREEN +#define RVC_DIR WHITE #define RVC_CHR MAGENTA #define RVC_BLK MAGENTA #define RVC_FIFO BLUE @@ -48,28 +48,43 @@ /* KEY not defined by curses.h */ #ifndef KEY_SPACE -#define KEY_SPACE 32 +#define KEY_SPACE 0x20 #endif #ifndef KEY_ESC -#define KEY_ESC 27 +#define KEY_ESC 0x1B #endif #ifndef KEY_TAB -#define KEY_TAB 9 +#define KEY_TAB 0x9 #endif #ifndef KEY_RETURN -#define KEY_RETURN '\r' +#define KEY_RETURN 0xD #endif #ifndef KEY_CTRL_DEL -#define KEY_CTRL_DEL 519 +#define KEY_CTRL_DEL 0x207 #endif #ifndef KEY_CTRL_BS -#define KEY_CTRL_BS 8 +#define KEY_CTRL_BS 0x8 #endif #ifndef KEY_CTRL_LEFT -#define KEY_CTRL_LEFT 545 +#define KEY_CTRL_LEFT 0x221 #endif #ifndef KEY_CTRL_RIGHT -#define KEY_CTRL_RIGHT 560 +#define KEY_CTRL_RIGHT 0x230 +#endif +#ifndef KEY_CTRL_X +#define KEY_CTRL_X 0x18 +#endif +#ifndef KEY_CTRL_C +#define KEY_CTRL_C 0x3 +#endif +#ifndef KEY_CTRL_V +#define KEY_CTRL_V 0x10 +#endif +#ifndef KEY_CTRL_A +#define KEY_CTRL_A 0x01 +#endif +#ifndef KEY_CTRL_S +#define KEY_CTRL_S 0x13 #endif /* Line Editing Macros. */ From 30d7a24ab869d1d8a0bb9b52a7b2c6f7017e6928 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 6 Jan 2023 00:47:45 +0100 Subject: [PATCH 14/18] improve marked files operations improve copy and move of marked files fix some bugs TODO: progress bar for process --- rover.1 | 5 +- src/main.c | 6 +- src/os_funcs.c | 403 ++++++++++++++++++++++++++++++++----------------- src/os_funcs.h | 4 +- src/rover.c | 208 +++++++------------------ src/rover.h | 4 +- src/ui_funcs.c | 268 +++++++++++++++++--------------- src/ui_funcs.h | 14 +- 8 files changed, 484 insertions(+), 428 deletions(-) diff --git a/rover.1 b/rover.1 index 7aea114..3e7aaee 100755 --- a/rover.1 +++ b/rover.1 @@ -123,7 +123,7 @@ Start incremental search. .B F/D/H Toggle file/directory/hidden listing. .TP -.B / +.B / Create new file/directory. .TP .B @@ -144,6 +144,9 @@ Toggle mark on all visible entries. .B +a Mark all visible entries. .TP +.B +d +Unmark all visible entries. +.TP .B X/C/V Delete/copy/move all marked entries. .TP diff --git a/src/main.c b/src/main.c index 761c1be..e8afc4d 100755 --- a/src/main.c +++ b/src/main.c @@ -141,9 +141,9 @@ int main(int argc, char *argv[]) rover.tab = 1; rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); init_marks(&rover.marks); - if(!cd(true)) + if (!cd(true)) exit(EXIT_FAILURE); - + strcpy(clipboard, CWD); if (rover.nfiles > 0) strcat(clipboard, ENAME(ESEL)); @@ -163,6 +163,6 @@ int main(int argc, char *argv[]) fclose(save_marks_file); } free_marks(&rover.marks); - + return EXIT_SUCCESS; } diff --git a/src/os_funcs.c b/src/os_funcs.c index 452a75c..6d48389 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -27,53 +27,52 @@ return 0 if accesible otherwise -1 int fileexist(const char *fname) { int result; - char *errnomsg; + char *msgerr; errno = 0; result = access(fname, F_OK); - switch (errno) - { - case EACCES: - errnomsg = "The requested access would be denied to the file, or search permission is denied for one of the directories in the path prefix of pathname."; - break; - case EFAULT: - errnomsg = "pathname points outside your accessible address space."; - break; - case EINVAL: - errnomsg = "mode was incorrectly specified."; - break; - case EIO: - errnomsg = "An I/O error occurred."; - break; - case ELOOP: - errnomsg = "Too many symbolic links were encountered in resolving pathname."; - break; - case ENAMETOOLONG: - errnomsg = "pathname is too long."; - break; - case ENOENT: - errnomsg = "A component of pathname does not exist or is a dangling symbolic link."; - break; - case ENOMEM: - errnomsg = "Insufficient kernel memory was available."; - break; - case ENOTDIR: - errnomsg = "A component used as a directory in pathname is not, in fact, a directory."; - break; - case EROFS: - errnomsg = "Write permission was requested for a file on a read-only filesystem."; - break; - case ETXTBSY: - errnomsg = "Write access was requested to an executable which is being executed."; - break; - default: - errnomsg = "Unspecified error."; - break; + switch (errno) { + case EACCES: + msgerr = "The requested access would be denied to the file, or search permission is denied for one of the directories in the path prefix of pathname."; + break; + case EFAULT: + msgerr = "pathname points outside your accessible address space."; + break; + case EINVAL: + msgerr = "mode was incorrectly specified."; + break; + case EIO: + msgerr = "An I/O error occurred."; + break; + case ELOOP: + msgerr = "Too many symbolic links were encountered in resolving pathname."; + break; + case ENAMETOOLONG: + msgerr = "pathname is too long."; + break; + case ENOENT: + msgerr = "A component of pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + msgerr = "Insufficient kernel memory was available."; + break; + case ENOTDIR: + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EROFS: + msgerr = "Write permission was requested for a file on a read-only filesystem."; + break; + case ETXTBSY: + msgerr = "Write access was requested to an executable which is being executed."; + break; + default: + msgerr = "Unspecified error."; + break; } if (errno) //error is occurred - LOG(LOG_ERR, "Unable to access: \"%s\" errno[%d]: %s", fname, errno, errnomsg); //write detailed error message to log file - + LOG(LOG_ERR, "Unable to access: \"%s\" errno[%d]: %s", fname, errno, msgerr); //write detailed error message to log file + return result; } @@ -85,7 +84,7 @@ int rm(const char *fname) { int result = -1; mode_t mode; - char *errnomsg; + char *msgerr; errno = 0; @@ -99,53 +98,133 @@ int rm(const char *fname) switch (errno) { case EACCES: - errnomsg = "Write access to the file or directory is not allowed for the process's effective UID, or one of the directories in pathname did not allow search permission."; + msgerr = "Write access to the file or directory is not allowed for the process's effective UID, or one of the directories in pathname did not allow search permission."; break; case EBUSY: - errnomsg = "The file or directory cannot be unlinked because it is being used by the system or another process that prevents its removal."; + msgerr = "The file or directory cannot be unlinked because it is being used by the system or another process that prevents its removal."; break; case EFAULT: - errnomsg = "The file or directory points outside your accessible address space."; + msgerr = "The file or directory points outside your accessible address space."; break; case EIO: - errnomsg = "An I/O error occurred."; + msgerr = "An I/O error occurred."; break; case ELOOP: - errnomsg = "Too many symbolic links were encountered in translating."; + msgerr = "Too many symbolic links were encountered in translating."; break; case ENAMETOOLONG: - errnomsg = "pathname was too long."; + msgerr = "pathname was too long."; break; case ENOENT: - errnomsg = "A component of path does not exist or is a dangling symbolic link, or pathname is empty."; + msgerr = "A component of path does not exist or is a dangling symbolic link, or pathname is empty."; break; case ENOMEM: - errnomsg = "Insufficient kernel memory was available."; + msgerr = "Insufficient kernel memory was available."; break; case ENOTDIR: - errnomsg = "A component used as a directory in pathname is not, in fact, a directory."; + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; break; case EPERM: - errnomsg = "The system does not allow unlinking of directories, or unlinking of directories requires privileges that the calling process doesn't have."; + msgerr = "The system does not allow unlinking of directories, or unlinking of directories requires privileges that the calling process doesn't have."; break; case EROFS: - errnomsg = "The file or directory refers to a file on a read-only filesystem."; + msgerr = "The file or directory refers to a file on a read-only filesystem."; break; case EBADF: - errnomsg = "pathname is relative but dirfd is neither AT_FDCWD nor a valid file descriptor."; + msgerr = "pathname is relative but dirfd is neither AT_FDCWD nor a valid file descriptor."; break; case EINVAL: - errnomsg = "An invalid flag value was specified in flags."; + msgerr = "An invalid flag value was specified in flags."; break; case EISDIR: - errnomsg = "pathname refers to a directory, and AT_REMOVEDIR was not specified in flags."; + msgerr = "pathname refers to a directory, and AT_REMOVEDIR was not specified in flags."; + break; + default: + msgerr = "Generic error not specified by OS."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Error removing \"%s\" errno[%d]: %s", fname, errno, msgerr); //write detailed error message to log file + + return result; +} + +/* +create an empty file with 0644 mode +return 0 on success or -1 in case of error +*/ +int addfile(const char *path) +{ + int fd; + + fd = creat(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (fd < 0) + return fd; + + return close(fd); +} + +/* +create an empty dir with 0644 mode +return 0 on success or -1 in case of error +*/ +int adddir(const char *path) +{ + int result; + char *msgerr; + + errno = 0; + + result = mkdir(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + switch (errno) { + case EACCES: + msgerr = "The parent directory does not allow write permission to the process, or one of the directories in pathname did not allow search permission."; + break; + case EDQUOT: + msgerr = "The user's quota of disk blocks or inodes on the filesystem has been exhausted."; + break; + case EEXIST: + msgerr = "pathname already exists (not necessarily as a directory). This includes the case where pathname is a symbolic link, dangling or not."; + break; + case EFAULT: + msgerr = "pathname points outside your accessible address space."; + break; + case EINVAL: + msgerr = "The final component (basename) of the new directory's pathname is invalid (e.g., it contains characters not permitted by the underlying filesystem)."; + break; + case ELOOP: + msgerr = "Too many symbolic links were encountered in resolving pathname."; + break; + case EMLINK: + msgerr = "The number of links to the parent directory would exceed LINK_MAX."; + break; + case ENAMETOOLONG: + msgerr = "pathname was too long."; + case ENOENT: + msgerr = "A directory component in pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + msgerr = "Insufficient kernel memory was available."; + break; + case ENOSPC: + msgerr = "The new directory cannot be created because the user's disk quota is exhausted."; + break; + case ENOTDIR: + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EPERM: + msgerr = "The filesystem containing pathname does not support the creation of directories."; + break; + case EROFS: + msgerr = "pathname refers to a file on a read-only filesystem."; break; default: - errnomsg = "Generic error not specified by OS."; + msgerr = "Generic error not specified by OS."; break; } if (errno) //error is occurred - LOG(LOG_ERR, "Error removing \"%s\" errno[%d]: %s", fname, errno, errnomsg); //write detailed error message to log file + LOG(LOG_ERR, "Error creating dir \"%s\" errno[%d]: %s", path, errno, msgerr); //write detailed error message to log file return result; } @@ -194,6 +273,9 @@ bool isvalidfilename(const char *filename, bool wildcard) return result; //return true if filename its valid } +/* +returns the inode of filename +*/ static ino_t inodeof(const char *filename) { struct stat sb; @@ -204,10 +286,17 @@ static ino_t inodeof(const char *filename) return (ino_t)sb.st_ino; } +/* +try to open a new file with specfied mode, +check via inode if file is the same of source, +also check iffname already exist and ask for overwriting + +return file descriptor no if success, -1 in case of error or -2 if same inode +*/ static int opennew(const char *fname, mode_t mode, ino_t source_inode) { - int handle = -1L; - bool ok = true; + int fd = -1L; + bool ok = true; if (access(fname, F_OK) == 0) { //check if the target file already exists if (inodeof(fname) == source_inode) @@ -222,128 +311,129 @@ static int opennew(const char *fname, mode_t mode, ino_t source_inode) CLEAR_MESSAGE(); } if (ok) - handle = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); + fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); - return handle; + return fd; } +/* +copyng source file to destination dir using sendfile() with better performance because of kernel +in case of EINVAL or ENOSYS it try to use classic read()/write() funcs with best chunk of memory + +return 0 +*/ static ssize_t filecopy(const char *source, const char *todir) { int input, output; struct stat sb = { 0 }; - ssize_t result = -1L; - off_t chunk, bytesCount = 0; - char *errnomsg, *filename, *data, *ptr, *end, destination[FILENAME_MAX]; + ssize_t chunk, bytesCopied, bytesTocopy = 0; + char *msgerr, *data, *ptr, *end; bool trayAgain = false; /* Applications may wish to fall back to read(2)/write(2) in the case where sendfile() fails with EINVAL or ENOSYS. */ - filename = strdup(source); // duplicate because of basename() modify the string - sprintf(destination, "%s%s", todir, basename(filename)); // build complete destination path dir+fname - input = open(source, O_RDONLY); //try opening the source file if (input == -1) { LOG(LOG_ERR, "File open error: %s", source); - return result; + return -1L; } errno = 0; if (fstat(input, &sb)) { //read the attributes of the source file switch (errno) { case EACCES: - errnomsg = "Search permission is denied for one of the directories in the path prefix of source file."; + msgerr = "Search permission is denied for one of the directories in the path prefix of source file."; break; case EBADF: - errnomsg = "fd is not a valid open file descriptor."; + msgerr = "fd is not a valid open file descriptor."; break; case EFAULT: - errnomsg = "Bad address."; + msgerr = "Bad address."; break; case ELOOP: - errnomsg = "Too many symbolic links encountered while traversing the path."; + msgerr = "Too many symbolic links encountered while traversing the path."; break; case ENAMETOOLONG: - errnomsg = "Source pathname is too long."; + msgerr = "Source pathname is too long."; break; case ENOENT: - errnomsg = "A component of source pathname does not exist or is a dangling symbolic link."; + msgerr = "A component of source pathname does not exist or is a dangling symbolic link."; break; case ENOMEM: - errnomsg = "Out of memory (i.e., kernel memory)."; + msgerr = "Out of memory (i.e., kernel memory)."; break; case ENOTDIR: - errnomsg = "A component of the path prefix of pathname is not a directory."; + msgerr = "A component of the path prefix of pathname is not a directory."; break; case EOVERFLOW: - errnomsg = "Source pathname refers to a file whose size, inode number, or number of blocks cannot be represented in, respectively, the types off_t, ino_t, or blkcnt_t.\nThis error can occur when, for example, an application compiled on a 32-bit platform without -D_FILE_OFFSET_BITS=64 calls stat() on a file whose size exceeds (1<<31)-1 bytes."; + msgerr = "Source pathname refers to a file whose size, inode number, or number of blocks cannot be represented in, respectively, the types off_t, ino_t, or blkcnt_t.\nThis error can occur when, for example, an application compiled on a 32-bit platform without -D_FILE_OFFSET_BITS=64 calls stat() on a file whose size exceeds (1<<31)-1 bytes."; break; case 0: default: - errnomsg = NULL; + msgerr = NULL; break; } - LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", source, errno, errnomsg); + LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", source, errno, msgerr); message(RED, "Error reading info from source file. more info in \"%s.log\"", ROVER); rover_getch(); CLEAR_MESSAGE(); - return result; + return -1L; } - output = opennew(destination, sb.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source + output = opennew(todir, sb.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source if (output < 0) { //try to open the destinatiion file close(input); - errnomsg = (output == -1 ? "Error opening destination file" : "Destination file must be in a different directory of the source file"); + msgerr = (output == -1 ? "Error opening destination file" : "Destination file must be in a different directory of the source file"); - LOG(LOG_ERR, "%s", errnomsg); - message(RED, "%s", errnomsg); + LOG(LOG_ERR, "%s", msgerr); + message(RED, "%s", msgerr); rover_getch(); CLEAR_MESSAGE(); - return result; + return -1L; } - errno = 0; - bytesCount = sb.st_size; - result = sendfile(output, input, NULL, bytesCount); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ - + errno = 0; + bytesTocopy = sb.st_size; + bytesCopied = sendfile(output, input, NULL, bytesTocopy); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ switch (errno) { case EAGAIN: - errnomsg = "Nonblocking I/O has been selected using O_NONBLOCK and the write would block."; + msgerr = "Nonblocking I/O has been selected using O_NONBLOCK and the write would block."; break; case EBADF: - errnomsg = "The input file was not opened for reading or the output file was not opened for writing."; + msgerr = "The input file was not opened for reading or the output file was not opened for writing."; break; case EFAULT: - errnomsg = "Bad address."; + msgerr = "Bad address."; break; case EINVAL: case ENOSYS: - errnomsg = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; + msgerr = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; trayAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ break; case EIO: - errnomsg = "Unspecified error while reading from in_fd."; + msgerr = "Unspecified error while reading from in_fd."; break; case ENOMEM: - errnomsg = "Insufficient memory to read from in_fd."; + msgerr = "Insufficient memory to read from in_fd."; break; case EOVERFLOW: - errnomsg = "count is too large, the operation would result in exceeding the maximum size of either the input file or the output file."; + msgerr = "count is too large, the operation would result in exceeding the maximum size of either the input file or the output file."; break; case ESPIPE: - errnomsg = "offset is not NULL but the input file is not seekable."; + msgerr = "offset is not NULL but the input file is not seekable."; break; case 0: default: - errnomsg = NULL; + msgerr = NULL; break; } if (errno) //if an error has occurred - LOG(LOG_ERR, "File copy error errno[%d]: %s", errno, errnomsg); + LOG(LOG_ERR, "File copy error errno[%d]: %s", errno, msgerr); - if (result != sb.st_size) { //verify that all bytes have been copied - message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", result, sb.st_size); + if (bytesCopied != bytesTocopy) { //verify that all bytes have been copied + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", bytesCopied, bytesTocopy); rover_getch(); CLEAR_MESSAGE(); } @@ -351,23 +441,25 @@ static ssize_t filecopy(const char *source, const char *todir) if (trayAgain) { LOG(LOG_INFO, "%s try to copy again with read()/write() instead of sendfile().", ROVER); errno = 0; - chunk = MIN(sb.st_size, DEFAULT_CHUNK); //set better performance for copy - data = malloc((size_t)chunk); /* Allocate temporary data buffer. */ + chunk = MIN(bytesTocopy, DEFAULT_CHUNK); //set better performance for copy + data = malloc((size_t)chunk); // Allocate temporary data buffer. if (data) { - do { /* Copy loop. */ - bytesCount = read(input, data, chunk); - if (bytesCount <= 0) { - FREE(data); - break; - } - /* Write that same chunk. */ + bytesCopied = 0L; + do { // read/write loop + bytesTocopy = read(input, data, chunk); + if (bytesTocopy <= 0) + break; // exit from do while loop + ptr = data; - for (end = (char *)(data + bytesCount); ptr < end; ptr += bytesCount) { - bytesCount = write(output, ptr, (size_t)(end - ptr)); - if (bytesCount <= 0) { - trayAgain = false; // exit from do while - break; // exit from for + end = (char *)(data + bytesTocopy); + while (ptr < end) { //write data loop + bytesTocopy = write(output, ptr, (size_t)(end - ptr)); + if (bytesTocopy <= 0) { + trayAgain = false; // exit from do while loop + break; // exit from while loop } + bytesCopied += bytesTocopy; + ptr += bytesTocopy; } } while (trayAgain); FREE(data); @@ -375,31 +467,39 @@ static ssize_t filecopy(const char *source, const char *todir) LOG(LOG_INFO, "%s copy with success using read()/write() instead of sendfile().", ROVER); } } - close(input); //close the handle - close(output); //close the handle + close(input); //close the file descriptor + close(output); //close the file descriptor if (errno) - unlink(destination); /* Remove output file. */ + unlink(todir); // in case of error remove output file. - return result; //return the number of bytes copied + if (sb.st_size != bytesCopied) { // verify that all bytes have been copied + LOG(LOG_ERR, "Error not all data was copied! Writed %ld bytes on %ld bytes: errno[%d]", bytesCopied, sb.st_size, errno); + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", bytesCopied, sb.st_size); + rover_getch(); + CLEAR_MESSAGE(); + } + + return bytesCopied; //return the number of bytes copied } /* copy source file in CWD using filecopy() func +returns 0 if success */ int cpyfile(const char *srcpath) { char dstpath[PATH_MAX], buffer[PATH_MAX]; - ssize_t result = -1L; + ssize_t bytesTocopy; + int result; mode_t mode; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - mode = fileinfo(srcpath, NULL); + mode = fileinfo(srcpath, &bytesTocopy); if (!mode) - return (int)result; - - if (S_ISLNK(mode)) { + result = -2; + else if (S_ISLNK(mode)) { result = readlink(srcpath, buffer, PATH_MAX); if (result < 0) { message(RED, "Error reading symbolic link"); @@ -410,35 +510,58 @@ int cpyfile(const char *srcpath) result = symlink(buffer, dstpath); } } else if (S_ISREG(mode)) { - result = filecopy(srcpath, dstpath); + result = (filecopy(srcpath, dstpath) != bytesTocopy); } else { message(RED, "Error: source file must be a regular file or symbolic link"); rover_getch(); CLEAR_MESSAGE(); + result = -3; } - return (int)result; + return result; } /* -move source file in CWD using rename() or the combination of filecopy() + rm() funcs +move source file in CWD using rename() func +returns 0 if success */ int movfile(const char *srcpath) { - char dstpath[PATH_MAX]; + char dstpath[PATH_MAX], buffer[PATH_MAX]; + ssize_t bytesTocopy; int result; + mode_t mode; strcpy(dstpath, CWD); strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - errno = 0; - result = rename(srcpath, dstpath); - if (errno == EXDEV) { - filecopy(srcpath, dstpath); - result = rm(srcpath); + mode = fileinfo(srcpath, &bytesTocopy); + if (!mode) + result = -2; + else if (S_ISLNK(mode)) { + result = readlink(srcpath, buffer, PATH_MAX); + if (result < 0) { + message(RED, "Error reading symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + } else { + buffer[result] = '\0'; + result = symlink(buffer, dstpath); + result = rm(srcpath); + } + } else if (S_ISREG(mode)) { + errno = 0; + result = rename(srcpath, dstpath); + if (errno == EXDEV) { // see man page at https://man7.org/linux/man-pages/man2/rename.2.html + result = (filecopy(srcpath, dstpath) != bytesTocopy); + result = rm(srcpath); + } + } else { + message(RED, "Error: source file must be a regular file or symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + result = -3; } - if (!fileexist(dstpath)) - return -1; return result; } @@ -471,7 +594,7 @@ static int ls(Row **rowsp, uint8_t flags) for (n = 0; (ep = readdir(dp)); n++) if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - n--; // We don't want the entries "." and "..". + n--; // We don't want the entries "." and "..". if (n == 0) { closedir(dp); @@ -487,11 +610,11 @@ static int ls(Row **rowsp, uint8_t flags) if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') continue; - mode = fileinfo(ep->d_name, &size); + mode = fileinfo(ep->d_name, &size); rows[i].islink = (S_ISLNK(mode)); if (S_ISDIR(mode)) { if (flags & SHOW_DIRS) { - rows[i].name = (char *) malloc(strlen(ep->d_name) + 2); + rows[i].name = (char *)malloc(strlen(ep->d_name) + 2); strcpy(rows[i].name, ep->d_name); if (!rows[i].islink) ADDSLASH(rows[i].name); @@ -532,7 +655,7 @@ bool cd(bool reset) free_rows(&rover.rows, rover.nfiles); rover.nfiles = ls(&rover.rows, FLAGS); - if(rover.nfiles < 0) { + if (rover.nfiles < 0) { message(RED, "Error reading current directory"); rover_getch(); CLEAR_MESSAGE(); diff --git a/src/os_funcs.h b/src/os_funcs.h index c6f4526..67ad826 100644 --- a/src/os_funcs.h +++ b/src/os_funcs.h @@ -12,11 +12,13 @@ #include #include -#define DEFAULT_CHUNK 262144 /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ +#define DEFAULT_CHUNK ((ssize_t)262144L) /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ mode_t fileinfo(const char *fname, off_t *size); int fileexist(const char *fname); int rm(const char *fname); +int addfile(const char *path); +int adddir(const char *path); bool isvalidfilename(const char *filename, bool wildcard); int cpyfile(const char *srcpath); int movfile(const char *srcpath); diff --git a/src/rover.c b/src/rover.c index 7aaf358..cbf182b 100755 --- a/src/rover.c +++ b/src/rover.c @@ -43,7 +43,7 @@ int endsession(void) if (rover.nfiles) free_rows(&rover.rows, rover.nfiles); delwin(rover.window); -/* + /* curs_set(TRUE); keypad(stdscr, FALSE); intrflush(stdscr, TRUE); @@ -59,120 +59,6 @@ int endsession(void) return EXIT_SUCCESS; } -void update_view() -{ - int i, j, numsize, ishidden, marking, length, namecols, center, height; - char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; - wchar_t wbuffer[PATH_MAX]; - off_t human_size; - - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, buffer_one); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(wbuffer, CWD, PATH_MAX); - mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window - shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = (ENAME(j)[0] == '.'); - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - /* following a series of if else in order to set colors of listing */ - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - - if (S_ISDIR(EMODE(j))) { - mbstowcs(wbuffer, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(wbuffer, L"/"); - } else { - human_size = ESIZE(j) * 10; - length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); - namecols = wcswidth(wbuffer, length); - - for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - - if (*suffix == 'B') - swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", - (int)(COLS - namecols - 6), - (int)human_size / 10, *suffix); - else - swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", - (int)(COLS - namecols - 8), - (int)human_size / 10, (int)human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - - if (rover.nfiles > HEIGHT) { - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT - 1) * HEIGHT / rover.nfiles; - height = height ? height : 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); - } - buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - - if (!rover.nfiles) - strcpy(buffer_two, "0/0"); - else - snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); - - snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, buffer_one); - wrefresh(rover.window); -} - void free_rows(Row **rowsp, int nfiles) { for (int i = 0; i < nfiles; i++) @@ -206,65 +92,87 @@ void reload(void) cd(true); } -static off_t count_dir(const char *path) +/* Recursively process a source directory using CWD as destination root. + For each node (i.e. directory), do the following: + 1. call pre(destination); + 2. call proc() on every child leaf (i.e. files); + 3. recurse into every child node; + 4. call pos(source). + E.g. to move directory /src/ (and all its contents) inside /dst/: + strcpy(CWD, "/dst/"); + process_dir(adddir, movfile, rm, "/src/"); */ +static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) { + int ret = 0; DIR *dp; struct dirent *ep; - char subpath[PATH_MAX]; - off_t total = 0L, size = -1L; + char subpath[PATH_MAX], dstpath[PATH_MAX]; - if (!(dp = opendir(path))) - return total; + if (pre) { + strcpy(dstpath, CWD); + strcat(dstpath, path + strlen(rover.marks.dirpath)); + ret |= pre(dstpath); + } + + dp = opendir(path); + if (dp == NULL) + return -1; while ((ep = readdir(dp))) { if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - if (S_ISDIR(fileinfo(subpath, &size))) { + snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); + if (S_ISDIR(fileinfo(subpath, NULL))) { ADDSLASH(subpath); - total += count_dir(subpath); + ret |= process_dir(pre, proc, pos, subpath); } else - total += size; + ret |= proc(subpath); } closedir(dp); - return total; + if (pos) + ret |= pos(path); + + return ret; } -off_t count_marked() +/* Process all marked entries using CWD as destination root. + All marked entries that are directories will be recursively processed. + See process_dir() for details on the parameters. */ +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) { + int i, ret; char *entry; - off_t total = 0L, size = -1L; + char path[PATH_MAX]; - chdir(rover.marks.dirpath); - for (int i = 0; i < rover.marks.bulk; i++) { + CLEAR_MESSAGE(); + message(CYAN, "%s...", msg_doing); + refresh(); + for (i = 0; i < rover.marks.bulk; i++) { entry = rover.marks.entries[i]; if (entry) { + ret = 0; + snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); if (ISDIR(entry)) { - total += count_dir(entry); + if (!strncmp(path, CWD, strlen(path))) + ret = -1; + else + ret = process_dir(pre, proc, pos, path); } else { - fileinfo(entry, &size); - total += size; + ret = proc(path); + } + + if (!ret) { + del_mark(&rover.marks, entry); + reload(); } } } - chdir(CWD); - - return total; -} - -void update_progress(off_t delta) -{ - int percent; - - if (!rover.prog.total) - return; - - rover.prog.partial += delta; - percent = (int)(rover.prog.partial * 100 / rover.prog.total); - message(CYAN, "%s...%d%%", rover.prog.msg, percent); - refresh(); - - return; + reload(); + if (rover.marks.nentries) + message(RED, "Some errors occured while %s.", msg_doing); + else + message(GREEN, "%s all marked entries.", msg_done); + RV_ALERT(); } diff --git a/src/rover.h b/src/rover.h index 717c9b4..a03995a 100644 --- a/src/rover.h +++ b/src/rover.h @@ -179,10 +179,8 @@ extern char rover_home_path[RV_PATH_MAX]; void logfile(const char *format, ...); int endsession(void); void reload(void); -void update_view(void); void free_rows(Row **rowsp, int nfiles); void try_to_sel(const char *target); -off_t count_marked(void); -void update_progress(off_t delta); +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done); #endif // _ROVER_H \ No newline at end of file diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 4326fd4..60f38e8 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -130,6 +130,120 @@ static void start_line_edit(const char *init_input) rover.edit_scroll = 0; } +void update_view() +{ + int i, j, numsize, ishidden, marking, length, namecols, center, height; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; + wchar_t wbuffer[PATH_MAX]; + off_t human_size; + + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, buffer_one); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window + shrinking. In that case, the scroll must be moved to make it visible. */ + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = (ENAME(j)[0] == '.'); + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + /* following a series of if else in order to set colors of listing */ + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + + if (S_ISDIR(EMODE(j))) { + mbstowcs(wbuffer, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(wbuffer, L"/"); + } else { + human_size = ESIZE(j) * 10; + length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + namecols = wcswidth(wbuffer, length); + + for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + + if (*suffix == 'B') + swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", + (int)(COLS - namecols - 6), + (int)human_size / 10, *suffix); + else + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", + (int)(COLS - namecols - 8), + (int)human_size / 10, (int)human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); + + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); + } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + + if (rover.nfiles > HEIGHT) { + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT - 1) * HEIGHT / rover.nfiles; + height = height ? height : 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); + } + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + + if (!rover.nfiles) + strcpy(buffer_two, "0/0"); + else + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + + snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); + wrefresh(rover.window); +} + /* Handle any signals received since last call. */ static void sync_signals(void) { @@ -172,12 +286,12 @@ static EditStat get_line_edit(char *string) ch = rover_getch(); switch (ch) { case KEY_ENTER: - case KEY_RETURN: + case KEY_RETURN: curs_set(FALSE); return CONFIRM; break; case KEY_ESC: - if(strlen(string)) { + if (strlen(string)) { EDIT_CLEAR(rover.edit); CLEAR_MESSAGE(); } else { @@ -217,7 +331,7 @@ static EditStat get_line_edit(char *string) if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit); break; - case KEY_CANCEL: + case KEY_CANCEL: break; case KEY_F(1)... KEY_F(12): case KEY_IC: @@ -236,28 +350,6 @@ static EditStat get_line_edit(char *string) return CONTINUE; } -static int addfile(const char *path) -{ /* Using creat(2) because mknod(2) doesn't seem to be portable. */ - int ret; - - ret = creat(path, 0644); - if (ret < 0) - return ret; - - return close(ret); -} - -static int adddir(const char *path) -{ - mode_t mode; - - mode = fileinfo(CWD, NULL); - if (!mode) - return -1; - - return mkdir(path, mode); -} - /* Unmark all entries. */ static void mark_none(Marks *marks) { @@ -324,89 +416,6 @@ void del_mark(Marks *marks, char *entry) mark_none(marks); } -/* Recursively process a source directory using CWD as destination root. - For each node (i.e. directory), do the following: - 1. call pre(destination); - 2. call proc() on every child leaf (i.e. files); - 3. recurse into every child node; - 4. call pos(source). - E.g. to move directory /src/ (and all its contents) inside /dst/: - strcpy(CWD, "/dst/"); - process_dir(adddir, movfile, rm, "/src/"); */ -static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) -{ - int ret; - DIR *dp; - struct dirent *ep; - char subpath[PATH_MAX], dstpath[PATH_MAX]; - - ret = 0; - if (pre) { - strcpy(dstpath, CWD); - strcat(dstpath, path + strlen(rover.marks.dirpath)); - ret |= pre(dstpath); - } - - if (!(dp = opendir(path))) - return -1; - - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - if (S_ISDIR(fileinfo(subpath, NULL))) { - ADDSLASH(subpath); - ret |= process_dir(pre, proc, pos, subpath); - } else - ret |= proc(subpath); - } - closedir(dp); - if (pos) - ret |= pos(path); - - return ret; -} - -/* Process all marked entries using CWD as destination root. - All marked entries that are directories will be recursively processed. - See process_dir() for details on the parameters. */ -static void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) -{ - int i, ret; - char *entry; - char path[PATH_MAX]; - - CLEAR_MESSAGE(); - message(CYAN, "%s...", msg_doing); - refresh(); - rover.prog = (Prog){ 0, count_marked(), msg_doing }; // init progress - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - ret = 0; - snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); - if (ISDIR(entry)) { - if (!strncmp(path, CWD, strlen(path))) - ret = -1; - else - ret = process_dir(pre, proc, pos, path); - } else - ret = proc(path); - if (!ret) { - del_mark(&rover.marks, entry); - reload(); - } - } - } - rover.prog.total = 0; // reset progress - reload(); - if (!rover.marks.nentries) - message(GREEN, "%s all marked entries.", msg_done); - else - message(RED, "Some errors occured while %s.", msg_doing); - RV_ALERT(); -} - void main_menu(void) { int i, ch, oldsel, oldscroll, length, ok, sel; @@ -527,15 +536,15 @@ void main_menu(void) continue; } mode = fileinfo(buffer, NULL); - strcpy(CWD, buffer); + strcpy(CWD, buffer); if (S_ISREG(mode)) { strcpy(CWD, buffer); - strcpy(buffer, strrchr(CWD, '/') +1 ); + strcpy(buffer, strrchr(CWD, '/') + 1); dirname(CWD); ADDSLASH(CWD); } cd(true); - if(S_ISREG(mode)) + if (S_ISREG(mode)) try_to_sel(buffer); if (rover.nfiles > HEIGHT) SCROLL = ESEL - HEIGHT / 2; @@ -699,7 +708,7 @@ void main_menu(void) } } break; - case KEY_F(10): //Create new dir + case KEY_F(12): //Create new dir start_line_edit(""); DELSLASH(input); update_input(RVP_NEW_DIR, YELLOW, input); @@ -785,16 +794,20 @@ void main_menu(void) case KEY_F(4): //Toggle execute permission of the selected file if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; + if (S_IXUSR & EMODE(ESEL)) EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); else EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; + if (chmod(ENAME(ESEL), EMODE(ESEL))) { message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); } else { message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); update_view(); } + rover_getch(); + CLEAR_MESSAGE(); break; case KEY_DC: //Delete selected file or (empty) directory if (rover.nfiles) { @@ -805,30 +818,26 @@ void main_menu(void) del_mark(&rover.marks, ENAME(ESEL)); ok = rm(buffer); reload(); - if (ok == 0) { - CLEAR_MESSAGE(); - message(YELLOW, "\"%s\" deleted!", buffer); - rover_getch(); - } else { - CLEAR_MESSAGE(); + CLEAR_MESSAGE(); + if (ok == 0) + message(GREEN, "\"%s\" deleted!", buffer); + else message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer, ROVER); //print error message - rover_getch(); - CLEAR_MESSAGE(); - } + rover_getch(); } - CLEAR_MESSAGE(); } else { message(RED, "No entry for deletion."); rover_getch(); } + CLEAR_MESSAGE(); break; case KEY_SPACE: //Toggle mark on the selected entry if (MARKED(ESEL)) del_mark(&rover.marks, ENAME(ESEL)); else add_mark(&rover.marks, CWD, ENAME(ESEL)); - MARKED(ESEL) = !MARKED(ESEL); - ESEL = (ESEL + 1) % rover.nfiles; + MARKED(ESEL) = !MARKED(ESEL); //toggle mark + ESEL = (ESEL + 1);// % rover.nfiles; // if remove comment to the modulo the selection restart from first element of listing update_view(); break; case KEY_CTRL_S: //Toggle mark on all visible entries @@ -849,6 +858,14 @@ void main_menu(void) } update_view(); break; + case KEY_CTRL_D: //Unmark all visible entries + for (i = 0; i < rover.nfiles; i++) + if (MARKED(i)) { + del_mark(&rover.marks, ENAME(i)); + MARKED(i) = false; + } + update_view(); + break; case 'X': //Delete all marked entries if (rover.marks.nentries) { message(YELLOW, "Delete all marked entries? (Y/n)"); @@ -878,7 +895,8 @@ void main_menu(void) message(RED, "No entries marked for moving."); break; default: - LOG(LOG_INFO, "0x%02X", ch); + RV_ALERT(); + break; } } while (!quit); diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 8350420..33213c4 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -54,16 +54,16 @@ #define KEY_ESC 0x1B #endif #ifndef KEY_TAB -#define KEY_TAB 0x9 +#define KEY_TAB 0x09 #endif #ifndef KEY_RETURN -#define KEY_RETURN 0xD +#define KEY_RETURN 0x0D #endif #ifndef KEY_CTRL_DEL #define KEY_CTRL_DEL 0x207 #endif #ifndef KEY_CTRL_BS -#define KEY_CTRL_BS 0x8 +#define KEY_CTRL_BS 0x08 #endif #ifndef KEY_CTRL_LEFT #define KEY_CTRL_LEFT 0x221 @@ -75,7 +75,7 @@ #define KEY_CTRL_X 0x18 #endif #ifndef KEY_CTRL_C -#define KEY_CTRL_C 0x3 +#define KEY_CTRL_C 0x03 #endif #ifndef KEY_CTRL_V #define KEY_CTRL_V 0x10 @@ -86,6 +86,9 @@ #ifndef KEY_CTRL_S #define KEY_CTRL_S 0x13 #endif +#ifndef KEY_CTRL_D +#define KEY_CTRL_D 0x04 +#endif /* Line Editing Macros. */ #define EDIT_FULL(E) ((E).left == (E).right) @@ -101,7 +104,7 @@ (E).left = 0; \ (E).right = PATH_MAX - 1; \ } - + /* Get user programs from the environment vars */ #define ROVER_ENV(dst, src) \ { \ @@ -144,6 +147,7 @@ void handlers(bool enable); int rover_getch(void); void message(Color color, char *fmt, ...); void update_view(void); +void del_mark(Marks *marks, char *entry); void main_menu(void); #endif // _UI_FUNCS_H \ No newline at end of file From 515d8810c1c91a158f4597c594a9fac82ba18763 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 6 Jan 2023 11:52:17 +0100 Subject: [PATCH 15/18] improved file style and fix some bugs --- rover.1 | 7 +++++-- src/main.c | 18 ++++++++--------- src/os_funcs.c | 54 ++++++++++++++++++++++++++++++-------------------- src/rover.c | 29 +++++++++++++++------------ src/rover.h | 3 +++ src/ui_funcs.c | 53 +++++++++++++++++++++++++++---------------------- src/ui_funcs.h | 2 +- 7 files changed, 95 insertions(+), 71 deletions(-) diff --git a/rover.1 b/rover.1 index 3e7aaee..040de16 100755 --- a/rover.1 +++ b/rover.1 @@ -75,6 +75,9 @@ pointed to the "source" directory of the operation and then issue the command on another tab that is pointed to the "destination" directory. .SH COMMANDS .TP +.B or ? +Show this manual. +.TP .B Quit rover. .TP @@ -226,9 +229,9 @@ will not use or create any external file during its execution, except when asked to do so by user commands or command-line options. .SH NOTES .PP -\fBImportant\fR: Currently, Rover never asks for confirmation before overwriting +\fBImportant\fR: In this version, Rover asks for confirmation before overwriting existing files while copying/moving marked entries. Please be careful to not -accidentally lose your data. +accidentally lose your data anyway. .SH LINKS Rover homepage: . .SH SEE ALSO diff --git a/src/main.c b/src/main.c index e8afc4d..c3bebfa 100755 --- a/src/main.c +++ b/src/main.c @@ -11,14 +11,14 @@ static void init_term(void) setlocale(LC_ALL, ""); initscr(); - cbreak(); /* Get one character at a time. */ - nodelay(stdscr, TRUE); /* For getch(). */ + cbreak(); // Get one character at a time. + nodelay(stdscr, TRUE); // For getch(). noecho(); - nonl(); /* No NL->CR/NL on output. */ + nonl(); // No NL->CR/NL on output. intrflush(stdscr, FALSE); keypad(stdscr, TRUE); - curs_set(FALSE); /* Hide blinking cursor. */ - raw(); + curs_set(FALSE); // Hide blinking cursor. + raw(); //to read Ctrl+C and so on if (has_colors()) { short bg; @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { printf("rover %s\n", RV_VERSION); - return 0; + return EXIT_SUCCESS; } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { printf( "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) "See rover(1) for more information.\n" "Rover homepage: .\n"); - return 0; + return EXIT_SUCCESS; } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { if (argc > 2) { save_cwd_file = fopen(argv[2], "w"); @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) } else { fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; + return EXIT_FAILURE; } } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { if (argc > 2) { @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) } else { fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; + return EXIT_FAILURE; } } } diff --git a/src/os_funcs.c b/src/os_funcs.c index 6d48389..4306067 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -139,6 +139,9 @@ int rm(const char *fname) case EISDIR: msgerr = "pathname refers to a directory, and AT_REMOVEDIR was not specified in flags."; break; + case ENOTEMPTY: + msgerr = "Dirctory not empty."; + break; default: msgerr = "Generic error not specified by OS."; break; @@ -295,14 +298,14 @@ return file descriptor no if success, -1 in case of error or -2 if same inode */ static int opennew(const char *fname, mode_t mode, ino_t source_inode) { - int fd = -1L; + int fd = -3; bool ok = true; if (access(fname, F_OK) == 0) { //check if the target file already exists if (inodeof(fname) == source_inode) return -2; - message(RED, "Warning destination file already exist. Overwrite (Y/n)?"); + message(RED, "Warning: \"%s\" already exist. Overwrite (Y/n)?", FILENAME(fname); if (rover_getch() == 'Y') rm(fname); else @@ -317,12 +320,12 @@ static int opennew(const char *fname, mode_t mode, ino_t source_inode) } /* -copyng source file to destination dir using sendfile() with better performance because of kernel +copyng srcfile file to destination dir using sendfile() with better performance because of kernel in case of EINVAL or ENOSYS it try to use classic read()/write() funcs with best chunk of memory -return 0 +return total bytes copied if success, -1L in case of error, -2L if srcfile == dstfile, -3L user abort overwriting */ -static ssize_t filecopy(const char *source, const char *todir) +static ssize_t filecopy(const char *srcfile, const char *dstfile) { int input, output; struct stat sb = { 0 }; @@ -331,18 +334,18 @@ static ssize_t filecopy(const char *source, const char *todir) bool trayAgain = false; /* Applications may wish to fall back to read(2)/write(2) in the case where sendfile() fails with EINVAL or ENOSYS. */ - input = open(source, O_RDONLY); //try opening the source file + input = open(srcfile, O_RDONLY); //try opening the srcfile file if (input == -1) { - LOG(LOG_ERR, "File open error: %s", source); + LOG(LOG_ERR, "File open error: %s", srcfile); return -1L; } errno = 0; - if (fstat(input, &sb)) { //read the attributes of the source file + if (fstat(input, &sb)) { //read the attributes of the srcfile file switch (errno) { case EACCES: - msgerr = "Search permission is denied for one of the directories in the path prefix of source file."; + msgerr = "Search permission is denied for one of the directories in the path prefix of srcfile file."; break; case EBADF: msgerr = "fd is not a valid open file descriptor."; @@ -373,7 +376,7 @@ static ssize_t filecopy(const char *source, const char *todir) msgerr = NULL; break; } - LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", source, errno, msgerr); + LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", srcfile, errno, msgerr); message(RED, "Error reading info from source file. more info in \"%s.log\"", ROVER); rover_getch(); CLEAR_MESSAGE(); @@ -381,17 +384,24 @@ static ssize_t filecopy(const char *source, const char *todir) return -1L; } - output = opennew(todir, sb.st_mode, inodeof(source)); //try to open the destinatiion file with same mode of source + output = opennew(dstfile, sb.st_mode, inodeof(srcfile)); //try to open the destinatiion file with same mode of srcfile if (output < 0) { //try to open the destinatiion file close(input); - msgerr = (output == -1 ? "Error opening destination file" : "Destination file must be in a different directory of the source file"); + if (output == -1) + msgerr = "Error opening destination file"; + else if (output == -2) + msgerr = "Destination file must be in a different directory of the source file"; + else + msgerr = NULL; - LOG(LOG_ERR, "%s", msgerr); - message(RED, "%s", msgerr); - rover_getch(); - CLEAR_MESSAGE(); + if (msgerr) { + LOG(LOG_ERR, "%s", msgerr); + message(RED, "%s", msgerr); + rover_getch(); + CLEAR_MESSAGE(); + } - return -1L; + return (ssize_t)output; } errno = 0; @@ -470,7 +480,7 @@ static ssize_t filecopy(const char *source, const char *todir) close(input); //close the file descriptor close(output); //close the file descriptor if (errno) - unlink(todir); // in case of error remove output file. + unlink(dstfile); // in case of error remove output file. if (sb.st_size != bytesCopied) { // verify that all bytes have been copied LOG(LOG_ERR, "Error not all data was copied! Writed %ld bytes on %ld bytes: errno[%d]", bytesCopied, sb.st_size, errno); @@ -489,7 +499,7 @@ returns 0 if success int cpyfile(const char *srcpath) { char dstpath[PATH_MAX], buffer[PATH_MAX]; - ssize_t bytesTocopy; + ssize_t bytesTocopy, bytesCopied; int result; mode_t mode; @@ -510,14 +520,14 @@ int cpyfile(const char *srcpath) result = symlink(buffer, dstpath); } } else if (S_ISREG(mode)) { - result = (filecopy(srcpath, dstpath) != bytesTocopy); + bytesCopied = filecopy(srcpath, dstpath); + result = (int)((bytesCopied == bytesTocopy) ? false : bytesCopied); } else { message(RED, "Error: source file must be a regular file or symbolic link"); rover_getch(); CLEAR_MESSAGE(); - result = -3; + result = -4; } - return result; } diff --git a/src/rover.c b/src/rover.c index cbf182b..27cc33a 100755 --- a/src/rover.c +++ b/src/rover.c @@ -103,7 +103,7 @@ void reload(void) process_dir(adddir, movfile, rm, "/src/"); */ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) { - int ret = 0; + int result = 0; DIR *dp; struct dirent *ep; char subpath[PATH_MAX], dstpath[PATH_MAX]; @@ -111,7 +111,7 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) if (pre) { strcpy(dstpath, CWD); strcat(dstpath, path + strlen(rover.marks.dirpath)); - ret |= pre(dstpath); + result |= pre(dstpath); } dp = opendir(path); @@ -125,16 +125,16 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); if (S_ISDIR(fileinfo(subpath, NULL))) { ADDSLASH(subpath); - ret |= process_dir(pre, proc, pos, subpath); + result |= process_dir(pre, proc, pos, subpath); } else - ret |= proc(subpath); + result |= proc(subpath); } closedir(dp); if (pos) - ret |= pos(path); + result |= pos(path); - return ret; + return result; } /* Process all marked entries using CWD as destination root. @@ -142,7 +142,7 @@ static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) See process_dir() for details on the parameters. */ void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) { - int i, ret; + int i, result; char *entry; char path[PATH_MAX]; @@ -152,27 +152,30 @@ void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doin for (i = 0; i < rover.marks.bulk; i++) { entry = rover.marks.entries[i]; if (entry) { - ret = 0; + result = 0; snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); if (ISDIR(entry)) { if (!strncmp(path, CWD, strlen(path))) - ret = -1; + result = -1; else - ret = process_dir(pre, proc, pos, path); + result = process_dir(pre, proc, pos, path); } else { - ret = proc(path); + result = proc(path); } - if (!ret) { + if (!result) { del_mark(&rover.marks, entry); reload(); } } } reload(); - if (rover.marks.nentries) + if(result == -3) + message(YELLOW, "%s aborted.", msg_doing) ; + else if (result < 0) //rover.marks.nentries message(RED, "Some errors occured while %s.", msg_doing); else message(GREEN, "%s all marked entries.", msg_done); + RV_ALERT(); } diff --git a/src/rover.h b/src/rover.h index a03995a..615bf07 100644 --- a/src/rover.h +++ b/src/rover.h @@ -138,6 +138,9 @@ struct Rover { #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define ISDIR(E) (strchr((E), '/') != NULL) +/* Get file or dir name without path */ +#define FILENAME(_path) (strrchr(_path, '/') + 1) + /* Check if not root dir than add / at the end of path */ #define ADDSLASH(_path) \ { \ diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 60f38e8..0430e17 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -509,7 +509,7 @@ void main_menu(void) if (!strcmp(CWD, "/")) continue; DELSLASH(CWD); - strcpy(buffer, strrchr(CWD, '/') + 1); //copy the cwd for try_to_sel + strcpy(buffer, FILENAME(CWD)); //copy the cwd for try_to_sel ADDSLASH(buffer); dirname(CWD); ADDSLASH(CWD); @@ -539,7 +539,7 @@ void main_menu(void) strcpy(CWD, buffer); if (S_ISREG(mode)) { strcpy(CWD, buffer); - strcpy(buffer, strrchr(CWD, '/') + 1); + strcpy(buffer, FILENAME(CWD)); dirname(CWD); ADDSLASH(CWD); } @@ -809,28 +809,6 @@ void main_menu(void) rover_getch(); CLEAR_MESSAGE(); break; - case KEY_DC: //Delete selected file or (empty) directory - if (rover.nfiles) { - message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); - if (rover_getch() == 'Y') { - strcpy(buffer, ENAME(ESEL)); - if (MARKED(ESEL)) - del_mark(&rover.marks, ENAME(ESEL)); - ok = rm(buffer); - reload(); - CLEAR_MESSAGE(); - if (ok == 0) - message(GREEN, "\"%s\" deleted!", buffer); - else - message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer, ROVER); //print error message - rover_getch(); - } - } else { - message(RED, "No entry for deletion."); - rover_getch(); - } - CLEAR_MESSAGE(); - break; case KEY_SPACE: //Toggle mark on the selected entry if (MARKED(ESEL)) del_mark(&rover.marks, ENAME(ESEL)); @@ -866,6 +844,32 @@ void main_menu(void) } update_view(); break; + case KEY_DC: //Delete selected file or (empty) directory + if (rover.marks.nentries) { + message(YELLOW, "There is marked entries, to delete them use X"); + rover_getch(); + CLEAR_MESSAGE(); + } + if (rover.nfiles) { + message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); + if (rover_getch() == 'Y') { + strcpy(buffer, ENAME(ESEL)); + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + ok = rm(buffer); + reload(); + CLEAR_MESSAGE(); + if (ok == 0) + message(GREEN, "\"%s\" deleted!", buffer); + else + message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer, ROVER); //print error message + } + } else + message(RED, "No entry for deletion."); + + rover_getch(); + CLEAR_MESSAGE(); + break; case 'X': //Delete all marked entries if (rover.marks.nentries) { message(YELLOW, "Delete all marked entries? (Y/n)"); @@ -895,6 +899,7 @@ void main_menu(void) message(RED, "No entries marked for moving."); break; default: + //LOG(LOG_INFO, "keypressed [0x%02x]", ch); // Used for DEBUG RV_ALERT(); break; } diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 33213c4..245afa4 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -78,7 +78,7 @@ #define KEY_CTRL_C 0x03 #endif #ifndef KEY_CTRL_V -#define KEY_CTRL_V 0x10 +#define KEY_CTRL_V 0x16 #endif #ifndef KEY_CTRL_A #define KEY_CTRL_A 0x01 From 6c9dc492c4e0db5d74af4ca00fce0ff3a38e49e3 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 6 Jan 2023 19:16:18 +0100 Subject: [PATCH 16/18] improve style and performances also command arguments with getopt_long() --- rover.1 | 4 +- src/main.c | 158 +++++++++++++++++++------------------------------ src/os_funcs.c | 2 +- src/rover.c | 38 ++++++++++++ src/rover.h | 42 ++++++++----- src/ui_funcs.c | 21 +++++++ src/ui_funcs.h | 7 ++- 7 files changed, 157 insertions(+), 115 deletions(-) diff --git a/rover.1 b/rover.1 index 040de16..aedaa5e 100755 --- a/rover.1 +++ b/rover.1 @@ -3,7 +3,7 @@ rover \- file browser for the terminal .SH SYNOPSIS .B rover -[\fB\-d\fR|\fB\-\-save\-cwd\fR \fIFILE\fR] +[\fB\-c\fR|\fB\-\-save\-cwd\fR \fIFILE\fR] [\fB\-m\fR|\fB\-\-save\-marks\fR \fIFILE\fR] [\fIDIR\fR [\fIDIR\fR [\fIDIR\fR [...]]]] .br @@ -16,7 +16,7 @@ rover \- file browser for the terminal Browse current working directory or the ones specified. .SH OPTIONS .TP -\fB\-d\fR, \fB\-\-save\-cwd\fR +\fB\-c\fR, \fB\-\-save\-cwd\fR write last visited path to \fIFILE\fR before exiting .TP \fB\-m\fR, \fB\-\-save\-marks\fR diff --git a/src/main.c b/src/main.c index c3bebfa..257adf3 100755 --- a/src/main.c +++ b/src/main.c @@ -5,125 +5,91 @@ struct Rover rover; char rover_home_path[RV_PATH_MAX]; -/* Curses setup. */ -static void init_term(void) -{ - setlocale(LC_ALL, ""); - initscr(); - - cbreak(); // Get one character at a time. - nodelay(stdscr, TRUE); // For getch(). - noecho(); - nonl(); // No NL->CR/NL on output. - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - curs_set(FALSE); // Hide blinking cursor. - raw(); //to read Ctrl+C and so on - - if (has_colors()) { - short bg; - start_color(); - -#ifdef NCURSES_EXT_FUNCS - use_default_colors(); - bg = -1; -#else - bg = COLOR_BLACK; -#endif - init_pair(RED, COLOR_RED, bg); - init_pair(GREEN, COLOR_GREEN, bg); - init_pair(YELLOW, COLOR_YELLOW, bg); - init_pair(BLUE, COLOR_BLUE, bg); - init_pair(CYAN, COLOR_CYAN, bg); - init_pair(MAGENTA, COLOR_MAGENTA, bg); - init_pair(WHITE, COLOR_WHITE, bg); - init_pair(BLACK, COLOR_BLACK, bg); - } - atexit((void (*)(void))endsession); - handlers(true); //enable handlers -} - -static void init_marks(Marks *marks) -{ - strcpy(marks->dirpath, ""); - marks->bulk = BULK_INIT; - marks->nentries = 0; - marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); -} - -static void free_marks(Marks *marks) -{ - int i; - - for (i = 0; marks->nentries && i < marks->bulk; i++) - if (marks->entries[i]) { - FREE(marks->entries[i]); - marks->nentries--; - } - - FREE(marks->entries); -} - int main(int argc, char *argv[]) { - int i; + int i, opt, long_index = 0; char clipboard[PATH_MAX]; char *entry; DIR *d; FILE *save_cwd_file = NULL, *save_marks_file = NULL; - - if (argc >= 2) { - if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { + static struct option long_options[] = { + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { "save-cwd", required_argument, NULL, 'c' }, + { "save-marks", required_argument, NULL, 'm' }, + { NULL, 0, NULL, 0 } + }; + + while ((opt = getopt_long(argc, argv, "vhc:m:", long_options, &long_index)) != -1) { + switch (opt) { + case 'v': printf("rover %s\n", RV_VERSION); return EXIT_SUCCESS; - } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { - printf( - "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" - " Browse current directory or the ones specified.\n\n" - " or: rover -h|--help\n" - " Print this help message and exit.\n\n" - " or: rover -v|--version\n" - " Print program version and exit.\n\n" - "See rover(1) for more information.\n" - "Rover homepage: .\n"); + break; + case '?': // in case of missing arguments + if (save_cwd_file) + fclose(save_cwd_file); + if (save_marks_file) + fclose(save_marks_file); + fprintf(stderr, "read following instructions:\n"); + getchar(); + case 'h': + puts("rover - file browser for the terminal\n\n" + "SYNOPSIS\n" + "\trover [-c|--save-cwd FILE] [-m|--save-marks FILE] [DIR [DIR [DIR [...]]]]\n" + "\trover -h|--help\n" + "\trover -v|--version\n\n" + "DESCRIPTION\n" + "\tRover browse current working directory or the ones specified.\n\n" + "OPTIONS\n" + "\t-c, --save-cwd\n" + "\t\twrite last visited path to FILE before exiting.\n\n" + "\t-m, --save-marks\n" + "\t\tappend path of marked entries to FILE before exiting; if FILE doesn't exist, it'll be created.\n\n" + "\t-h, --help\n" + "\t\tprint help message and exit.\n\n" + "\t-v, --version\n" + "\t\tprint program version and exit.\n"); return EXIT_SUCCESS; - } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { - if (argc > 2) { - save_cwd_file = fopen(argv[2], "w"); - argc -= 2; - argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); + break; + case 'c': + if (isvalidfilename(optarg, false)) + save_cwd_file = fopen(optarg, "w"); + else { + LOG(LOG_ERR, "\"%s\" invalid filename to store last working directory.", optarg); + fprintf(stderr, "\"%s\" invalid filename to store last working directory.\n", optarg); return EXIT_FAILURE; } - } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { - if (argc > 2) { - save_marks_file = fopen(argv[2], "a"); - argc -= 2; - argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); + break; + case 'm': + if (isvalidfilename(optarg, false)) + save_marks_file = fopen(optarg, "a"); + else { + LOG(LOG_ERR, "\"%s\" invalid filename to store last marked entries.", optarg); + fprintf(stderr, "\"%s\" invalid filename to store last marked entries.\n", optarg); return EXIT_FAILURE; } + break; + default: + LOG(LOG_ERR, "getopt_long() Invalid arguments/options\n"); + fprintf(stderr, "Invalid arguments/options\n"); + return EXIT_FAILURE; + break; } } init_term(); - rover.nfiles = 0; - for (i = 0; i < 10; i++) { - rover.tabs[i].esel = 0; - rover.tabs[i].scroll = 0; - rover.tabs[i].flags = RV_FLAGS; - } + INIT_ROVER(rover); //initialize rover struct strcpy(rover.tabs[0].cwd, getenv("HOME")); - for (i = 1; i < argc && i < 10; i++) { - if ((d = opendir(argv[i]))) { - realpath(argv[i], rover.tabs[i].cwd); + opt = (argc - optind); + for (i = 0; i < opt && i < 10; i++) { + if ((d = opendir(argv[optind + i]))) { + realpath(argv[optind + i], rover.tabs[i].cwd); closedir(d); } else strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); diff --git a/src/os_funcs.c b/src/os_funcs.c index 4306067..3af15ae 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -305,7 +305,7 @@ static int opennew(const char *fname, mode_t mode, ino_t source_inode) if (inodeof(fname) == source_inode) return -2; - message(RED, "Warning: \"%s\" already exist. Overwrite (Y/n)?", FILENAME(fname); + message(RED, "Warning: \"%s\" already exist. Overwrite (Y/n)?", FILENAME(fname)); if (rover_getch() == 'Y') rm(fname); else diff --git a/src/rover.c b/src/rover.c index 27cc33a..2ad0b99 100755 --- a/src/rover.c +++ b/src/rover.c @@ -36,6 +36,44 @@ void logfile(const char *format, ...) return; } +/* Curses setup. */ +void init_term(void) +{ + setlocale(LC_ALL, ""); + initscr(); + + cbreak(); // Get one character at a time. + nodelay(stdscr, TRUE); // For getch(). + noecho(); + nonl(); // No NL->CR/NL on output. + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); // Hide blinking cursor. + raw(); //to read Ctrl+C and so on + + if (has_colors()) { + short bg; + start_color(); + +#ifdef NCURSES_EXT_FUNCS + use_default_colors(); + bg = -1; +#else + bg = COLOR_BLACK; +#endif + init_pair(RED, COLOR_RED, bg); + init_pair(GREEN, COLOR_GREEN, bg); + init_pair(YELLOW, COLOR_YELLOW, bg); + init_pair(BLUE, COLOR_BLUE, bg); + init_pair(CYAN, COLOR_CYAN, bg); + init_pair(MAGENTA, COLOR_MAGENTA, bg); + init_pair(WHITE, COLOR_WHITE, bg); + init_pair(BLACK, COLOR_BLACK, bg); + } + atexit((void (*)(void))endsession); + handlers(true); //enable handlers +} + /* atexit func */ int endsession(void) { diff --git a/src/rover.h b/src/rover.h index 615bf07..de36cfb 100644 --- a/src/rover.h +++ b/src/rover.h @@ -32,7 +32,7 @@ #include #include -#define RV_VERSION "2.0.1" +#define RV_VERSION "2.0.1" //Sandroid75 /* Special symbols used by the TUI. See for available constants. */ #define RVS_SCROLLBAR ACS_CKBOARD @@ -49,9 +49,6 @@ May include SHOW_FILES, SHOW_DIRS and SHOW_HIDDEN. */ #define RV_FLAGS SHOW_FILES | SHOW_DIRS -/* Optional macro to be executed when a batch operation finishes. */ -#define RV_ALERT() beep() - /* This signal is not defined by POSIX, but should be present on all systems that have resizable terminals. */ #ifndef SIGWINCH @@ -70,6 +67,11 @@ #define BULK_INIT 5 #define BULK_THRESH 256 +#define ROVER "rover" +#define LOG_INFO false +#define LOG_ERR true +#define RV_PATH_MAX (PATH_MAX - 16) + /* Information associated to each entry in listing. */ typedef struct Row { char *name; @@ -138,6 +140,26 @@ struct Rover { #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define ISDIR(E) (strchr((E), '/') != NULL) +/* Macro to initialize the rover struct */ +#define INIT_ROVER(_r) \ + { \ + _r.nfiles = 0; \ + for (int idx = 0; idx < 10; idx++) { \ + _r.tabs[idx].esel = 0; \ + _r.tabs[idx].scroll = 0; \ + _r.tabs[idx].flags = RV_FLAGS; \ + } \ + } + +/* Optional macro to be executed when a batch operation finishes. */ +#define RV_ALERT() beep() + +/* Macro to manage logfile() func in order to read detailed info from source files */ +#define LOG(flag, msg, ...) \ + { \ + logfile("{%s} %s %s() <%d>: " msg "\n", (flag ? "ERR" : "INFO"), FILENAME(__FILE__), __func__, __LINE__, ##__VA_ARGS__); \ + } + /* Get file or dir name without path */ #define FILENAME(_path) (strrchr(_path, '/') + 1) @@ -155,6 +177,7 @@ struct Rover { _path[strlen(_path) - 1] == '/') \ _path[strlen(_path) - 1] = '\0'; \ } + /* Safe version of free() don't need assign NULL after free */ #define FREE(p) \ { \ @@ -163,16 +186,6 @@ struct Rover { (p) = NULL; \ } -#define ROVER "rover" -#define LOG_INFO false -#define LOG_ERR true -#define RV_PATH_MAX (PATH_MAX - 16) - -#define LOG(flag, msg, ...) \ - { \ - logfile("{%s} %s %s() <%d>: " msg "\n", (flag ? "ERR" : "INFO"), basename(__FILE__), __func__, __LINE__, ##__VA_ARGS__); \ - } - typedef int (*PROCESS)(const char *path); extern struct Rover rover; @@ -180,6 +193,7 @@ extern char rover_home_path[RV_PATH_MAX]; // Functions declaration void logfile(const char *format, ...); +void init_term(void); int endsession(void); void reload(void); void free_rows(Row **rowsp, int nfiles); diff --git a/src/ui_funcs.c b/src/ui_funcs.c index 0430e17..a854b6d 100755 --- a/src/ui_funcs.c +++ b/src/ui_funcs.c @@ -350,6 +350,27 @@ static EditStat get_line_edit(char *string) return CONTINUE; } +void init_marks(Marks *marks) +{ + strcpy(marks->dirpath, ""); + marks->bulk = BULK_INIT; + marks->nentries = 0; + marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); +} + +void free_marks(Marks *marks) +{ + int i; + + for (i = 0; marks->nentries && i < marks->bulk; i++) + if (marks->entries[i]) { + FREE(marks->entries[i]); + marks->nentries--; + } + + FREE(marks->entries); +} + /* Unmark all entries. */ static void mark_none(Marks *marks) { diff --git a/src/ui_funcs.h b/src/ui_funcs.h index 245afa4..c2ea114 100755 --- a/src/ui_funcs.h +++ b/src/ui_funcs.h @@ -11,8 +11,7 @@ #include #include /* environ PAGER SHELL EDITOR VISUAL */ #include /* dirname() */ - -#define STATUSPOS (COLS - 16) +#include /* Shell used to launch external programs. Defining this macro will force Rover to launch external @@ -25,6 +24,8 @@ `$EXTERNAL_PROGRAM [arg]`. */ #define RV_SHELL "/bin/sh" +#define STATUSPOS (COLS - 16) + /* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ #define RV_JUMP 10 @@ -147,6 +148,8 @@ void handlers(bool enable); int rover_getch(void); void message(Color color, char *fmt, ...); void update_view(void); +void init_marks(Marks *marks); +void free_marks(Marks *marks); void del_mark(Marks *marks, char *entry); void main_menu(void); From 91ae31dfcf8af5dcdbb7d733a2d5aaa81466f951 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 6 Jan 2023 20:55:27 +0100 Subject: [PATCH 17/18] Updated files --- CHANGES.md | 21 +++++++++++++++++++++ Makefile | 2 +- README.md | 35 ++++++++++++++++++----------------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 341e2d7..d1b9be7 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,26 @@ # Change Log +## [2.0.1] - 2022-12-31 + +### New revision + +- completely revised and improved functions +- now it can browse the directories with arrows (also scroll mouse) +- now most commands are associated with function keys +- better copy performance usign kernel function sendfile() +- confirmation is asked before overwriting existing files +- a formatted "rover.log" file is created with info and errors +- programming style is closer to GNU standards +- functions are divided into specific files +- Fixed several bugs +- also improved the handling of command line arguments +- VSCode folder with JSON files +- .clang-format file in order to keep a good style + +to do: +- implement a modern progress bar + + ## [1.0.1] - 2020-06-04 ### Bug Fixes diff --git a/Makefile b/Makefile index 2e428cd..7d23101 100755 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LIBS_NCURSESW := `$(PKG_CONFIG) --libs ncursesw` all: rover -rover: rover.c rover.h +rover: main.c rover.c rover.h os_funcs.c os_funcs.h ui_funcs.c ui_funcs.h $(CC) $(CFLAGS) $(CFLAGS_NCURSESW) -o $@ $< $(LDFLAGS) $(LIBS_NCURSESW) install: rover diff --git a/README.md b/README.md index 4353700..50a998a 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Introduction ============ - Rover is a file browser for the terminal. + This is a complete review of Rover is a file browser for the terminal. ![Rover screenshot](/../screenshots/screenshot.png?raw=true "Screenshot") @@ -28,22 +28,23 @@ Quick Start Basic Usage: ``` - q - quit Rover - ? - show Rover manual - j/k - move cursor down/up - J/K - move cursor down/up 10 lines - g/G - move cursor to top/bottom of listing - l - enter selected directory - h - go to parent directory - H - go to $HOME directory - 0-9 - change tab - RETURN - open $SHELL on the current directory - SPACE - open $PAGER with the selected file - e - open $VISUAL or $EDITOR with the selected file - / - start incremental search (RETURN to finish) - n/N - create new file/directory - R - rename selected file or directory - D - delete selected file or (empty) directory + or ? Show this manual. + Quit rover. + Arrow / Move cursor up/down. + Page / Move cursor up/down 10 lines. + / Move cursor to top/bottom of the list. + Arrow / Enter selected directory/Go to parent directory. + / Go to $HOME directory. + l Go to the target of the selected symbolic link. + t Open terminal $SHELL on the current directory. + Open $PAGER with the selected file. + Open $VISUAL or $EDITOR with the selected file. + Open $OPEN with the selected file. + F/D/H Toggle file/directory/hidden listing. + / Create new file/directory. + Rename selected file or directory. + Delete selected file or (empty) directory. + Toggle mark on the selected entry. ``` Please read rover(1) for more information. From 7212d17307a17da40441c572c60319ee8f0a882b Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 7 Jan 2023 22:09:37 +0100 Subject: [PATCH 18/18] renamed a variable --- src/os_funcs.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/os_funcs.c b/src/os_funcs.c index 3af15ae..abe7d3e 100644 --- a/src/os_funcs.c +++ b/src/os_funcs.c @@ -331,7 +331,7 @@ static ssize_t filecopy(const char *srcfile, const char *dstfile) struct stat sb = { 0 }; ssize_t chunk, bytesCopied, bytesTocopy = 0; char *msgerr, *data, *ptr, *end; - bool trayAgain = false; /* Applications may wish to fall back to read(2)/write(2) in + bool tryAgain = false; /* Applications may wish to fall back to read(2)/write(2) in the case where sendfile() fails with EINVAL or ENOSYS. */ input = open(srcfile, O_RDONLY); //try opening the srcfile file @@ -420,7 +420,7 @@ static ssize_t filecopy(const char *srcfile, const char *dstfile) case EINVAL: case ENOSYS: msgerr = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; - trayAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ + tryAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ break; case EIO: msgerr = "Unspecified error while reading from in_fd."; @@ -448,7 +448,7 @@ static ssize_t filecopy(const char *srcfile, const char *dstfile) CLEAR_MESSAGE(); } - if (trayAgain) { + if (tryAgain) { LOG(LOG_INFO, "%s try to copy again with read()/write() instead of sendfile().", ROVER); errno = 0; chunk = MIN(bytesTocopy, DEFAULT_CHUNK); //set better performance for copy @@ -465,13 +465,13 @@ static ssize_t filecopy(const char *srcfile, const char *dstfile) while (ptr < end) { //write data loop bytesTocopy = write(output, ptr, (size_t)(end - ptr)); if (bytesTocopy <= 0) { - trayAgain = false; // exit from do while loop + tryAgain = false; // exit from do while loop break; // exit from while loop } bytesCopied += bytesTocopy; ptr += bytesTocopy; } - } while (trayAgain); + } while (tryAgain); FREE(data); if (!errno) LOG(LOG_INFO, "%s copy with success using read()/write() instead of sendfile().", ROVER);